diff options
| -rw-r--r-- | drivers/rtc/Kconfig | 6 | ||||
| -rw-r--r-- | drivers/rtc/Makefile | 1 | ||||
| -rw-r--r-- | drivers/rtc/rtc-lp8788.c | 344 |
3 files changed, 351 insertions, 0 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 7a8dd0c2cae4..e94ae65af171 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig | |||
| @@ -204,6 +204,12 @@ config RTC_DRV_DS3232 | |||
| 204 | This driver can also be built as a module. If so, the module | 204 | This driver can also be built as a module. If so, the module |
| 205 | will be called rtc-ds3232. | 205 | will be called rtc-ds3232. |
| 206 | 206 | ||
| 207 | config RTC_DRV_LP8788 | ||
| 208 | tristate "TI LP8788 RTC driver" | ||
| 209 | depends on MFD_LP8788 | ||
| 210 | help | ||
| 211 | Say Y to enable support for the LP8788 RTC/ALARM driver. | ||
| 212 | |||
| 207 | config RTC_DRV_MAX6900 | 213 | config RTC_DRV_MAX6900 |
| 208 | tristate "Maxim MAX6900" | 214 | tristate "Maxim MAX6900" |
| 209 | help | 215 | help |
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 327c430fdb9f..1a837767b68d 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile | |||
| @@ -57,6 +57,7 @@ obj-$(CONFIG_RTC_DRV_IMXDI) += rtc-imxdi.o | |||
| 57 | obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o | 57 | obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o |
| 58 | obj-$(CONFIG_RTC_DRV_ISL12022) += rtc-isl12022.o | 58 | obj-$(CONFIG_RTC_DRV_ISL12022) += rtc-isl12022.o |
| 59 | obj-$(CONFIG_RTC_DRV_JZ4740) += rtc-jz4740.o | 59 | obj-$(CONFIG_RTC_DRV_JZ4740) += rtc-jz4740.o |
| 60 | obj-$(CONFIG_RTC_DRV_LP8788) += rtc-lp8788.o | ||
| 60 | obj-$(CONFIG_RTC_DRV_LPC32XX) += rtc-lpc32xx.o | 61 | obj-$(CONFIG_RTC_DRV_LPC32XX) += rtc-lpc32xx.o |
| 61 | obj-$(CONFIG_RTC_DRV_LOONGSON1) += rtc-ls1x.o | 62 | obj-$(CONFIG_RTC_DRV_LOONGSON1) += rtc-ls1x.o |
| 62 | obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o | 63 | obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o |
diff --git a/drivers/rtc/rtc-lp8788.c b/drivers/rtc/rtc-lp8788.c new file mode 100644 index 000000000000..4a4e78e2231c --- /dev/null +++ b/drivers/rtc/rtc-lp8788.c | |||
| @@ -0,0 +1,344 @@ | |||
| 1 | /* | ||
| 2 | * TI LP8788 MFD - rtc driver | ||
| 3 | * | ||
| 4 | * Copyright 2012 Texas Instruments | ||
| 5 | * | ||
| 6 | * Author: Milo(Woogyom) Kim <milo.kim@ti.com> | ||
| 7 | * | ||
| 8 | * This program is free software; you can redistribute it and/or modify | ||
| 9 | * it under the terms of the GNU General Public License version 2 as | ||
| 10 | * published by the Free Software Foundation. | ||
| 11 | * | ||
| 12 | */ | ||
| 13 | |||
| 14 | #include <linux/err.h> | ||
| 15 | #include <linux/irqdomain.h> | ||
| 16 | #include <linux/mfd/lp8788.h> | ||
| 17 | #include <linux/module.h> | ||
| 18 | #include <linux/platform_device.h> | ||
| 19 | #include <linux/rtc.h> | ||
| 20 | #include <linux/slab.h> | ||
| 21 | |||
| 22 | /* register address */ | ||
| 23 | #define LP8788_INTEN_3 0x05 | ||
| 24 | #define LP8788_RTC_UNLOCK 0x64 | ||
| 25 | #define LP8788_RTC_SEC 0x70 | ||
| 26 | #define LP8788_ALM1_SEC 0x77 | ||
| 27 | #define LP8788_ALM1_EN 0x7D | ||
| 28 | #define LP8788_ALM2_SEC 0x7E | ||
| 29 | #define LP8788_ALM2_EN 0x84 | ||
| 30 | |||
| 31 | /* mask/shift bits */ | ||
| 32 | #define LP8788_INT_RTC_ALM1_M BIT(1) /* Addr 05h */ | ||
| 33 | #define LP8788_INT_RTC_ALM1_S 1 | ||
| 34 | #define LP8788_INT_RTC_ALM2_M BIT(2) /* Addr 05h */ | ||
| 35 | #define LP8788_INT_RTC_ALM2_S 2 | ||
| 36 | #define LP8788_ALM_EN_M BIT(7) /* Addr 7Dh or 84h */ | ||
| 37 | #define LP8788_ALM_EN_S 7 | ||
| 38 | |||
| 39 | #define DEFAULT_ALARM_SEL LP8788_ALARM_1 | ||
| 40 | #define LP8788_MONTH_OFFSET 1 | ||
| 41 | #define LP8788_BASE_YEAR 2000 | ||
| 42 | #define MAX_WDAY_BITS 7 | ||
| 43 | #define LP8788_WDAY_SET 1 | ||
| 44 | #define RTC_UNLOCK 0x1 | ||
| 45 | #define RTC_LATCH 0x2 | ||
| 46 | #define ALARM_IRQ_FLAG (RTC_IRQF | RTC_AF) | ||
| 47 | |||
| 48 | enum lp8788_time { | ||
| 49 | LPTIME_SEC, | ||
| 50 | LPTIME_MIN, | ||
| 51 | LPTIME_HOUR, | ||
| 52 | LPTIME_MDAY, | ||
| 53 | LPTIME_MON, | ||
| 54 | LPTIME_YEAR, | ||
| 55 | LPTIME_WDAY, | ||
| 56 | LPTIME_MAX, | ||
| 57 | }; | ||
| 58 | |||
| 59 | struct lp8788_rtc { | ||
| 60 | struct lp8788 *lp; | ||
| 61 | struct rtc_device *rdev; | ||
| 62 | enum lp8788_alarm_sel alarm; | ||
| 63 | int irq; | ||
| 64 | }; | ||
| 65 | |||
| 66 | static const u8 addr_alarm_sec[LP8788_ALARM_MAX] = { | ||
| 67 | LP8788_ALM1_SEC, | ||
| 68 | LP8788_ALM2_SEC, | ||
| 69 | }; | ||
| 70 | |||
| 71 | static const u8 addr_alarm_en[LP8788_ALARM_MAX] = { | ||
| 72 | LP8788_ALM1_EN, | ||
| 73 | LP8788_ALM2_EN, | ||
| 74 | }; | ||
| 75 | |||
| 76 | static const u8 mask_alarm_en[LP8788_ALARM_MAX] = { | ||
| 77 | LP8788_INT_RTC_ALM1_M, | ||
| 78 | LP8788_INT_RTC_ALM2_M, | ||
| 79 | }; | ||
| 80 | |||
| 81 | static const u8 shift_alarm_en[LP8788_ALARM_MAX] = { | ||
| 82 | LP8788_INT_RTC_ALM1_S, | ||
| 83 | LP8788_INT_RTC_ALM2_S, | ||
| 84 | }; | ||
| 85 | |||
| 86 | static int _to_tm_wday(u8 lp8788_wday) | ||
| 87 | { | ||
| 88 | int i; | ||
| 89 | |||
| 90 | if (lp8788_wday == 0) | ||
| 91 | return 0; | ||
| 92 | |||
| 93 | /* lookup defined weekday from read register value */ | ||
| 94 | for (i = 0; i < MAX_WDAY_BITS; i++) { | ||
| 95 | if ((lp8788_wday >> i) == LP8788_WDAY_SET) | ||
| 96 | break; | ||
| 97 | } | ||
| 98 | |||
| 99 | return i + 1; | ||
| 100 | } | ||
| 101 | |||
| 102 | static inline int _to_lp8788_wday(int tm_wday) | ||
| 103 | { | ||
| 104 | return LP8788_WDAY_SET << (tm_wday - 1); | ||
| 105 | } | ||
| 106 | |||
| 107 | static void lp8788_rtc_unlock(struct lp8788 *lp) | ||
| 108 | { | ||
| 109 | lp8788_write_byte(lp, LP8788_RTC_UNLOCK, RTC_UNLOCK); | ||
| 110 | lp8788_write_byte(lp, LP8788_RTC_UNLOCK, RTC_LATCH); | ||
| 111 | } | ||
| 112 | |||
| 113 | static int lp8788_rtc_read_time(struct device *dev, struct rtc_time *tm) | ||
| 114 | { | ||
| 115 | struct lp8788_rtc *rtc = dev_get_drvdata(dev); | ||
| 116 | struct lp8788 *lp = rtc->lp; | ||
| 117 | u8 data[LPTIME_MAX]; | ||
| 118 | int ret; | ||
| 119 | |||
| 120 | lp8788_rtc_unlock(lp); | ||
| 121 | |||
| 122 | ret = lp8788_read_multi_bytes(lp, LP8788_RTC_SEC, data, LPTIME_MAX); | ||
| 123 | if (ret) | ||
| 124 | return ret; | ||
| 125 | |||
| 126 | tm->tm_sec = data[LPTIME_SEC]; | ||
| 127 | tm->tm_min = data[LPTIME_MIN]; | ||
| 128 | tm->tm_hour = data[LPTIME_HOUR]; | ||
| 129 | tm->tm_mday = data[LPTIME_MDAY]; | ||
| 130 | tm->tm_mon = data[LPTIME_MON] - LP8788_MONTH_OFFSET; | ||
| 131 | tm->tm_year = data[LPTIME_YEAR] + LP8788_BASE_YEAR - 1900; | ||
| 132 | tm->tm_wday = _to_tm_wday(data[LPTIME_WDAY]); | ||
| 133 | |||
| 134 | return 0; | ||
| 135 | } | ||
| 136 | |||
| 137 | static int lp8788_rtc_set_time(struct device *dev, struct rtc_time *tm) | ||
| 138 | { | ||
| 139 | struct lp8788_rtc *rtc = dev_get_drvdata(dev); | ||
| 140 | struct lp8788 *lp = rtc->lp; | ||
| 141 | u8 data[LPTIME_MAX - 1]; | ||
| 142 | int ret, i, year; | ||
| 143 | |||
| 144 | year = tm->tm_year + 1900 - LP8788_BASE_YEAR; | ||
| 145 | if (year < 0) { | ||
| 146 | dev_err(lp->dev, "invalid year: %d\n", year); | ||
| 147 | return -EINVAL; | ||
| 148 | } | ||
| 149 | |||
| 150 | /* because rtc weekday is a readonly register, do not update */ | ||
| 151 | data[LPTIME_SEC] = tm->tm_sec; | ||
| 152 | data[LPTIME_MIN] = tm->tm_min; | ||
| 153 | data[LPTIME_HOUR] = tm->tm_hour; | ||
| 154 | data[LPTIME_MDAY] = tm->tm_mday; | ||
| 155 | data[LPTIME_MON] = tm->tm_mon + LP8788_MONTH_OFFSET; | ||
| 156 | data[LPTIME_YEAR] = year; | ||
| 157 | |||
| 158 | for (i = 0; i < ARRAY_SIZE(data); i++) { | ||
| 159 | ret = lp8788_write_byte(lp, LP8788_RTC_SEC + i, data[i]); | ||
| 160 | if (ret) | ||
| 161 | return ret; | ||
| 162 | } | ||
| 163 | |||
| 164 | return 0; | ||
| 165 | } | ||
| 166 | |||
| 167 | static int lp8788_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) | ||
| 168 | { | ||
| 169 | struct lp8788_rtc *rtc = dev_get_drvdata(dev); | ||
| 170 | struct lp8788 *lp = rtc->lp; | ||
| 171 | struct rtc_time *tm = &alarm->time; | ||
| 172 | u8 addr, data[LPTIME_MAX]; | ||
| 173 | int ret; | ||
| 174 | |||
| 175 | addr = addr_alarm_sec[rtc->alarm]; | ||
| 176 | ret = lp8788_read_multi_bytes(lp, addr, data, LPTIME_MAX); | ||
| 177 | if (ret) | ||
| 178 | return ret; | ||
| 179 | |||
| 180 | tm->tm_sec = data[LPTIME_SEC]; | ||
| 181 | tm->tm_min = data[LPTIME_MIN]; | ||
| 182 | tm->tm_hour = data[LPTIME_HOUR]; | ||
| 183 | tm->tm_mday = data[LPTIME_MDAY]; | ||
| 184 | tm->tm_mon = data[LPTIME_MON] - LP8788_MONTH_OFFSET; | ||
| 185 | tm->tm_year = data[LPTIME_YEAR] + LP8788_BASE_YEAR - 1900; | ||
| 186 | tm->tm_wday = _to_tm_wday(data[LPTIME_WDAY]); | ||
| 187 | alarm->enabled = data[LPTIME_WDAY] & LP8788_ALM_EN_M; | ||
| 188 | |||
| 189 | return 0; | ||
| 190 | } | ||
| 191 | |||
| 192 | static int lp8788_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) | ||
| 193 | { | ||
| 194 | struct lp8788_rtc *rtc = dev_get_drvdata(dev); | ||
| 195 | struct lp8788 *lp = rtc->lp; | ||
| 196 | struct rtc_time *tm = &alarm->time; | ||
| 197 | u8 addr, data[LPTIME_MAX]; | ||
| 198 | int ret, i, year; | ||
| 199 | |||
| 200 | year = tm->tm_year + 1900 - LP8788_BASE_YEAR; | ||
| 201 | if (year < 0) { | ||
| 202 | dev_err(lp->dev, "invalid year: %d\n", year); | ||
| 203 | return -EINVAL; | ||
| 204 | } | ||
| 205 | |||
| 206 | data[LPTIME_SEC] = tm->tm_sec; | ||
| 207 | data[LPTIME_MIN] = tm->tm_min; | ||
| 208 | data[LPTIME_HOUR] = tm->tm_hour; | ||
| 209 | data[LPTIME_MDAY] = tm->tm_mday; | ||
| 210 | data[LPTIME_MON] = tm->tm_mon + LP8788_MONTH_OFFSET; | ||
| 211 | data[LPTIME_YEAR] = year; | ||
| 212 | data[LPTIME_WDAY] = _to_lp8788_wday(tm->tm_wday); | ||
| 213 | |||
| 214 | for (i = 0; i < ARRAY_SIZE(data); i++) { | ||
| 215 | addr = addr_alarm_sec[rtc->alarm] + i; | ||
| 216 | ret = lp8788_write_byte(lp, addr, data[i]); | ||
| 217 | if (ret) | ||
| 218 | return ret; | ||
| 219 | } | ||
| 220 | |||
| 221 | alarm->enabled = 1; | ||
| 222 | addr = addr_alarm_en[rtc->alarm]; | ||
| 223 | |||
| 224 | return lp8788_update_bits(lp, addr, LP8788_ALM_EN_M, | ||
| 225 | alarm->enabled << LP8788_ALM_EN_S); | ||
| 226 | } | ||
| 227 | |||
| 228 | static int lp8788_alarm_irq_enable(struct device *dev, unsigned int enable) | ||
| 229 | { | ||
| 230 | struct lp8788_rtc *rtc = dev_get_drvdata(dev); | ||
| 231 | struct lp8788 *lp = rtc->lp; | ||
| 232 | u8 mask, shift; | ||
| 233 | |||
| 234 | if (!rtc->irq) | ||
| 235 | return -EIO; | ||
| 236 | |||
| 237 | mask = mask_alarm_en[rtc->alarm]; | ||
| 238 | shift = shift_alarm_en[rtc->alarm]; | ||
| 239 | |||
| 240 | return lp8788_update_bits(lp, LP8788_INTEN_3, mask, enable << shift); | ||
| 241 | } | ||
| 242 | |||
| 243 | static const struct rtc_class_ops lp8788_rtc_ops = { | ||
| 244 | .read_time = lp8788_rtc_read_time, | ||
| 245 | .set_time = lp8788_rtc_set_time, | ||
| 246 | .read_alarm = lp8788_read_alarm, | ||
| 247 | .set_alarm = lp8788_set_alarm, | ||
| 248 | .alarm_irq_enable = lp8788_alarm_irq_enable, | ||
| 249 | }; | ||
| 250 | |||
| 251 | static irqreturn_t lp8788_alarm_irq_handler(int irq, void *ptr) | ||
| 252 | { | ||
| 253 | struct lp8788_rtc *rtc = ptr; | ||
| 254 | |||
| 255 | rtc_update_irq(rtc->rdev, 1, ALARM_IRQ_FLAG); | ||
| 256 | return IRQ_HANDLED; | ||
| 257 | } | ||
| 258 | |||
| 259 | static int lp8788_alarm_irq_register(struct platform_device *pdev, | ||
| 260 | struct lp8788_rtc *rtc) | ||
| 261 | { | ||
| 262 | struct resource *r; | ||
| 263 | struct lp8788 *lp = rtc->lp; | ||
| 264 | struct irq_domain *irqdm = lp->irqdm; | ||
| 265 | int irq; | ||
| 266 | |||
| 267 | rtc->irq = 0; | ||
| 268 | |||
| 269 | /* even the alarm IRQ number is not specified, rtc time should work */ | ||
| 270 | r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, LP8788_ALM_IRQ); | ||
| 271 | if (!r) | ||
| 272 | return 0; | ||
| 273 | |||
| 274 | if (rtc->alarm == LP8788_ALARM_1) | ||
| 275 | irq = r->start; | ||
| 276 | else | ||
| 277 | irq = r->end; | ||
| 278 | |||
| 279 | rtc->irq = irq_create_mapping(irqdm, irq); | ||
| 280 | |||
| 281 | return request_threaded_irq(rtc->irq, NULL, lp8788_alarm_irq_handler, | ||
| 282 | 0, LP8788_ALM_IRQ, rtc); | ||
| 283 | } | ||
| 284 | |||
| 285 | static void lp8788_alarm_irq_unregister(struct lp8788_rtc *rtc) | ||
| 286 | { | ||
| 287 | if (rtc->irq) | ||
| 288 | free_irq(rtc->irq, rtc); | ||
| 289 | } | ||
| 290 | |||
| 291 | static int lp8788_rtc_probe(struct platform_device *pdev) | ||
| 292 | { | ||
| 293 | struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); | ||
| 294 | struct lp8788_rtc *rtc; | ||
| 295 | struct device *dev = &pdev->dev; | ||
| 296 | |||
| 297 | rtc = devm_kzalloc(dev, sizeof(struct lp8788_rtc), GFP_KERNEL); | ||
| 298 | if (!rtc) | ||
| 299 | return -ENOMEM; | ||
| 300 | |||
| 301 | rtc->lp = lp; | ||
| 302 | rtc->alarm = lp->pdata ? lp->pdata->alarm_sel : DEFAULT_ALARM_SEL; | ||
| 303 | platform_set_drvdata(pdev, rtc); | ||
| 304 | |||
| 305 | device_init_wakeup(dev, 1); | ||
| 306 | |||
| 307 | rtc->rdev = rtc_device_register("lp8788_rtc", dev, | ||
| 308 | &lp8788_rtc_ops, THIS_MODULE); | ||
| 309 | if (IS_ERR(rtc->rdev)) { | ||
| 310 | dev_err(dev, "can not register rtc device\n"); | ||
| 311 | return PTR_ERR(rtc->rdev); | ||
| 312 | } | ||
| 313 | |||
| 314 | if (lp8788_alarm_irq_register(pdev, rtc)) | ||
| 315 | dev_warn(lp->dev, "no rtc irq handler\n"); | ||
| 316 | |||
| 317 | return 0; | ||
| 318 | } | ||
| 319 | |||
| 320 | static int lp8788_rtc_remove(struct platform_device *pdev) | ||
| 321 | { | ||
| 322 | struct lp8788_rtc *rtc = platform_get_drvdata(pdev); | ||
| 323 | |||
| 324 | lp8788_alarm_irq_unregister(rtc); | ||
| 325 | rtc_device_unregister(rtc->rdev); | ||
| 326 | platform_set_drvdata(pdev, NULL); | ||
| 327 | |||
| 328 | return 0; | ||
| 329 | } | ||
| 330 | |||
| 331 | static struct platform_driver lp8788_rtc_driver = { | ||
| 332 | .probe = lp8788_rtc_probe, | ||
| 333 | .remove = lp8788_rtc_remove, | ||
| 334 | .driver = { | ||
| 335 | .name = LP8788_DEV_RTC, | ||
| 336 | .owner = THIS_MODULE, | ||
| 337 | }, | ||
| 338 | }; | ||
| 339 | module_platform_driver(lp8788_rtc_driver); | ||
| 340 | |||
| 341 | MODULE_DESCRIPTION("Texas Instruments LP8788 RTC Driver"); | ||
| 342 | MODULE_AUTHOR("Milo Kim"); | ||
| 343 | MODULE_LICENSE("GPL"); | ||
| 344 | MODULE_ALIAS("platform:lp8788-rtc"); | ||
