From patchwork Fri Dec 21 07:55:21 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Kim, Milo" X-Patchwork-Id: 207767 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from mail-vb0-f58.google.com (mail-vb0-f58.google.com [209.85.212.58]) (using TLSv1 with cipher ECDHE-RSA-RC4-SHA (128/128 bits)) (Client CN "smtp.gmail.com", Issuer "Google Internet Authority" (not verified)) by ozlabs.org (Postfix) with ESMTPS id 272182C0094 for ; Fri, 21 Dec 2012 18:55:32 +1100 (EST) Received: by mail-vb0-f58.google.com with SMTP id fa15sf2140470vbb.3 for ; Thu, 20 Dec 2012 23:55:30 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=20120806; h=x-received:x-beenthere:x-received:x-received:received-spf:from:to :cc:subject:thread-topic:thread-index:date:message-id :accept-language:x-ms-has-attach:x-ms-tnef-correlator :x-originating-ip:mime-version:x-original-sender :x-original-authentication-results:reply-to:precedence:mailing-list :list-id:x-google-group-id:list-post:list-help:list-archive:sender :list-subscribe:list-unsubscribe:content-language:content-type; bh=d2xkGeRl50kErIg7NNgUpeK5UKiXRmu31PCGuLW70Wg=; b=JI/qoV4k4uf9DBttAyl9d5Dd+BVAFURfHN2pb3DgvFqwnY87eLV7dNTIKoF/OBwwYG h9SoW7T4RxGBhhv6nP/vmx6g5f2kSxWUCLTyYtcjsuumQ/1EbFXZIulr8iAVIDMATZMr Yt0Iuqr2ZEsaXurbKLn6IqJWp9NEgvVctogU3ohTGkV2z/gKpW53qFWUqAJ3gSWVcWf3 cwA2ngSqYJ1f/GQ+H2G7F945zfSKzJZPJO5OHqhqlVTzm+OVrHGRDl8CGZouppWPXOz+ Ke2XFHFSYylYKtwzFYExbdhtWxdZx8BgW+R2ZCvQM0coLWZABu00zkQk2pi+LXXcL6TJ h86Q== X-Received: by 10.50.216.170 with SMTP id or10mr2434520igc.15.1356076529265; Thu, 20 Dec 2012 23:55:29 -0800 (PST) X-BeenThere: rtc-linux@googlegroups.com Received: by 10.50.6.233 with SMTP id e9ls6349419iga.3.canary; Thu, 20 Dec 2012 23:55:28 -0800 (PST) X-Received: by 10.42.212.209 with SMTP id gt17mr9314993icb.31.1356076528820; Thu, 20 Dec 2012 23:55:28 -0800 (PST) X-Received: by 10.42.212.209 with SMTP id gt17mr9314991icb.31.1356076528809; Thu, 20 Dec 2012 23:55:28 -0800 (PST) Received: from devils.ext.ti.com (devils.ext.ti.com. [198.47.26.153]) by gmr-mx.google.com with ESMTPS id vb13si911704igb.2.2012.12.20.23.55.28 (version=TLSv1/SSLv3 cipher=OTHER); Thu, 20 Dec 2012 23:55:28 -0800 (PST) Received-SPF: pass (google.com: domain of Milo.Kim@ti.com designates 198.47.26.153 as permitted sender) client-ip=198.47.26.153; Received: from dbdp20.itg.ti.com ([172.24.170.38]) by devils.ext.ti.com (8.13.7/8.13.7) with ESMTP id qBL7tQEt023507; Fri, 21 Dec 2012 01:55:27 -0600 Received: from DQHE74.ent.ti.com (localhost [127.0.0.1]) by dbdp20.itg.ti.com (8.13.8/8.13.8) with ESMTP id qBL7tMOV005260; Fri, 21 Dec 2012 13:25:23 +0530 (IST) Received: from DQHE02.ent.ti.com ([fe80::f41c:f77f:5d6a:8699]) by DQHE74.ent.ti.com ([fe80::2088:fac8:8d64:a01c%35]) with mapi id 14.01.0323.003; Fri, 21 Dec 2012 15:55:22 +0800 From: "Kim, Milo" To: "a.zummo@towertech.it" CC: Andrew Morton , "rtc-linux@googlegroups.com" , "linux-kernel@vger.kernel.org" , Samuel Ortiz Subject: [rtc-linux] [PATCH v3 1/2] rtc: add new lp8788 rtc driver Thread-Topic: [PATCH v3 1/2] rtc: add new lp8788 rtc driver Thread-Index: Ac3fUIXDqZYgoeWsSjiBLm1bKZpEpg== Date: Fri, 21 Dec 2012 07:55:21 +0000 Message-ID: Accept-Language: ko-KR, en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-originating-ip: [172.16.34.32] MIME-Version: 1.0 X-Original-Sender: milo.kim@ti.com X-Original-Authentication-Results: gmr-mx.google.com; spf=pass (google.com: domain of Milo.Kim@ti.com designates 198.47.26.153 as permitted sender) smtp.mail=Milo.Kim@ti.com Reply-To: rtc-linux@googlegroups.com Precedence: list Mailing-list: list rtc-linux@googlegroups.com; contact rtc-linux+owners@googlegroups.com List-ID: X-Google-Group-Id: 712029733259 List-Post: , List-Help: , List-Archive: Sender: rtc-linux@googlegroups.com List-Subscribe: , List-Unsubscribe: , Content-Language: en-US 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 --- 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 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 + * + * 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 +#include +#include +#include +#include +#include +#include + +/* 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");