323 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * TI LP8788 MFD - rtc driver4 *5 * Copyright 2012 Texas Instruments6 *7 * Author: Milo(Woogyom) Kim <milo.kim@ti.com>8 */9 10#include <linux/err.h>11#include <linux/irqdomain.h>12#include <linux/mfd/lp8788.h>13#include <linux/module.h>14#include <linux/platform_device.h>15#include <linux/rtc.h>16#include <linux/slab.h>17 18/* register address */19#define LP8788_INTEN_3 0x0520#define LP8788_RTC_UNLOCK 0x6421#define LP8788_RTC_SEC 0x7022#define LP8788_ALM1_SEC 0x7723#define LP8788_ALM1_EN 0x7D24#define LP8788_ALM2_SEC 0x7E25#define LP8788_ALM2_EN 0x8426 27/* mask/shift bits */28#define LP8788_INT_RTC_ALM1_M BIT(1) /* Addr 05h */29#define LP8788_INT_RTC_ALM1_S 130#define LP8788_INT_RTC_ALM2_M BIT(2) /* Addr 05h */31#define LP8788_INT_RTC_ALM2_S 232#define LP8788_ALM_EN_M BIT(7) /* Addr 7Dh or 84h */33#define LP8788_ALM_EN_S 734 35#define DEFAULT_ALARM_SEL LP8788_ALARM_136#define LP8788_MONTH_OFFSET 137#define LP8788_BASE_YEAR 200038#define MAX_WDAY_BITS 739#define LP8788_WDAY_SET 140#define RTC_UNLOCK 0x141#define RTC_LATCH 0x242#define ALARM_IRQ_FLAG (RTC_IRQF | RTC_AF)43 44enum lp8788_time {45 LPTIME_SEC,46 LPTIME_MIN,47 LPTIME_HOUR,48 LPTIME_MDAY,49 LPTIME_MON,50 LPTIME_YEAR,51 LPTIME_WDAY,52 LPTIME_MAX,53};54 55struct lp8788_rtc {56 struct lp8788 *lp;57 struct rtc_device *rdev;58 enum lp8788_alarm_sel alarm;59 int irq;60};61 62static const u8 addr_alarm_sec[LP8788_ALARM_MAX] = {63 LP8788_ALM1_SEC,64 LP8788_ALM2_SEC,65};66 67static const u8 addr_alarm_en[LP8788_ALARM_MAX] = {68 LP8788_ALM1_EN,69 LP8788_ALM2_EN,70};71 72static const u8 mask_alarm_en[LP8788_ALARM_MAX] = {73 LP8788_INT_RTC_ALM1_M,74 LP8788_INT_RTC_ALM2_M,75};76 77static const u8 shift_alarm_en[LP8788_ALARM_MAX] = {78 LP8788_INT_RTC_ALM1_S,79 LP8788_INT_RTC_ALM2_S,80};81 82static int _to_tm_wday(u8 lp8788_wday)83{84 int i;85 86 if (lp8788_wday == 0)87 return 0;88 89 /* lookup defined weekday from read register value */90 for (i = 0; i < MAX_WDAY_BITS; i++) {91 if ((lp8788_wday >> i) == LP8788_WDAY_SET)92 break;93 }94 95 return i + 1;96}97 98static inline int _to_lp8788_wday(int tm_wday)99{100 return LP8788_WDAY_SET << (tm_wday - 1);101}102 103static void lp8788_rtc_unlock(struct lp8788 *lp)104{105 lp8788_write_byte(lp, LP8788_RTC_UNLOCK, RTC_UNLOCK);106 lp8788_write_byte(lp, LP8788_RTC_UNLOCK, RTC_LATCH);107}108 109static int lp8788_rtc_read_time(struct device *dev, struct rtc_time *tm)110{111 struct lp8788_rtc *rtc = dev_get_drvdata(dev);112 struct lp8788 *lp = rtc->lp;113 u8 data[LPTIME_MAX];114 int ret;115 116 lp8788_rtc_unlock(lp);117 118 ret = lp8788_read_multi_bytes(lp, LP8788_RTC_SEC, data, LPTIME_MAX);119 if (ret)120 return ret;121 122 tm->tm_sec = data[LPTIME_SEC];123 tm->tm_min = data[LPTIME_MIN];124 tm->tm_hour = data[LPTIME_HOUR];125 tm->tm_mday = data[LPTIME_MDAY];126 tm->tm_mon = data[LPTIME_MON] - LP8788_MONTH_OFFSET;127 tm->tm_year = data[LPTIME_YEAR] + LP8788_BASE_YEAR - 1900;128 tm->tm_wday = _to_tm_wday(data[LPTIME_WDAY]);129 130 return 0;131}132 133static int lp8788_rtc_set_time(struct device *dev, struct rtc_time *tm)134{135 struct lp8788_rtc *rtc = dev_get_drvdata(dev);136 struct lp8788 *lp = rtc->lp;137 u8 data[LPTIME_MAX - 1];138 int ret, i, year;139 140 year = tm->tm_year + 1900 - LP8788_BASE_YEAR;141 if (year < 0) {142 dev_err(lp->dev, "invalid year: %d\n", year);143 return -EINVAL;144 }145 146 /* because rtc weekday is a readonly register, do not update */147 data[LPTIME_SEC] = tm->tm_sec;148 data[LPTIME_MIN] = tm->tm_min;149 data[LPTIME_HOUR] = tm->tm_hour;150 data[LPTIME_MDAY] = tm->tm_mday;151 data[LPTIME_MON] = tm->tm_mon + LP8788_MONTH_OFFSET;152 data[LPTIME_YEAR] = year;153 154 for (i = 0; i < ARRAY_SIZE(data); i++) {155 ret = lp8788_write_byte(lp, LP8788_RTC_SEC + i, data[i]);156 if (ret)157 return ret;158 }159 160 return 0;161}162 163static int lp8788_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)164{165 struct lp8788_rtc *rtc = dev_get_drvdata(dev);166 struct lp8788 *lp = rtc->lp;167 struct rtc_time *tm = &alarm->time;168 u8 addr, data[LPTIME_MAX];169 int ret;170 171 addr = addr_alarm_sec[rtc->alarm];172 ret = lp8788_read_multi_bytes(lp, addr, data, LPTIME_MAX);173 if (ret)174 return ret;175 176 tm->tm_sec = data[LPTIME_SEC];177 tm->tm_min = data[LPTIME_MIN];178 tm->tm_hour = data[LPTIME_HOUR];179 tm->tm_mday = data[LPTIME_MDAY];180 tm->tm_mon = data[LPTIME_MON] - LP8788_MONTH_OFFSET;181 tm->tm_year = data[LPTIME_YEAR] + LP8788_BASE_YEAR - 1900;182 tm->tm_wday = _to_tm_wday(data[LPTIME_WDAY]);183 alarm->enabled = data[LPTIME_WDAY] & LP8788_ALM_EN_M;184 185 return 0;186}187 188static int lp8788_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)189{190 struct lp8788_rtc *rtc = dev_get_drvdata(dev);191 struct lp8788 *lp = rtc->lp;192 struct rtc_time *tm = &alarm->time;193 u8 addr, data[LPTIME_MAX];194 int ret, i, year;195 196 year = tm->tm_year + 1900 - LP8788_BASE_YEAR;197 if (year < 0) {198 dev_err(lp->dev, "invalid year: %d\n", year);199 return -EINVAL;200 }201 202 data[LPTIME_SEC] = tm->tm_sec;203 data[LPTIME_MIN] = tm->tm_min;204 data[LPTIME_HOUR] = tm->tm_hour;205 data[LPTIME_MDAY] = tm->tm_mday;206 data[LPTIME_MON] = tm->tm_mon + LP8788_MONTH_OFFSET;207 data[LPTIME_YEAR] = year;208 data[LPTIME_WDAY] = _to_lp8788_wday(tm->tm_wday);209 210 for (i = 0; i < ARRAY_SIZE(data); i++) {211 addr = addr_alarm_sec[rtc->alarm] + i;212 ret = lp8788_write_byte(lp, addr, data[i]);213 if (ret)214 return ret;215 }216 217 alarm->enabled = 1;218 addr = addr_alarm_en[rtc->alarm];219 220 return lp8788_update_bits(lp, addr, LP8788_ALM_EN_M,221 alarm->enabled << LP8788_ALM_EN_S);222}223 224static int lp8788_alarm_irq_enable(struct device *dev, unsigned int enable)225{226 struct lp8788_rtc *rtc = dev_get_drvdata(dev);227 struct lp8788 *lp = rtc->lp;228 u8 mask, shift;229 230 if (!rtc->irq)231 return -EIO;232 233 mask = mask_alarm_en[rtc->alarm];234 shift = shift_alarm_en[rtc->alarm];235 236 return lp8788_update_bits(lp, LP8788_INTEN_3, mask, enable << shift);237}238 239static const struct rtc_class_ops lp8788_rtc_ops = {240 .read_time = lp8788_rtc_read_time,241 .set_time = lp8788_rtc_set_time,242 .read_alarm = lp8788_read_alarm,243 .set_alarm = lp8788_set_alarm,244 .alarm_irq_enable = lp8788_alarm_irq_enable,245};246 247static irqreturn_t lp8788_alarm_irq_handler(int irq, void *ptr)248{249 struct lp8788_rtc *rtc = ptr;250 251 rtc_update_irq(rtc->rdev, 1, ALARM_IRQ_FLAG);252 return IRQ_HANDLED;253}254 255static int lp8788_alarm_irq_register(struct platform_device *pdev,256 struct lp8788_rtc *rtc)257{258 struct resource *r;259 struct lp8788 *lp = rtc->lp;260 struct irq_domain *irqdm = lp->irqdm;261 int irq;262 263 rtc->irq = 0;264 265 /* even the alarm IRQ number is not specified, rtc time should work */266 r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, LP8788_ALM_IRQ);267 if (!r)268 return 0;269 270 if (rtc->alarm == LP8788_ALARM_1)271 irq = r->start;272 else273 irq = r->end;274 275 rtc->irq = irq_create_mapping(irqdm, irq);276 277 return devm_request_threaded_irq(&pdev->dev, rtc->irq, NULL,278 lp8788_alarm_irq_handler,279 0, LP8788_ALM_IRQ, rtc);280}281 282static int lp8788_rtc_probe(struct platform_device *pdev)283{284 struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent);285 struct lp8788_rtc *rtc;286 struct device *dev = &pdev->dev;287 288 rtc = devm_kzalloc(dev, sizeof(struct lp8788_rtc), GFP_KERNEL);289 if (!rtc)290 return -ENOMEM;291 292 rtc->lp = lp;293 rtc->alarm = lp->pdata ? lp->pdata->alarm_sel : DEFAULT_ALARM_SEL;294 platform_set_drvdata(pdev, rtc);295 296 device_init_wakeup(dev, 1);297 298 rtc->rdev = devm_rtc_device_register(dev, "lp8788_rtc",299 &lp8788_rtc_ops, THIS_MODULE);300 if (IS_ERR(rtc->rdev)) {301 dev_err(dev, "can not register rtc device\n");302 return PTR_ERR(rtc->rdev);303 }304 305 if (lp8788_alarm_irq_register(pdev, rtc))306 dev_warn(lp->dev, "no rtc irq handler\n");307 308 return 0;309}310 311static struct platform_driver lp8788_rtc_driver = {312 .probe = lp8788_rtc_probe,313 .driver = {314 .name = LP8788_DEV_RTC,315 },316};317module_platform_driver(lp8788_rtc_driver);318 319MODULE_DESCRIPTION("Texas Instruments LP8788 RTC Driver");320MODULE_AUTHOR("Milo Kim");321MODULE_LICENSE("GPL");322MODULE_ALIAS("platform:lp8788-rtc");323