brintos

brintos / linux-shallow public Read only

0
0
Text · 7.6 KiB · c0b8fbc Raw
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