Message ID | A874F61F95741C4A9BA573A70FE3998F69E108CA@DQHE02.ent.ti.com |
---|---|
State | Superseded |
Headers | show |
Hello, On Fri, Dec 21, 2012 at 1:25 PM, Kim, Milo <Milo.Kim@ti.com> wrote: > TI LP8788 PMU supports regulators, battery charger, RTC, ADC, backlight driver > and current sinks. > This patch enables LP8788 RTC module. > Nice one, but i just have some queries or questions below, > Patch v3. > remove __devinit and __devexit. > add weekday definition and comments. > > Patch v2. > use IRQ domain for handling alarm IRQ. > replace module_init() and module_exit() with module_platform_driver(). > clean up code for _probe() and _remove(). > > Patch v1. > initial patch > > Signed-off-by: Milo(Woogyom) Kim <milo.kim@ti.com> > --- > diff --git a/drivers/rtc/rtc-lp8788.c b/drivers/rtc/rtc-lp8788.c > new file mode 100644 > index 0000000..2363e52 > --- /dev/null > +++ b/drivers/rtc/rtc-lp8788.c > @@ -0,0 +1,345 @@ > +/* > + * TI LP8788 MFD - rtc driver > + * > + * Copyright 2012 Texas Instruments > + * > + * Author: Milo(Woogyom) Kim <milo.kim@ti.com> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + */ > + > +#include <linux/err.h> > +#include <linux/irqdomain.h> > +#include <linux/mfd/lp8788.h> > +#include <linux/module.h> > +#include <linux/platform_device.h> > +#include <linux/rtc.h> > +#include <linux/slab.h> > + > +/* register address */ > +#define LP8788_INTEN_3 0x05 > +#define LP8788_RTC_UNLOCK 0x64 > +#define LP8788_RTC_SEC 0x70 > +#define LP8788_ALM1_SEC 0x77 > +#define LP8788_ALM1_EN 0x7D > +#define LP8788_ALM2_SEC 0x7E > +#define LP8788_ALM2_EN 0x84 > + > +/* mask/shift bits */ > +#define LP8788_INT_RTC_ALM1_M BIT(1) /* Addr 05h */ > +#define LP8788_INT_RTC_ALM1_S 1 > +#define LP8788_INT_RTC_ALM2_M BIT(2) /* Addr 05h */ > +#define LP8788_INT_RTC_ALM2_S 2 > +#define LP8788_ALM_EN_M BIT(7) /* Addr 7Dh or 84h */ > +#define LP8788_ALM_EN_S 7 > + > +#define DEFAULT_ALARM_SEL LP8788_ALARM_1 > +#define LP8788_MONTH_OFFSET 1 > +#define LP8788_BASE_YEAR 2000 > +#define MAX_WDAY_BITS 7 > +#define LP8788_WDAY_SET 1 > +#define RTC_UNLOCK 0x1 > +#define RTC_LATCH 0x2 > +#define ALARM_IRQ_FLAG (RTC_IRQF | RTC_AF) > + > +enum lp8788_time { > + LPTIME_SEC, > + LPTIME_MIN, > + LPTIME_HOUR, > + LPTIME_MDAY, > + LPTIME_MON, > + LPTIME_YEAR, > + LPTIME_WDAY, > + LPTIME_MAX, > +}; > + > +struct lp8788_rtc { > + struct lp8788 *lp; > + struct rtc_device *rdev; > + enum lp8788_alarm_sel alarm; > + int irq; > +}; > + > +static const u8 addr_alarm_sec[LP8788_ALARM_MAX] = { > + LP8788_ALM1_SEC, > + LP8788_ALM2_SEC, > +}; > + > +static const u8 addr_alarm_en[LP8788_ALARM_MAX] = { > + LP8788_ALM1_EN, > + LP8788_ALM2_EN, > +}; > + > +static const u8 mask_alarm_en[LP8788_ALARM_MAX] = { > + LP8788_INT_RTC_ALM1_M, > + LP8788_INT_RTC_ALM2_M, > +}; > + > +static const u8 shift_alarm_en[LP8788_ALARM_MAX] = { > + LP8788_INT_RTC_ALM1_S, > + LP8788_INT_RTC_ALM2_S, > +}; > + <snip> > +static int lp8788_alarm_irq_enable(struct device *dev, unsigned int enable) > +{ > + struct lp8788_rtc *rtc = dev_get_drvdata(dev); > + struct lp8788 *lp = rtc->lp; > + u8 mask, shift; > + > + mask = mask_alarm_en[rtc->alarm]; > + shift = shift_alarm_en[rtc->alarm]; > + > + return lp8788_update_bits(lp, LP8788_INTEN_3, mask, enable << shift); i have no idea about this function but just asking, this will be called from ioctl right? so what if user calls RTC_AIE_ON and you return the IRQ status ? enable or disable like that? i guess a failure in setting for no irq and a success otherwise. > +} > + > +static const struct rtc_class_ops lp8788_rtc_ops = { > + .read_time = lp8788_rtc_read_time, > + .set_time = lp8788_rtc_set_time, > + .read_alarm = lp8788_read_alarm, > + .set_alarm = lp8788_set_alarm, > + .alarm_irq_enable = lp8788_alarm_irq_enable, > +}; > + > +static irqreturn_t lp8788_alarm_irq_handler(int irq, void *ptr) > +{ > + struct lp8788_rtc *rtc = ptr; > + > + rtc_update_irq(rtc->rdev, 1, ALARM_IRQ_FLAG); > + return IRQ_HANDLED; > +} > + > +static int lp8788_alarm_irq_register(struct platform_device *pdev, > + struct lp8788_rtc *rtc) > +{ > + struct resource *r; > + struct lp8788 *lp = rtc->lp; > + struct irq_domain *irqdm = lp->irqdm; > + int irq; > + > + rtc->irq = 0; > + > + if (!lp->pdata) { > + dev_warn(lp->dev, "no platform data for rtc irq\n"); but we didn't registered irqs right? if we return 0 here means that we have a vaild irq no? > + return 0; > + } > + > + r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, LP8788_ALM_IRQ); > + if (!r) > + return 0; > + > + if (rtc->alarm == LP8788_ALARM_1) > + irq = r->start; > + else > + irq = r->end; > + > + rtc->irq = irq_create_mapping(irqdm, irq); > + > + return request_threaded_irq(rtc->irq, NULL, lp8788_alarm_irq_handler, > + 0, LP8788_ALM_IRQ, rtc); > +} > + > +static void lp8788_alarm_irq_unregister(struct lp8788_rtc *rtc) > +{ > + if (rtc->irq) > + free_irq(rtc->irq, rtc); > +} > + > +static int lp8788_rtc_probe(struct platform_device *pdev) > +{ > + struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); > + struct lp8788_rtc *rtc; > + struct device *dev = &pdev->dev; > + > + rtc = devm_kzalloc(lp->dev, sizeof(struct lp8788_rtc), GFP_KERNEL); lp->dev may be parent device, is &pdev->dev, or dev may be correct? > + if (!rtc) > + return -ENOMEM; > + > + rtc->lp = lp; > + rtc->alarm = lp->pdata ? lp->pdata->alarm_sel : DEFAULT_ALARM_SEL; > + platform_set_drvdata(pdev, rtc); > + > + device_init_wakeup(dev, 1); > + > + rtc->rdev = rtc_device_register("lp8788_rtc", dev, > + &lp8788_rtc_ops, THIS_MODULE); > + if (IS_ERR(rtc->rdev)) { > + dev_err(dev, "can not register rtc device\n"); return PTR_ERR(rtc->rdev); may be better? > + return -ENODEV; > + } > + > + if (lp8788_alarm_irq_register(pdev, rtc)) > + dev_warn(lp->dev, "no rtc irq handler\n"); > + even if there is no alarm irq, we can still use the rtc to get the time, but there wont be RTC_AIE_ON ioctl enabled ? > + return 0; > +} > + > -- > To unsubscribe from this list: send the line "unsubscribe linux-kernel" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html > Please read the FAQ at http://www.tux.org/lkml/
Hi Devendra, > > +static int lp8788_alarm_irq_enable(struct device *dev, unsigned int > enable) > > +{ > > + struct lp8788_rtc *rtc = dev_get_drvdata(dev); > > + struct lp8788 *lp = rtc->lp; > > + u8 mask, shift; > > + > > + mask = mask_alarm_en[rtc->alarm]; > > + shift = shift_alarm_en[rtc->alarm]; > > + > > + return lp8788_update_bits(lp, LP8788_INTEN_3, mask, enable << > shift); > > i have no idea about this function but just asking, > > this will be called from ioctl right? so what if user calls RTC_AIE_ON > and you return the IRQ status ? enable or disable like that? > > i guess a failure in setting for no irq and a success otherwise. Please go to my answer below - at the end of mail. > > > +} > > + > > +static const struct rtc_class_ops lp8788_rtc_ops = { > > + .read_time = lp8788_rtc_read_time, > > + .set_time = lp8788_rtc_set_time, > > + .read_alarm = lp8788_read_alarm, > > + .set_alarm = lp8788_set_alarm, > > + .alarm_irq_enable = lp8788_alarm_irq_enable, > > +}; > > + > > +static irqreturn_t lp8788_alarm_irq_handler(int irq, void *ptr) > > +{ > > + struct lp8788_rtc *rtc = ptr; > > + > > + rtc_update_irq(rtc->rdev, 1, ALARM_IRQ_FLAG); > > + return IRQ_HANDLED; > > +} > > + > > +static int lp8788_alarm_irq_register(struct platform_device *pdev, > > + struct lp8788_rtc *rtc) > > +{ > > + struct resource *r; > > + struct lp8788 *lp = rtc->lp; > > + struct irq_domain *irqdm = lp->irqdm; > > + int irq; > > + > > + rtc->irq = 0; > > + > > + if (!lp->pdata) { > > + dev_warn(lp->dev, "no platform data for rtc irq\n"); > > but we didn't registered irqs right? if we return 0 here means that we > have a vaild irq no? Oh, it's my mistake. Alarm IRQ number is configured in the mfd-lp8788 driver and map it by irq-domain. Therefore no platform data is required here. > > +static int lp8788_rtc_probe(struct platform_device *pdev) > > +{ > > + struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); > > + struct lp8788_rtc *rtc; > > + struct device *dev = &pdev->dev; > > + > > + rtc = devm_kzalloc(lp->dev, sizeof(struct lp8788_rtc), > GFP_KERNEL); > > lp->dev may be parent device, is &pdev->dev, or dev may be correct? Great catch! You're correct. Thanks! > > > + if (!rtc) > > + return -ENOMEM; > > + > > + rtc->lp = lp; > > + rtc->alarm = lp->pdata ? lp->pdata->alarm_sel : > DEFAULT_ALARM_SEL; > > + platform_set_drvdata(pdev, rtc); > > + > > + device_init_wakeup(dev, 1); > > + > > + rtc->rdev = rtc_device_register("lp8788_rtc", dev, > > + &lp8788_rtc_ops, THIS_MODULE); > > + if (IS_ERR(rtc->rdev)) { > > + dev_err(dev, "can not register rtc device\n"); > > return PTR_ERR(rtc->rdev); may be better? Agree, thanks! > > > + return -ENODEV; > > + } > > + > > + if (lp8788_alarm_irq_register(pdev, rtc)) > > + dev_warn(lp->dev, "no rtc irq handler\n"); > > + > > even if there is no alarm irq, we can still use the rtc to get the > time, but there wont be RTC_AIE_ON ioctl enabled ? If ALARM IRQ can't be registered, there are two handling conditions. (a) Should it return as error in _probe() and no RTC is used anymore? : Very simple handling but we can't set/get the RTC time even no problem. (b) Just returns OK in _probe() and error handling when RTC_AIE_ON/OFF IOCTL is called? : It guarantees RTC time functions but alarm IRQ doesn't work. Please see the code below. static int lp8788_alarm_irq_enable(struct device *dev, unsigned int enable) { struct lp8788_rtc *rtc = dev_get_drvdata(dev); struct lp8788 *lp = rtc->lp; u8 mask, shift; + if (!rtc->irq) + return -EIO; mask = mask_alarm_en[rtc->alarm]; shift = shift_alarm_en[rtc->alarm]; return lp8788_update_bits(lp, LP8788_INTEN_3, mask, enable << shift); } 'rtc->irq' is updated only when lp8788_alarm_irq_register() returns true. If no alarm IRQ is specified, RTC_AIE_ON/OFF returns as EIO. Then, RTC set/get time works well and alarm IRQ doesn't work. I believe case (b) is better solution, but I'd like to have your opinion. Thanks! Best Regards, Milo
Hello, On Fri, Dec 21, 2012 at 9:39 PM, Kim, Milo <Milo.Kim@ti.com> wrote: > Hi Devendra, > >> > +static int lp8788_alarm_irq_enable(struct device *dev, unsigned int >> enable) >> > +{ >> > + struct lp8788_rtc *rtc = dev_get_drvdata(dev); >> > + struct lp8788 *lp = rtc->lp; >> > + u8 mask, shift; >> > + >> > + mask = mask_alarm_en[rtc->alarm]; >> > + shift = shift_alarm_en[rtc->alarm]; >> > + >> > + return lp8788_update_bits(lp, LP8788_INTEN_3, mask, enable << >> shift); >> >> i have no idea about this function but just asking, >> >> this will be called from ioctl right? so what if user calls RTC_AIE_ON >> and you return the IRQ status ? enable or disable like that? >> >> i guess a failure in setting for no irq and a success otherwise. > > Please go to my answer below - at the end of mail. > >> >> > +} >> > + >> > +static const struct rtc_class_ops lp8788_rtc_ops = { >> > + .read_time = lp8788_rtc_read_time, >> > + .set_time = lp8788_rtc_set_time, >> > + .read_alarm = lp8788_read_alarm, >> > + .set_alarm = lp8788_set_alarm, >> > + .alarm_irq_enable = lp8788_alarm_irq_enable, >> > +}; >> > + >> > +static irqreturn_t lp8788_alarm_irq_handler(int irq, void *ptr) >> > +{ >> > + struct lp8788_rtc *rtc = ptr; >> > + >> > + rtc_update_irq(rtc->rdev, 1, ALARM_IRQ_FLAG); >> > + return IRQ_HANDLED; >> > +} >> > + >> > +static int lp8788_alarm_irq_register(struct platform_device *pdev, >> > + struct lp8788_rtc *rtc) >> > +{ >> > + struct resource *r; >> > + struct lp8788 *lp = rtc->lp; >> > + struct irq_domain *irqdm = lp->irqdm; >> > + int irq; >> > + >> > + rtc->irq = 0; >> > + >> > + if (!lp->pdata) { >> > + dev_warn(lp->dev, "no platform data for rtc irq\n"); >> >> but we didn't registered irqs right? if we return 0 here means that we >> have a vaild irq no? > > Oh, it's my mistake. > Alarm IRQ number is configured in the mfd-lp8788 driver and map it by irq-domain. > Therefore no platform data is required here. > >> > +static int lp8788_rtc_probe(struct platform_device *pdev) >> > +{ >> > + struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); >> > + struct lp8788_rtc *rtc; >> > + struct device *dev = &pdev->dev; >> > + >> > + rtc = devm_kzalloc(lp->dev, sizeof(struct lp8788_rtc), >> GFP_KERNEL); >> >> lp->dev may be parent device, is &pdev->dev, or dev may be correct? > > Great catch! You're correct. Thanks! > >> >> > + if (!rtc) >> > + return -ENOMEM; >> > + >> > + rtc->lp = lp; >> > + rtc->alarm = lp->pdata ? lp->pdata->alarm_sel : >> DEFAULT_ALARM_SEL; >> > + platform_set_drvdata(pdev, rtc); >> > + >> > + device_init_wakeup(dev, 1); >> > + >> > + rtc->rdev = rtc_device_register("lp8788_rtc", dev, >> > + &lp8788_rtc_ops, THIS_MODULE); >> > + if (IS_ERR(rtc->rdev)) { >> > + dev_err(dev, "can not register rtc device\n"); >> >> return PTR_ERR(rtc->rdev); may be better? > > Agree, thanks! > >> >> > + return -ENODEV; >> > + } >> > + >> > + if (lp8788_alarm_irq_register(pdev, rtc)) >> > + dev_warn(lp->dev, "no rtc irq handler\n"); >> > + >> >> even if there is no alarm irq, we can still use the rtc to get the >> time, but there wont be RTC_AIE_ON ioctl enabled ? > > If ALARM IRQ can't be registered, there are two handling conditions. > > (a) Should it return as error in _probe() and no RTC is used anymore? > : Very simple handling but we can't set/get the RTC time even no problem. > > (b) Just returns OK in _probe() and error handling when RTC_AIE_ON/OFF IOCTL is > called? > : It guarantees RTC time functions but alarm IRQ doesn't work. > Please see the code below. > > static int lp8788_alarm_irq_enable(struct device *dev, unsigned int enable) > { > struct lp8788_rtc *rtc = dev_get_drvdata(dev); > struct lp8788 *lp = rtc->lp; > u8 mask, shift; > > + if (!rtc->irq) > + return -EIO; > > mask = mask_alarm_en[rtc->alarm]; > shift = shift_alarm_en[rtc->alarm]; > > return lp8788_update_bits(lp, LP8788_INTEN_3, mask, enable << shift); > } > > 'rtc->irq' is updated only when lp8788_alarm_irq_register() returns true. > If no alarm IRQ is specified, RTC_AIE_ON/OFF returns as EIO. > Then, RTC set/get time works well and alarm IRQ doesn't work. > > I believe case (b) is better solution, but I'd like to have your opinion. > Thanks! > yes, i agree. A very good explanation indeed thanks. > Best Regards, > Milo > > thanks,
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index d0cea02..a3996c4 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -194,6 +194,12 @@ config RTC_DRV_DS3232 This driver can also be built as a module. If so, the module will be called rtc-ds3232. +config RTC_DRV_LP8788 + tristate "TI LP8788 RTC driver" + depends on MFD_LP8788 + help + Say Y to enable support for the LP8788 RTC/ALARM driver. + config RTC_DRV_MAX6900 tristate "Maxim MAX6900" help diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index c3f62c8..66050d8 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -56,6 +56,7 @@ obj-$(CONFIG_RTC_DRV_IMXDI) += rtc-imxdi.o obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o obj-$(CONFIG_RTC_DRV_ISL12022) += rtc-isl12022.o obj-$(CONFIG_RTC_DRV_JZ4740) += rtc-jz4740.o +obj-$(CONFIG_RTC_DRV_LP8788) += rtc-lp8788.o obj-$(CONFIG_RTC_DRV_LPC32XX) += rtc-lpc32xx.o obj-$(CONFIG_RTC_DRV_LOONGSON1) += rtc-ls1x.o 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 0000000..2363e52 --- /dev/null +++ b/drivers/rtc/rtc-lp8788.c @@ -0,0 +1,345 @@ +/* + * TI LP8788 MFD - rtc driver + * + * Copyright 2012 Texas Instruments + * + * Author: Milo(Woogyom) Kim <milo.kim@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/err.h> +#include <linux/irqdomain.h> +#include <linux/mfd/lp8788.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/slab.h> + +/* register address */ +#define LP8788_INTEN_3 0x05 +#define LP8788_RTC_UNLOCK 0x64 +#define LP8788_RTC_SEC 0x70 +#define LP8788_ALM1_SEC 0x77 +#define LP8788_ALM1_EN 0x7D +#define LP8788_ALM2_SEC 0x7E +#define LP8788_ALM2_EN 0x84 + +/* mask/shift bits */ +#define LP8788_INT_RTC_ALM1_M BIT(1) /* Addr 05h */ +#define LP8788_INT_RTC_ALM1_S 1 +#define LP8788_INT_RTC_ALM2_M BIT(2) /* Addr 05h */ +#define LP8788_INT_RTC_ALM2_S 2 +#define LP8788_ALM_EN_M BIT(7) /* Addr 7Dh or 84h */ +#define LP8788_ALM_EN_S 7 + +#define DEFAULT_ALARM_SEL LP8788_ALARM_1 +#define LP8788_MONTH_OFFSET 1 +#define LP8788_BASE_YEAR 2000 +#define MAX_WDAY_BITS 7 +#define LP8788_WDAY_SET 1 +#define RTC_UNLOCK 0x1 +#define RTC_LATCH 0x2 +#define ALARM_IRQ_FLAG (RTC_IRQF | RTC_AF) + +enum lp8788_time { + LPTIME_SEC, + LPTIME_MIN, + LPTIME_HOUR, + LPTIME_MDAY, + LPTIME_MON, + LPTIME_YEAR, + LPTIME_WDAY, + LPTIME_MAX, +}; + +struct lp8788_rtc { + struct lp8788 *lp; + struct rtc_device *rdev; + enum lp8788_alarm_sel alarm; + int irq; +}; + +static const u8 addr_alarm_sec[LP8788_ALARM_MAX] = { + LP8788_ALM1_SEC, + LP8788_ALM2_SEC, +}; + +static const u8 addr_alarm_en[LP8788_ALARM_MAX] = { + LP8788_ALM1_EN, + LP8788_ALM2_EN, +}; + +static const u8 mask_alarm_en[LP8788_ALARM_MAX] = { + LP8788_INT_RTC_ALM1_M, + LP8788_INT_RTC_ALM2_M, +}; + +static const u8 shift_alarm_en[LP8788_ALARM_MAX] = { + LP8788_INT_RTC_ALM1_S, + LP8788_INT_RTC_ALM2_S, +}; + +static int _to_tm_wday(u8 lp8788_wday) +{ + int i; + + if (lp8788_wday == 0) + return 0; + + /* lookup defined weekday from read register value */ + for (i = 0; i < MAX_WDAY_BITS; i++) { + if ((lp8788_wday >> i) == LP8788_WDAY_SET) + break; + } + + return i + 1; +} + +static inline int _to_lp8788_wday(int tm_wday) +{ + return LP8788_WDAY_SET << (tm_wday - 1); +} + +static void lp8788_rtc_unlock(struct lp8788 *lp) +{ + lp8788_write_byte(lp, LP8788_RTC_UNLOCK, RTC_UNLOCK); + lp8788_write_byte(lp, LP8788_RTC_UNLOCK, RTC_LATCH); +} + +static int lp8788_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct lp8788_rtc *rtc = dev_get_drvdata(dev); + struct lp8788 *lp = rtc->lp; + u8 data[LPTIME_MAX]; + int ret; + + lp8788_rtc_unlock(lp); + + ret = lp8788_read_multi_bytes(lp, LP8788_RTC_SEC, data, LPTIME_MAX); + if (ret) + return ret; + + tm->tm_sec = data[LPTIME_SEC]; + tm->tm_min = data[LPTIME_MIN]; + tm->tm_hour = data[LPTIME_HOUR]; + tm->tm_mday = data[LPTIME_MDAY]; + tm->tm_mon = data[LPTIME_MON] - LP8788_MONTH_OFFSET; + tm->tm_year = data[LPTIME_YEAR] + LP8788_BASE_YEAR - 1900; + tm->tm_wday = _to_tm_wday(data[LPTIME_WDAY]); + + return 0; +} + +static int lp8788_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct lp8788_rtc *rtc = dev_get_drvdata(dev); + struct lp8788 *lp = rtc->lp; + u8 data[LPTIME_MAX - 1]; + int ret, i, year; + + year = tm->tm_year + 1900 - LP8788_BASE_YEAR; + if (year < 0) { + dev_err(lp->dev, "invalid year: %d\n", year); + return -EINVAL; + } + + /* because rtc weekday is a readonly register, do not update */ + data[LPTIME_SEC] = tm->tm_sec; + data[LPTIME_MIN] = tm->tm_min; + data[LPTIME_HOUR] = tm->tm_hour; + data[LPTIME_MDAY] = tm->tm_mday; + data[LPTIME_MON] = tm->tm_mon + LP8788_MONTH_OFFSET; + data[LPTIME_YEAR] = year; + + for (i = 0; i < ARRAY_SIZE(data); i++) { + ret = lp8788_write_byte(lp, LP8788_RTC_SEC + i, data[i]); + if (ret) + return ret; + } + + return 0; +} + +static int lp8788_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct lp8788_rtc *rtc = dev_get_drvdata(dev); + struct lp8788 *lp = rtc->lp; + struct rtc_time *tm = &alarm->time; + u8 addr, data[LPTIME_MAX]; + int ret; + + addr = addr_alarm_sec[rtc->alarm]; + ret = lp8788_read_multi_bytes(lp, addr, data, LPTIME_MAX); + if (ret) + return ret; + + tm->tm_sec = data[LPTIME_SEC]; + tm->tm_min = data[LPTIME_MIN]; + tm->tm_hour = data[LPTIME_HOUR]; + tm->tm_mday = data[LPTIME_MDAY]; + tm->tm_mon = data[LPTIME_MON] - LP8788_MONTH_OFFSET; + tm->tm_year = data[LPTIME_YEAR] + LP8788_BASE_YEAR - 1900; + tm->tm_wday = _to_tm_wday(data[LPTIME_WDAY]); + alarm->enabled = data[LPTIME_WDAY] & LP8788_ALM_EN_M; + + return 0; +} + +static int lp8788_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct lp8788_rtc *rtc = dev_get_drvdata(dev); + struct lp8788 *lp = rtc->lp; + struct rtc_time *tm = &alarm->time; + u8 addr, data[LPTIME_MAX]; + int ret, i, year; + + year = tm->tm_year + 1900 - LP8788_BASE_YEAR; + if (year < 0) { + dev_err(lp->dev, "invalid year: %d\n", year); + return -EINVAL; + } + + data[LPTIME_SEC] = tm->tm_sec; + data[LPTIME_MIN] = tm->tm_min; + data[LPTIME_HOUR] = tm->tm_hour; + data[LPTIME_MDAY] = tm->tm_mday; + data[LPTIME_MON] = tm->tm_mon + LP8788_MONTH_OFFSET; + data[LPTIME_YEAR] = year; + data[LPTIME_WDAY] = _to_lp8788_wday(tm->tm_wday); + + for (i = 0; i < ARRAY_SIZE(data); i++) { + addr = addr_alarm_sec[rtc->alarm] + i; + ret = lp8788_write_byte(lp, addr, data[i]); + if (ret) + return ret; + } + + alarm->enabled = 1; + addr = addr_alarm_en[rtc->alarm]; + + return lp8788_update_bits(lp, addr, LP8788_ALM_EN_M, + alarm->enabled << LP8788_ALM_EN_S); +} + +static int lp8788_alarm_irq_enable(struct device *dev, unsigned int enable) +{ + struct lp8788_rtc *rtc = dev_get_drvdata(dev); + struct lp8788 *lp = rtc->lp; + u8 mask, shift; + + mask = mask_alarm_en[rtc->alarm]; + shift = shift_alarm_en[rtc->alarm]; + + return lp8788_update_bits(lp, LP8788_INTEN_3, mask, enable << shift); +} + +static const struct rtc_class_ops lp8788_rtc_ops = { + .read_time = lp8788_rtc_read_time, + .set_time = lp8788_rtc_set_time, + .read_alarm = lp8788_read_alarm, + .set_alarm = lp8788_set_alarm, + .alarm_irq_enable = lp8788_alarm_irq_enable, +}; + +static irqreturn_t lp8788_alarm_irq_handler(int irq, void *ptr) +{ + struct lp8788_rtc *rtc = ptr; + + rtc_update_irq(rtc->rdev, 1, ALARM_IRQ_FLAG); + return IRQ_HANDLED; +} + +static int lp8788_alarm_irq_register(struct platform_device *pdev, + struct lp8788_rtc *rtc) +{ + struct resource *r; + struct lp8788 *lp = rtc->lp; + struct irq_domain *irqdm = lp->irqdm; + int irq; + + rtc->irq = 0; + + if (!lp->pdata) { + dev_warn(lp->dev, "no platform data for rtc irq\n"); + return 0; + } + + r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, LP8788_ALM_IRQ); + if (!r) + return 0; + + if (rtc->alarm == LP8788_ALARM_1) + irq = r->start; + else + irq = r->end; + + rtc->irq = irq_create_mapping(irqdm, irq); + + return request_threaded_irq(rtc->irq, NULL, lp8788_alarm_irq_handler, + 0, LP8788_ALM_IRQ, rtc); +} + +static void lp8788_alarm_irq_unregister(struct lp8788_rtc *rtc) +{ + if (rtc->irq) + free_irq(rtc->irq, rtc); +} + +static int lp8788_rtc_probe(struct platform_device *pdev) +{ + struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); + struct lp8788_rtc *rtc; + struct device *dev = &pdev->dev; + + rtc = devm_kzalloc(lp->dev, sizeof(struct lp8788_rtc), GFP_KERNEL); + if (!rtc) + return -ENOMEM; + + rtc->lp = lp; + rtc->alarm = lp->pdata ? lp->pdata->alarm_sel : DEFAULT_ALARM_SEL; + platform_set_drvdata(pdev, rtc); + + device_init_wakeup(dev, 1); + + rtc->rdev = rtc_device_register("lp8788_rtc", dev, + &lp8788_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc->rdev)) { + dev_err(dev, "can not register rtc device\n"); + return -ENODEV; + } + + if (lp8788_alarm_irq_register(pdev, rtc)) + dev_warn(lp->dev, "no rtc irq handler\n"); + + return 0; +} + +static int lp8788_rtc_remove(struct platform_device *pdev) +{ + struct lp8788_rtc *rtc = platform_get_drvdata(pdev); + + lp8788_alarm_irq_unregister(rtc); + rtc_device_unregister(rtc->rdev); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver lp8788_rtc_driver = { + .probe = lp8788_rtc_probe, + .remove = lp8788_rtc_remove, + .driver = { + .name = LP8788_DEV_RTC, + .owner = THIS_MODULE, + }, +}; +module_platform_driver(lp8788_rtc_driver); + +MODULE_DESCRIPTION("Texas Instruments LP8788 RTC Driver"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:lp8788-rtc");
TI LP8788 PMU supports regulators, battery charger, RTC, ADC, backlight driver and current sinks. This patch enables LP8788 RTC module. Patch v3. remove __devinit and __devexit. add weekday definition and comments. Patch v2. use IRQ domain for handling alarm IRQ. replace module_init() and module_exit() with module_platform_driver(). clean up code for _probe() and _remove(). Patch v1. initial patch Signed-off-by: Milo(Woogyom) Kim <milo.kim@ti.com> --- drivers/rtc/Kconfig | 6 + drivers/rtc/Makefile | 1 + drivers/rtc/rtc-lp8788.c | 345 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 352 insertions(+) create mode 100644 drivers/rtc/rtc-lp8788.c