diff options
Diffstat (limited to 'drivers/rtc')
-rw-r--r-- | drivers/rtc/Kconfig | 68 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 8 | ||||
-rw-r--r-- | drivers/rtc/rtc-88pm860x.c | 427 | ||||
-rw-r--r-- | drivers/rtc/rtc-em3027.c | 161 | ||||
-rw-r--r-- | drivers/rtc/rtc-m41t93.c | 225 | ||||
-rw-r--r-- | drivers/rtc/rtc-mrst.c | 4 | ||||
-rw-r--r-- | drivers/rtc/rtc-mxc.c | 6 | ||||
-rw-r--r-- | drivers/rtc/rtc-pcf50633.c | 23 | ||||
-rw-r--r-- | drivers/rtc/rtc-puv3.c | 359 | ||||
-rw-r--r-- | drivers/rtc/rtc-rv3029c2.c | 454 | ||||
-rw-r--r-- | drivers/rtc/rtc-spear.c | 534 | ||||
-rw-r--r-- | drivers/rtc/rtc-tile.c | 162 | ||||
-rw-r--r-- | drivers/rtc/rtc-vt8500.c | 366 |
13 files changed, 2769 insertions, 28 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 42891726ea72..ce2aabf5c550 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -125,6 +125,16 @@ comment "I2C RTC drivers" if I2C +config RTC_DRV_88PM860X + tristate "Marvell 88PM860x" + depends on RTC_CLASS && I2C && MFD_88PM860X + help + If you say yes here you get support for RTC function in Marvell + 88PM860x chips. + + This driver can also be built as a module. If so, the module + will be called rtc-88pm860x. + config RTC_DRV_DS1307 tristate "Dallas/Maxim DS1307/37/38/39/40, ST M41T00, EPSON RX-8025" help @@ -351,12 +361,39 @@ config RTC_DRV_RX8025 This driver can also be built as a module. If so, the module will be called rtc-rx8025. +config RTC_DRV_EM3027 + tristate "EM Microelectronic EM3027" + help + If you say yes here you get support for the EM + Microelectronic EM3027 RTC chips. + + This driver can also be built as a module. If so, the module + will be called rtc-em3027. + +config RTC_DRV_RV3029C2 + tristate "Micro Crystal RTC" + help + If you say yes here you get support for the Micro Crystal + RV3029-C2 RTC chips. + + This driver can also be built as a module. If so, the module + will be called rtc-rv3029c2. + endif # I2C comment "SPI RTC drivers" if SPI_MASTER +config RTC_DRV_M41T93 + tristate "ST M41T93" + help + If you say yes here you will get support for the + ST M41T93 SPI RTC chip. + + This driver can also be built as a module. If so, the module + will be called rtc-m41t93. + config RTC_DRV_M41T94 tristate "ST M41T94" help @@ -645,6 +682,14 @@ config RTC_DRV_WM8350 This driver can also be built as a module. If so, the module will be called "rtc-wm8350". +config RTC_DRV_SPEAR + tristate "SPEAR ST RTC" + depends on PLAT_SPEAR + default y + help + If you say Y here you will get support for the RTC found on + spear + config RTC_DRV_PCF50633 depends on MFD_PCF50633 tristate "NXP PCF50633 RTC" @@ -874,6 +919,13 @@ config RTC_DRV_PXA This RTC driver uses PXA RTC registers available since pxa27x series (RDxR, RYxR) instead of legacy RCNR, RTAR. +config RTC_DRV_VT8500 + tristate "VIA/WonderMedia 85xx SoC RTC" + depends on ARCH_VT8500 + help + If you say Y here you will get access to the real time clock + built into your VIA VT8500 SoC or its relatives. + config RTC_DRV_SUN4V bool "SUN4V Hypervisor RTC" @@ -992,4 +1044,20 @@ config RTC_DRV_TEGRA This drive can also be built as a module. If so, the module will be called rtc-tegra. +config RTC_DRV_TILE + tristate "Tilera hypervisor RTC support" + depends on TILE + help + Enable support for the Linux driver side of the Tilera + hypervisor's real-time clock interface. + +config RTC_DRV_PUV3 + tristate "PKUnity v3 RTC support" + depends on ARCH_PUV3 + help + This enables support for the RTC in the PKUnity-v3 SoCs. + + This drive can also be built as a module. If so, the module + will be called rtc-puv3. + endif # RTC_CLASS diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index ca91c3c42e98..0ffefe877bfa 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -15,6 +15,7 @@ rtc-core-$(CONFIG_RTC_INTF_SYSFS) += rtc-sysfs.o # Keep the list ordered. +obj-$(CONFIG_RTC_DRV_88PM860X) += rtc-88pm860x.o obj-$(CONFIG_RTC_DRV_AB3100) += rtc-ab3100.o obj-$(CONFIG_RTC_DRV_AB8500) += rtc-ab8500.o obj-$(CONFIG_RTC_DRV_AT32AP700X)+= rtc-at32ap700x.o @@ -43,6 +44,7 @@ obj-$(CONFIG_RTC_DRV_DS1742) += rtc-ds1742.o obj-$(CONFIG_RTC_DRV_DS3232) += rtc-ds3232.o obj-$(CONFIG_RTC_DRV_DS3234) += rtc-ds3234.o obj-$(CONFIG_RTC_DRV_EFI) += rtc-efi.o +obj-$(CONFIG_RTC_DRV_EM3027) += rtc-em3027.o obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o obj-$(CONFIG_RTC_DRV_GENERIC) += rtc-generic.o @@ -52,6 +54,7 @@ obj-$(CONFIG_RTC_DRV_ISL12022) += rtc-isl12022.o obj-$(CONFIG_RTC_DRV_JZ4740) += rtc-jz4740.o obj-$(CONFIG_RTC_DRV_LPC32XX) += rtc-lpc32xx.o obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o +obj-$(CONFIG_RTC_DRV_M41T93) += rtc-m41t93.o obj-$(CONFIG_RTC_DRV_M41T94) += rtc-m41t94.o obj-$(CONFIG_RTC_DRV_M48T35) += rtc-m48t35.o obj-$(CONFIG_RTC_DRV_M48T59) += rtc-m48t59.o @@ -75,28 +78,33 @@ obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o obj-$(CONFIG_RTC_DRV_PL030) += rtc-pl030.o obj-$(CONFIG_RTC_DRV_PL031) += rtc-pl031.o obj-$(CONFIG_RTC_DRV_PS3) += rtc-ps3.o +obj-$(CONFIG_RTC_DRV_PUV3) += rtc-puv3.o obj-$(CONFIG_RTC_DRV_PXA) += rtc-pxa.o obj-$(CONFIG_RTC_DRV_R9701) += rtc-r9701.o obj-$(CONFIG_RTC_DRV_RP5C01) += rtc-rp5c01.o obj-$(CONFIG_RTC_DRV_RS5C313) += rtc-rs5c313.o obj-$(CONFIG_RTC_DRV_RS5C348) += rtc-rs5c348.o obj-$(CONFIG_RTC_DRV_RS5C372) += rtc-rs5c372.o +obj-$(CONFIG_RTC_DRV_RV3029C2) += rtc-rv3029c2.o obj-$(CONFIG_RTC_DRV_RX8025) += rtc-rx8025.o obj-$(CONFIG_RTC_DRV_RX8581) += rtc-rx8581.o obj-$(CONFIG_RTC_DRV_S35390A) += rtc-s35390a.o obj-$(CONFIG_RTC_DRV_S3C) += rtc-s3c.o obj-$(CONFIG_RTC_DRV_SA1100) += rtc-sa1100.o obj-$(CONFIG_RTC_DRV_SH) += rtc-sh.o +obj-$(CONFIG_RTC_DRV_SPEAR) += rtc-spear.o obj-$(CONFIG_RTC_DRV_STARFIRE) += rtc-starfire.o obj-$(CONFIG_RTC_DRV_STK17TA8) += rtc-stk17ta8.o obj-$(CONFIG_RTC_DRV_STMP) += rtc-stmp3xxx.o obj-$(CONFIG_RTC_DRV_SUN4V) += rtc-sun4v.o obj-$(CONFIG_RTC_DRV_TEGRA) += rtc-tegra.o obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o +obj-$(CONFIG_RTC_DRV_TILE) += rtc-tile.o obj-$(CONFIG_RTC_DRV_TWL4030) += rtc-twl.o obj-$(CONFIG_RTC_DRV_TX4939) += rtc-tx4939.o obj-$(CONFIG_RTC_DRV_V3020) += rtc-v3020.o obj-$(CONFIG_RTC_DRV_VR41XX) += rtc-vr41xx.o +obj-$(CONFIG_RTC_DRV_VT8500) += rtc-vt8500.o obj-$(CONFIG_RTC_DRV_WM831X) += rtc-wm831x.o obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o diff --git a/drivers/rtc/rtc-88pm860x.c b/drivers/rtc/rtc-88pm860x.c new file mode 100644 index 000000000000..64b847b7f970 --- /dev/null +++ b/drivers/rtc/rtc-88pm860x.c @@ -0,0 +1,427 @@ +/* + * Real Time Clock driver for Marvell 88PM860x PMIC + * + * Copyright (c) 2010 Marvell International Ltd. + * Author: Haojian Zhuang <haojian.zhuang@marvell.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/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/rtc.h> +#include <linux/delay.h> +#include <linux/mfd/core.h> +#include <linux/mfd/88pm860x.h> + +#define VRTC_CALIBRATION + +struct pm860x_rtc_info { + struct pm860x_chip *chip; + struct i2c_client *i2c; + struct rtc_device *rtc_dev; + struct device *dev; + struct delayed_work calib_work; + + int irq; + int vrtc; + int (*sync)(unsigned int ticks); +}; + +#define REG_VRTC_MEAS1 0x7D + +#define REG0_ADDR 0xB0 +#define REG1_ADDR 0xB2 +#define REG2_ADDR 0xB4 +#define REG3_ADDR 0xB6 + +#define REG0_DATA 0xB1 +#define REG1_DATA 0xB3 +#define REG2_DATA 0xB5 +#define REG3_DATA 0xB7 + +/* bit definitions of Measurement Enable Register 2 (0x51) */ +#define MEAS2_VRTC (1 << 0) + +/* bit definitions of RTC Register 1 (0xA0) */ +#define ALARM_EN (1 << 3) +#define ALARM_WAKEUP (1 << 4) +#define ALARM (1 << 5) +#define RTC1_USE_XO (1 << 7) + +#define VRTC_CALIB_INTERVAL (HZ * 60 * 10) /* 10 minutes */ + +static irqreturn_t rtc_update_handler(int irq, void *data) +{ + struct pm860x_rtc_info *info = (struct pm860x_rtc_info *)data; + int mask; + + mask = ALARM | ALARM_WAKEUP; + pm860x_set_bits(info->i2c, PM8607_RTC1, mask | ALARM_EN, mask); + rtc_update_irq(info->rtc_dev, 1, RTC_AF); + return IRQ_HANDLED; +} + +static int pm860x_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct pm860x_rtc_info *info = dev_get_drvdata(dev); + + if (enabled) + pm860x_set_bits(info->i2c, PM8607_RTC1, ALARM, ALARM); + else + pm860x_set_bits(info->i2c, PM8607_RTC1, ALARM, 0); + return 0; +} + +/* + * Calculate the next alarm time given the requested alarm time mask + * and the current time. + */ +static void rtc_next_alarm_time(struct rtc_time *next, struct rtc_time *now, + struct rtc_time *alrm) +{ + unsigned long next_time; + unsigned long now_time; + + next->tm_year = now->tm_year; + next->tm_mon = now->tm_mon; + next->tm_mday = now->tm_mday; + next->tm_hour = alrm->tm_hour; + next->tm_min = alrm->tm_min; + next->tm_sec = alrm->tm_sec; + + rtc_tm_to_time(now, &now_time); + rtc_tm_to_time(next, &next_time); + + if (next_time < now_time) { + /* Advance one day */ + next_time += 60 * 60 * 24; + rtc_time_to_tm(next_time, next); + } +} + +static int pm860x_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct pm860x_rtc_info *info = dev_get_drvdata(dev); + unsigned char buf[8]; + unsigned long ticks, base, data; + + pm860x_page_bulk_read(info->i2c, REG0_ADDR, 8, buf); + dev_dbg(info->dev, "%x-%x-%x-%x-%x-%x-%x-%x\n", buf[0], buf[1], + buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]); + base = (buf[1] << 24) | (buf[3] << 16) | (buf[5] << 8) | buf[7]; + + /* load 32-bit read-only counter */ + pm860x_bulk_read(info->i2c, PM8607_RTC_COUNTER1, 4, buf); + data = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; + ticks = base + data; + dev_dbg(info->dev, "get base:0x%lx, RO count:0x%lx, ticks:0x%lx\n", + base, data, ticks); + + rtc_time_to_tm(ticks, tm); + + return 0; +} + +static int pm860x_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct pm860x_rtc_info *info = dev_get_drvdata(dev); + unsigned char buf[4]; + unsigned long ticks, base, data; + + if ((tm->tm_year < 70) || (tm->tm_year > 138)) { + dev_dbg(info->dev, "Set time %d out of range. " + "Please set time between 1970 to 2038.\n", + 1900 + tm->tm_year); + return -EINVAL; + } + rtc_tm_to_time(tm, &ticks); + + /* load 32-bit read-only counter */ + pm860x_bulk_read(info->i2c, PM8607_RTC_COUNTER1, 4, buf); + data = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; + base = ticks - data; + dev_dbg(info->dev, "set base:0x%lx, RO count:0x%lx, ticks:0x%lx\n", + base, data, ticks); + + pm860x_page_reg_write(info->i2c, REG0_DATA, (base >> 24) & 0xFF); + pm860x_page_reg_write(info->i2c, REG1_DATA, (base >> 16) & 0xFF); + pm860x_page_reg_write(info->i2c, REG2_DATA, (base >> 8) & 0xFF); + pm860x_page_reg_write(info->i2c, REG3_DATA, base & 0xFF); + + if (info->sync) + info->sync(ticks); + return 0; +} + +static int pm860x_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct pm860x_rtc_info *info = dev_get_drvdata(dev); + unsigned char buf[8]; + unsigned long ticks, base, data; + int ret; + + pm860x_page_bulk_read(info->i2c, REG0_ADDR, 8, buf); + dev_dbg(info->dev, "%x-%x-%x-%x-%x-%x-%x-%x\n", buf[0], buf[1], + buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]); + base = (buf[1] << 24) | (buf[3] << 16) | (buf[5] << 8) | buf[7]; + + pm860x_bulk_read(info->i2c, PM8607_RTC_EXPIRE1, 4, buf); + data = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; + ticks = base + data; + dev_dbg(info->dev, "get base:0x%lx, RO count:0x%lx, ticks:0x%lx\n", + base, data, ticks); + + rtc_time_to_tm(ticks, &alrm->time); + ret = pm860x_reg_read(info->i2c, PM8607_RTC1); + alrm->enabled = (ret & ALARM_EN) ? 1 : 0; + alrm->pending = (ret & (ALARM | ALARM_WAKEUP)) ? 1 : 0; + return 0; +} + +static int pm860x_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct pm860x_rtc_info *info = dev_get_drvdata(dev); + struct rtc_time now_tm, alarm_tm; + unsigned long ticks, base, data; + unsigned char buf[8]; + int mask; + + pm860x_set_bits(info->i2c, PM8607_RTC1, ALARM_EN, 0); + + pm860x_page_bulk_read(info->i2c, REG0_ADDR, 8, buf); + dev_dbg(info->dev, "%x-%x-%x-%x-%x-%x-%x-%x\n", buf[0], buf[1], + buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]); + base = (buf[1] << 24) | (buf[3] << 16) | (buf[5] << 8) | buf[7]; + + /* load 32-bit read-only counter */ + pm860x_bulk_read(info->i2c, PM8607_RTC_COUNTER1, 4, buf); + data = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; + ticks = base + data; + dev_dbg(info->dev, "get base:0x%lx, RO count:0x%lx, ticks:0x%lx\n", + base, data, ticks); + + rtc_time_to_tm(ticks, &now_tm); + rtc_next_alarm_time(&alarm_tm, &now_tm, &alrm->time); + /* get new ticks for alarm in 24 hours */ + rtc_tm_to_time(&alarm_tm, &ticks); + data = ticks - base; + + buf[0] = data & 0xff; + buf[1] = (data >> 8) & 0xff; + buf[2] = (data >> 16) & 0xff; + buf[3] = (data >> 24) & 0xff; + pm860x_bulk_write(info->i2c, PM8607_RTC_EXPIRE1, 4, buf); + if (alrm->enabled) { + mask = ALARM | ALARM_WAKEUP | ALARM_EN; + pm860x_set_bits(info->i2c, PM8607_RTC1, mask, mask); + } else { + mask = ALARM | ALARM_WAKEUP | ALARM_EN; + pm860x_set_bits(info->i2c, PM8607_RTC1, mask, + ALARM | ALARM_WAKEUP); + } + return 0; +} + +static const struct rtc_class_ops pm860x_rtc_ops = { + .read_time = pm860x_rtc_read_time, + .set_time = pm860x_rtc_set_time, + .read_alarm = pm860x_rtc_read_alarm, + .set_alarm = pm860x_rtc_set_alarm, + .alarm_irq_enable = pm860x_rtc_alarm_irq_enable, +}; + +#ifdef VRTC_CALIBRATION +static void calibrate_vrtc_work(struct work_struct *work) +{ + struct pm860x_rtc_info *info = container_of(work, + struct pm860x_rtc_info, calib_work.work); + unsigned char buf[2]; + unsigned int sum, data, mean, vrtc_set; + int i; + + for (i = 0, sum = 0; i < 16; i++) { + msleep(100); + pm860x_bulk_read(info->i2c, REG_VRTC_MEAS1, 2, buf); + data = (buf[0] << 4) | buf[1]; + data = (data * 5400) >> 12; /* convert to mv */ + sum += data; + } + mean = sum >> 4; + vrtc_set = 2700 + (info->vrtc & 0x3) * 200; + dev_dbg(info->dev, "mean:%d, vrtc_set:%d\n", mean, vrtc_set); + + sum = pm860x_reg_read(info->i2c, PM8607_RTC_MISC1); + data = sum & 0x3; + if ((mean + 200) < vrtc_set) { + /* try higher voltage */ + if (++data == 4) + goto out; + data = (sum & 0xf8) | (data & 0x3); + pm860x_reg_write(info->i2c, PM8607_RTC_MISC1, data); + } else if ((mean - 200) > vrtc_set) { + /* try lower voltage */ + if (data-- == 0) + goto out; + data = (sum & 0xf8) | (data & 0x3); + pm860x_reg_write(info->i2c, PM8607_RTC_MISC1, data); + } else + goto out; + dev_dbg(info->dev, "set 0x%x to RTC_MISC1\n", data); + /* trigger next calibration since VRTC is updated */ + schedule_delayed_work(&info->calib_work, VRTC_CALIB_INTERVAL); + return; +out: + /* disable measurement */ + pm860x_set_bits(info->i2c, PM8607_MEAS_EN2, MEAS2_VRTC, 0); + dev_dbg(info->dev, "finish VRTC calibration\n"); + return; +} +#endif + +static int __devinit pm860x_rtc_probe(struct platform_device *pdev) +{ + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct pm860x_rtc_pdata *pdata = NULL; + struct pm860x_rtc_info *info; + struct rtc_time tm; + unsigned long ticks = 0; + int ret; + + pdata = pdev->dev.platform_data; + if (pdata == NULL) + dev_warn(&pdev->dev, "No platform data!\n"); + + info = kzalloc(sizeof(struct pm860x_rtc_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + info->irq = platform_get_irq(pdev, 0); + if (info->irq < 0) { + dev_err(&pdev->dev, "No IRQ resource!\n"); + ret = -EINVAL; + goto out; + } + + info->chip = chip; + info->i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; + info->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, info); + + ret = request_threaded_irq(info->irq, NULL, rtc_update_handler, + IRQF_ONESHOT, "rtc", info); + if (ret < 0) { + dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", + info->irq, ret); + goto out; + } + + /* set addresses of 32-bit base value for RTC time */ + pm860x_page_reg_write(info->i2c, REG0_ADDR, REG0_DATA); + pm860x_page_reg_write(info->i2c, REG1_ADDR, REG1_DATA); + pm860x_page_reg_write(info->i2c, REG2_ADDR, REG2_DATA); + pm860x_page_reg_write(info->i2c, REG3_ADDR, REG3_DATA); + + ret = pm860x_rtc_read_time(&pdev->dev, &tm); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to read initial time.\n"); + goto out_rtc; + } + if ((tm.tm_year < 70) || (tm.tm_year > 138)) { + tm.tm_year = 70; + tm.tm_mon = 0; + tm.tm_mday = 1; + tm.tm_hour = 0; + tm.tm_min = 0; + tm.tm_sec = 0; + ret = pm860x_rtc_set_time(&pdev->dev, &tm); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to set initial time.\n"); + goto out_rtc; + } + } + rtc_tm_to_time(&tm, &ticks); + if (pdata && pdata->sync) { + pdata->sync(ticks); + info->sync = pdata->sync; + } + + info->rtc_dev = rtc_device_register("88pm860x-rtc", &pdev->dev, + &pm860x_rtc_ops, THIS_MODULE); + ret = PTR_ERR(info->rtc_dev); + if (IS_ERR(info->rtc_dev)) { + dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret); + goto out_rtc; + } + + /* + * enable internal XO instead of internal 3.25MHz clock since it can + * free running in PMIC power-down state. + */ + pm860x_set_bits(info->i2c, PM8607_RTC1, RTC1_USE_XO, RTC1_USE_XO); + +#ifdef VRTC_CALIBRATION + /* <00> -- 2.7V, <01> -- 2.9V, <10> -- 3.1V, <11> -- 3.3V */ + if (pdata && pdata->vrtc) + info->vrtc = pdata->vrtc & 0x3; + else + info->vrtc = 1; + pm860x_set_bits(info->i2c, PM8607_MEAS_EN2, MEAS2_VRTC, MEAS2_VRTC); + + /* calibrate VRTC */ + INIT_DELAYED_WORK(&info->calib_work, calibrate_vrtc_work); + schedule_delayed_work(&info->calib_work, VRTC_CALIB_INTERVAL); +#endif /* VRTC_CALIBRATION */ + return 0; +out_rtc: + free_irq(info->irq, info); +out: + kfree(info); + return ret; +} + +static int __devexit pm860x_rtc_remove(struct platform_device *pdev) +{ + struct pm860x_rtc_info *info = platform_get_drvdata(pdev); + +#ifdef VRTC_CALIBRATION + flush_scheduled_work(); + /* disable measurement */ + pm860x_set_bits(info->i2c, PM8607_MEAS_EN2, MEAS2_VRTC, 0); +#endif /* VRTC_CALIBRATION */ + + platform_set_drvdata(pdev, NULL); + rtc_device_unregister(info->rtc_dev); + free_irq(info->irq, info); + kfree(info); + return 0; +} + +static struct platform_driver pm860x_rtc_driver = { + .driver = { + .name = "88pm860x-rtc", + .owner = THIS_MODULE, + }, + .probe = pm860x_rtc_probe, + .remove = __devexit_p(pm860x_rtc_remove), +}; + +static int __init pm860x_rtc_init(void) +{ + return platform_driver_register(&pm860x_rtc_driver); +} +module_init(pm860x_rtc_init); + +static void __exit pm860x_rtc_exit(void) +{ + platform_driver_unregister(&pm860x_rtc_driver); +} +module_exit(pm860x_rtc_exit); + +MODULE_DESCRIPTION("Marvell 88PM860x RTC driver"); +MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-em3027.c b/drivers/rtc/rtc-em3027.c new file mode 100644 index 000000000000..d8e1c2578553 --- /dev/null +++ b/drivers/rtc/rtc-em3027.c @@ -0,0 +1,161 @@ +/* + * An rtc/i2c driver for the EM Microelectronic EM3027 + * Copyright 2011 CompuLab, Ltd. + * + * Author: Mike Rapoport <mike@compulab.co.il> + * + * Based on rtc-ds1672.c by Alessandro Zummo <a.zummo@towertech.it> + * + * 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/i2c.h> +#include <linux/rtc.h> +#include <linux/bcd.h> + +/* Registers */ +#define EM3027_REG_ON_OFF_CTRL 0x00 +#define EM3027_REG_IRQ_CTRL 0x01 +#define EM3027_REG_IRQ_FLAGS 0x02 +#define EM3027_REG_STATUS 0x03 +#define EM3027_REG_RST_CTRL 0x04 + +#define EM3027_REG_WATCH_SEC 0x08 +#define EM3027_REG_WATCH_MIN 0x09 +#define EM3027_REG_WATCH_HOUR 0x0a +#define EM3027_REG_WATCH_DATE 0x0b +#define EM3027_REG_WATCH_DAY 0x0c +#define EM3027_REG_WATCH_MON 0x0d +#define EM3027_REG_WATCH_YEAR 0x0e + +#define EM3027_REG_ALARM_SEC 0x10 +#define EM3027_REG_ALARM_MIN 0x11 +#define EM3027_REG_ALARM_HOUR 0x12 +#define EM3027_REG_ALARM_DATE 0x13 +#define EM3027_REG_ALARM_DAY 0x14 +#define EM3027_REG_ALARM_MON 0x15 +#define EM3027_REG_ALARM_YEAR 0x16 + +static struct i2c_driver em3027_driver; + +static int em3027_get_time(struct device *dev, struct rtc_time *tm) +{ + struct i2c_client *client = to_i2c_client(dev); + + unsigned char addr = EM3027_REG_WATCH_SEC; + unsigned char buf[7]; + + struct i2c_msg msgs[] = { + {client->addr, 0, 1, &addr}, /* setup read addr */ + {client->addr, I2C_M_RD, 7, buf}, /* read time/date */ + }; + + /* read time/date registers */ + if ((i2c_transfer(client->adapter, &msgs[0], 2)) != 2) { + dev_err(&client->dev, "%s: read error\n", __func__); + return -EIO; + } + + tm->tm_sec = bcd2bin(buf[0]); + tm->tm_min = bcd2bin(buf[1]); + tm->tm_hour = bcd2bin(buf[2]); + tm->tm_mday = bcd2bin(buf[3]); + tm->tm_wday = bcd2bin(buf[4]); + tm->tm_mon = bcd2bin(buf[5]); + tm->tm_year = bcd2bin(buf[6]) + 100; + + return 0; +} + +static int em3027_set_time(struct device *dev, struct rtc_time *tm) +{ + struct i2c_client *client = to_i2c_client(dev); + unsigned char buf[8]; + + struct i2c_msg msg = { + client->addr, 0, 8, buf, /* write time/date */ + }; + + buf[0] = EM3027_REG_WATCH_SEC; + buf[1] = bin2bcd(tm->tm_sec); + buf[2] = bin2bcd(tm->tm_min); + buf[3] = bin2bcd(tm->tm_hour); + buf[4] = bin2bcd(tm->tm_mday); + buf[5] = bin2bcd(tm->tm_wday); + buf[6] = bin2bcd(tm->tm_mon); + buf[7] = bin2bcd(tm->tm_year % 100); + + /* write time/date registers */ + if ((i2c_transfer(client->adapter, &msg, 1)) != 1) { + dev_err(&client->dev, "%s: write error\n", __func__); + return -EIO; + } + + return 0; +} + +static const struct rtc_class_ops em3027_rtc_ops = { + .read_time = em3027_get_time, + .set_time = em3027_set_time, +}; + +static int em3027_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct rtc_device *rtc; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENODEV; + + rtc = rtc_device_register(em3027_driver.driver.name, &client->dev, + &em3027_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc)) + return PTR_ERR(rtc); + + i2c_set_clientdata(client, rtc); + + return 0; +} + +static int em3027_remove(struct i2c_client *client) +{ + struct rtc_device *rtc = i2c_get_clientdata(client); + + if (rtc) + rtc_device_unregister(rtc); + + return 0; +} + +static struct i2c_device_id em3027_id[] = { + { "em3027", 0 }, + { } +}; + +static struct i2c_driver em3027_driver = { + .driver = { + .name = "rtc-em3027", + }, + .probe = &em3027_probe, + .remove = &em3027_remove, + .id_table = em3027_id, +}; + +static int __init em3027_init(void) +{ + return i2c_add_driver(&em3027_driver); +} + +static void __exit em3027_exit(void) +{ + i2c_del_driver(&em3027_driver); +} + +MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>"); +MODULE_DESCRIPTION("EM Microelectronic EM3027 RTC driver"); +MODULE_LICENSE("GPL"); + +module_init(em3027_init); +module_exit(em3027_exit); diff --git a/drivers/rtc/rtc-m41t93.c b/drivers/rtc/rtc-m41t93.c new file mode 100644 index 000000000000..7317d3b9a3d5 --- /dev/null +++ b/drivers/rtc/rtc-m41t93.c @@ -0,0 +1,225 @@ +/* + * + * Driver for ST M41T93 SPI RTC + * + * (c) 2010 Nikolaus Voss, Weinmann Medical GmbH + * + * 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/bcd.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/spi/spi.h> + +#define M41T93_REG_SSEC 0 +#define M41T93_REG_ST_SEC 1 +#define M41T93_REG_MIN 2 +#define M41T93_REG_CENT_HOUR 3 +#define M41T93_REG_WDAY 4 +#define M41T93_REG_DAY 5 +#define M41T93_REG_MON 6 +#define M41T93_REG_YEAR 7 + + +#define M41T93_REG_ALM_HOUR_HT 0xc +#define M41T93_REG_FLAGS 0xf + +#define M41T93_FLAG_ST (1 << 7) +#define M41T93_FLAG_OF (1 << 2) +#define M41T93_FLAG_BL (1 << 4) +#define M41T93_FLAG_HT (1 << 6) + +static inline int m41t93_set_reg(struct spi_device *spi, u8 addr, u8 data) +{ + u8 buf[2]; + + /* MSB must be '1' to write */ + buf[0] = addr | 0x80; + buf[1] = data; + + return spi_write(spi, buf, sizeof(buf)); +} + +static int m41t93_set_time(struct device *dev, struct rtc_time *tm) +{ + struct spi_device *spi = to_spi_device(dev); + u8 buf[9] = {0x80}; /* write cmd + 8 data bytes */ + u8 * const data = &buf[1]; /* ptr to first data byte */ + + dev_dbg(dev, "%s secs=%d, mins=%d, " + "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", + "write", tm->tm_sec, tm->tm_min, + tm->tm_hour, tm->tm_mday, + tm->tm_mon, tm->tm_year, tm->tm_wday); + + if (tm->tm_year < 100) { + dev_warn(&spi->dev, "unsupported date (before 2000-01-01).\n"); + return -EINVAL; + } + + data[M41T93_REG_SSEC] = 0; + data[M41T93_REG_ST_SEC] = bin2bcd(tm->tm_sec); + data[M41T93_REG_MIN] = bin2bcd(tm->tm_min); + data[M41T93_REG_CENT_HOUR] = bin2bcd(tm->tm_hour) | + ((tm->tm_year/100-1) << 6); + data[M41T93_REG_DAY] = bin2bcd(tm->tm_mday); + data[M41T93_REG_WDAY] = bin2bcd(tm->tm_wday + 1); + data[M41T93_REG_MON] = bin2bcd(tm->tm_mon + 1); + data[M41T93_REG_YEAR] = bin2bcd(tm->tm_year % 100); + + return spi_write(spi, buf, sizeof(buf)); +} + + +static int m41t93_get_time(struct device *dev, struct rtc_time *tm) +{ + struct spi_device *spi = to_spi_device(dev); + const u8 start_addr = 0; + u8 buf[8]; + int century_after_1900; + int tmp; + int ret = 0; + + /* Check status of clock. Two states must be considered: + 1. halt bit (HT) is set: the clock is running but update of readout + registers has been disabled due to power failure. This is normal + case after poweron. Time is valid after resetting HT bit. + 2. oscillator fail bit (OF) is set. Oscillator has be stopped and + time is invalid: + a) OF can be immeditely reset. + b) OF cannot be immediately reset: oscillator has to be restarted. + */ + tmp = spi_w8r8(spi, M41T93_REG_ALM_HOUR_HT); + if (tmp < 0) + return tmp; + + if (tmp & M41T93_FLAG_HT) { + dev_dbg(&spi->dev, "HT bit is set, reenable clock update.\n"); + m41t93_set_reg(spi, M41T93_REG_ALM_HOUR_HT, + tmp & ~M41T93_FLAG_HT); + } + + tmp = spi_w8r8(spi, M41T93_REG_FLAGS); + if (tmp < 0) + return tmp; + + if (tmp & M41T93_FLAG_OF) { + ret = -EINVAL; + dev_warn(&spi->dev, "OF bit is set, resetting.\n"); + m41t93_set_reg(spi, M41T93_REG_FLAGS, tmp & ~M41T93_FLAG_OF); + + tmp = spi_w8r8(spi, M41T93_REG_FLAGS); + if (tmp < 0) + return tmp; + else if (tmp & M41T93_FLAG_OF) { + u8 reset_osc = buf[M41T93_REG_ST_SEC] | M41T93_FLAG_ST; + + dev_warn(&spi->dev, + "OF bit is still set, kickstarting clock.\n"); + m41t93_set_reg(spi, M41T93_REG_ST_SEC, reset_osc); + reset_osc &= ~M41T93_FLAG_ST; + m41t93_set_reg(spi, M41T93_REG_ST_SEC, reset_osc); + } + } + + if (tmp & M41T93_FLAG_BL) + dev_warn(&spi->dev, "BL bit is set, replace battery.\n"); + + /* read actual time/date */ + tmp = spi_write_then_read(spi, &start_addr, 1, buf, sizeof(buf)); + if (tmp < 0) + return tmp; + + tm->tm_sec = bcd2bin(buf[M41T93_REG_ST_SEC]); + tm->tm_min = bcd2bin(buf[M41T93_REG_MIN]); + tm->tm_hour = bcd2bin(buf[M41T93_REG_CENT_HOUR] & 0x3f); + tm->tm_mday = bcd2bin(buf[M41T93_REG_DAY]); + tm->tm_mon = bcd2bin(buf[M41T93_REG_MON]) - 1; + tm->tm_wday = bcd2bin(buf[M41T93_REG_WDAY] & 0x0f) - 1; + + century_after_1900 = (buf[M41T93_REG_CENT_HOUR] >> 6) + 1; + tm->tm_year = bcd2bin(buf[M41T93_REG_YEAR]) + century_after_1900 * 100; + + dev_dbg(dev, "%s secs=%d, mins=%d, " + "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", + "read", tm->tm_sec, tm->tm_min, + tm->tm_hour, tm->tm_mday, + tm->tm_mon, tm->tm_year, tm->tm_wday); + + return ret < 0 ? ret : rtc_valid_tm(tm); +} + + +static const struct rtc_class_ops m41t93_rtc_ops = { + .read_time = m41t93_get_time, + .set_time = m41t93_set_time, +}; + +static struct spi_driver m41t93_driver; + +static int __devinit m41t93_probe(struct spi_device *spi) +{ + struct rtc_device *rtc; + int res; + + spi->bits_per_word = 8; + spi_setup(spi); + + res = spi_w8r8(spi, M41T93_REG_WDAY); + if (res < 0 || (res & 0xf8) != 0) { + dev_err(&spi->dev, "not found 0x%x.\n", res); + return -ENODEV; + } + + rtc = rtc_device_register(m41t93_driver.driver.name, + &spi->dev, &m41t93_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc)) + return PTR_ERR(rtc); + + dev_set_drvdata(&spi->dev, rtc); + + return 0; +} + + +static int __devexit m41t93_remove(struct spi_device *spi) +{ + struct rtc_device *rtc = spi_get_drvdata(spi); + + if (rtc) + rtc_device_unregister(rtc); + + return 0; +} + +static struct spi_driver m41t93_driver = { + .driver = { + .name = "rtc-m41t93", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = m41t93_probe, + .remove = __devexit_p(m41t93_remove), +}; + +static __init int m41t93_init(void) +{ + return spi_register_driver(&m41t93_driver); +} +module_init(m41t93_init); + +static __exit void m41t93_exit(void) +{ + spi_unregister_driver(&m41t93_driver); +} +module_exit(m41t93_exit); + +MODULE_AUTHOR("Nikolaus Voss <n.voss@weinmann.de>"); +MODULE_DESCRIPTION("Driver for ST M41T93 SPI RTC"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("spi:rtc-m41t93"); diff --git a/drivers/rtc/rtc-mrst.c b/drivers/rtc/rtc-mrst.c index b2f096871a97..0cec5650d56a 100644 --- a/drivers/rtc/rtc-mrst.c +++ b/drivers/rtc/rtc-mrst.c @@ -380,7 +380,7 @@ cleanup1: cleanup0: dev_set_drvdata(dev, NULL); mrst_rtc.dev = NULL; - release_region(iomem->start, iomem->end + 1 - iomem->start); + release_mem_region(iomem->start, resource_size(iomem)); dev_err(dev, "rtc-mrst: unable to initialise\n"); return retval; } @@ -406,7 +406,7 @@ static void __devexit rtc_mrst_do_remove(struct device *dev) mrst->rtc = NULL; iomem = mrst->iomem; - release_region(iomem->start, iomem->end + 1 - iomem->start); + release_mem_region(iomem->start, resource_size(iomem)); mrst->iomem = NULL; mrst->dev = NULL; diff --git a/drivers/rtc/rtc-mxc.c b/drivers/rtc/rtc-mxc.c index d814417bee8c..39e41fbdf08b 100644 --- a/drivers/rtc/rtc-mxc.c +++ b/drivers/rtc/rtc-mxc.c @@ -55,12 +55,6 @@ static const u32 PIE_BIT_DEF[MAX_PIE_NUM][2] = { { MAX_PIE_FREQ, RTC_SAM7_BIT }, }; -/* Those are the bits from a classic RTC we want to mimic */ -#define RTC_IRQF 0x80 /* any of the following 3 is active */ -#define RTC_PF 0x40 /* Periodic interrupt */ -#define RTC_AF 0x20 /* Alarm interrupt */ -#define RTC_UF 0x10 /* Update interrupt for 1Hz RTC */ - #define MXC_RTC_TIME 0 #define MXC_RTC_ALARM 1 diff --git a/drivers/rtc/rtc-pcf50633.c b/drivers/rtc/rtc-pcf50633.c index f90c574f9d05..0c423892923c 100644 --- a/drivers/rtc/rtc-pcf50633.c +++ b/drivers/rtc/rtc-pcf50633.c @@ -58,7 +58,6 @@ struct pcf50633_time { struct pcf50633_rtc { int alarm_enabled; - int second_enabled; int alarm_pending; struct pcf50633 *pcf; @@ -143,7 +142,7 @@ static int pcf50633_rtc_set_time(struct device *dev, struct rtc_time *tm) { struct pcf50633_rtc *rtc; struct pcf50633_time pcf_tm; - int second_masked, alarm_masked, ret = 0; + int alarm_masked, ret = 0; rtc = dev_get_drvdata(dev); @@ -162,11 +161,8 @@ static int pcf50633_rtc_set_time(struct device *dev, struct rtc_time *tm) pcf_tm.time[PCF50633_TI_SEC]); - second_masked = pcf50633_irq_mask_get(rtc->pcf, PCF50633_IRQ_SECOND); alarm_masked = pcf50633_irq_mask_get(rtc->pcf, PCF50633_IRQ_ALARM); - if (!second_masked) - pcf50633_irq_mask(rtc->pcf, PCF50633_IRQ_SECOND); if (!alarm_masked) pcf50633_irq_mask(rtc->pcf, PCF50633_IRQ_ALARM); @@ -175,8 +171,6 @@ static int pcf50633_rtc_set_time(struct device *dev, struct rtc_time *tm) PCF50633_TI_EXTENT, &pcf_tm.time[0]); - if (!second_masked) - pcf50633_irq_unmask(rtc->pcf, PCF50633_IRQ_SECOND); if (!alarm_masked) pcf50633_irq_unmask(rtc->pcf, PCF50633_IRQ_ALARM); @@ -250,15 +244,8 @@ static void pcf50633_rtc_irq(int irq, void *data) { struct pcf50633_rtc *rtc = data; - switch (irq) { - case PCF50633_IRQ_ALARM: - rtc_update_irq(rtc->rtc_dev, 1, RTC_AF | RTC_IRQF); - rtc->alarm_pending = 1; - break; - case PCF50633_IRQ_SECOND: - rtc_update_irq(rtc->rtc_dev, 1, RTC_UF | RTC_IRQF); - break; - } + rtc_update_irq(rtc->rtc_dev, 1, RTC_AF | RTC_IRQF); + rtc->alarm_pending = 1; } static int __devinit pcf50633_rtc_probe(struct platform_device *pdev) @@ -282,9 +269,6 @@ static int __devinit pcf50633_rtc_probe(struct platform_device *pdev) pcf50633_register_irq(rtc->pcf, PCF50633_IRQ_ALARM, pcf50633_rtc_irq, rtc); - pcf50633_register_irq(rtc->pcf, PCF50633_IRQ_SECOND, - pcf50633_rtc_irq, rtc); - return 0; } @@ -295,7 +279,6 @@ static int __devexit pcf50633_rtc_remove(struct platform_device *pdev) rtc = platform_get_drvdata(pdev); pcf50633_free_irq(rtc->pcf, PCF50633_IRQ_ALARM); - pcf50633_free_irq(rtc->pcf, PCF50633_IRQ_SECOND); rtc_device_unregister(rtc->rtc_dev); kfree(rtc); diff --git a/drivers/rtc/rtc-puv3.c b/drivers/rtc/rtc-puv3.c new file mode 100644 index 000000000000..46f14b82f3ab --- /dev/null +++ b/drivers/rtc/rtc-puv3.c @@ -0,0 +1,359 @@ +/* + * RTC driver code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * 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/module.h> +#include <linux/fs.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/rtc.h> +#include <linux/bcd.h> +#include <linux/clk.h> +#include <linux/log2.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +#include <asm/irq.h> +#include <mach/hardware.h> + +static struct resource *puv3_rtc_mem; + +static int puv3_rtc_alarmno = IRQ_RTCAlarm; +static int puv3_rtc_tickno = IRQ_RTC; + +static DEFINE_SPINLOCK(puv3_rtc_pie_lock); + +/* IRQ Handlers */ +static irqreturn_t puv3_rtc_alarmirq(int irq, void *id) +{ + struct rtc_device *rdev = id; + + writel(readl(RTC_RTSR) | RTC_RTSR_AL, RTC_RTSR); + rtc_update_irq(rdev, 1, RTC_AF | RTC_IRQF); + return IRQ_HANDLED; +} + +static irqreturn_t puv3_rtc_tickirq(int irq, void *id) +{ + struct rtc_device *rdev = id; + + writel(readl(RTC_RTSR) | RTC_RTSR_HZ, RTC_RTSR); + rtc_update_irq(rdev, 1, RTC_PF | RTC_IRQF); + return IRQ_HANDLED; +} + +/* Update control registers */ +static void puv3_rtc_setaie(int to) +{ + unsigned int tmp; + + pr_debug("%s: aie=%d\n", __func__, to); + + tmp = readl(RTC_RTSR) & ~RTC_RTSR_ALE; + + if (to) + tmp |= RTC_RTSR_ALE; + + writel(tmp, RTC_RTSR); +} + +static int puv3_rtc_setpie(struct device *dev, int enabled) +{ + unsigned int tmp; + + pr_debug("%s: pie=%d\n", __func__, enabled); + + spin_lock_irq(&puv3_rtc_pie_lock); + tmp = readl(RTC_RTSR) & ~RTC_RTSR_HZE; + + if (enabled) + tmp |= RTC_RTSR_HZE; + + writel(tmp, RTC_RTSR); + spin_unlock_irq(&puv3_rtc_pie_lock); + + return 0; +} + +/* Time read/write */ +static int puv3_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm) +{ + rtc_time_to_tm(readl(RTC_RCNR), rtc_tm); + + pr_debug("read time %02x.%02x.%02x %02x/%02x/%02x\n", + rtc_tm->tm_year, rtc_tm->tm_mon, rtc_tm->tm_mday, + rtc_tm->tm_hour, rtc_tm->tm_min, rtc_tm->tm_sec); + + return 0; +} + +static int puv3_rtc_settime(struct device *dev, struct rtc_time *tm) +{ + unsigned long rtc_count = 0; + + pr_debug("set time %02d.%02d.%02d %02d/%02d/%02d\n", + tm->tm_year, tm->tm_mon, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + + rtc_tm_to_time(tm, &rtc_count); + writel(rtc_count, RTC_RCNR); + + return 0; +} + +static int puv3_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct rtc_time *alm_tm = &alrm->time; + + rtc_time_to_tm(readl(RTC_RTAR), alm_tm); + + alrm->enabled = readl(RTC_RTSR) & RTC_RTSR_ALE; + + pr_debug("read alarm %02x %02x.%02x.%02x %02x/%02x/%02x\n", + alrm->enabled, + alm_tm->tm_year, alm_tm->tm_mon, alm_tm->tm_mday, + alm_tm->tm_hour, alm_tm->tm_min, alm_tm->tm_sec); + + return 0; +} + +static int puv3_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct rtc_time *tm = &alrm->time; + unsigned long rtcalarm_count = 0; + + pr_debug("puv3_rtc_setalarm: %d, %02x/%02x/%02x %02x.%02x.%02x\n", + alrm->enabled, + tm->tm_mday & 0xff, tm->tm_mon & 0xff, tm->tm_year & 0xff, + tm->tm_hour & 0xff, tm->tm_min & 0xff, tm->tm_sec); + + rtc_tm_to_time(tm, &rtcalarm_count); + writel(rtcalarm_count, RTC_RTAR); + + puv3_rtc_setaie(alrm->enabled); + + if (alrm->enabled) + enable_irq_wake(puv3_rtc_alarmno); + else + disable_irq_wake(puv3_rtc_alarmno); + + return 0; +} + +static int puv3_rtc_proc(struct device *dev, struct seq_file *seq) +{ + seq_printf(seq, "periodic_IRQ\t: %s\n", + (readl(RTC_RTSR) & RTC_RTSR_HZE) ? "yes" : "no"); + return 0; +} + +static int puv3_rtc_open(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_device *rtc_dev = platform_get_drvdata(pdev); + int ret; + + ret = request_irq(puv3_rtc_alarmno, puv3_rtc_alarmirq, + IRQF_DISABLED, "pkunity-rtc alarm", rtc_dev); + + if (ret) { + dev_err(dev, "IRQ%d error %d\n", puv3_rtc_alarmno, ret); + return ret; + } + + ret = request_irq(puv3_rtc_tickno, puv3_rtc_tickirq, + IRQF_DISABLED, "pkunity-rtc tick", rtc_dev); + + if (ret) { + dev_err(dev, "IRQ%d error %d\n", puv3_rtc_tickno, ret); + goto tick_err; + } + + return ret; + + tick_err: + free_irq(puv3_rtc_alarmno, rtc_dev); + return ret; +} + +static void puv3_rtc_release(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_device *rtc_dev = platform_get_drvdata(pdev); + + /* do not clear AIE here, it may be needed for wake */ + puv3_rtc_setpie(dev, 0); + free_irq(puv3_rtc_alarmno, rtc_dev); + free_irq(puv3_rtc_tickno, rtc_dev); +} + +static const struct rtc_class_ops puv3_rtcops = { + .open = puv3_rtc_open, + .release = puv3_rtc_release, + .read_time = puv3_rtc_gettime, + .set_time = puv3_rtc_settime, + .read_alarm = puv3_rtc_getalarm, + .set_alarm = puv3_rtc_setalarm, + .proc = puv3_rtc_proc, +}; + +static void puv3_rtc_enable(struct platform_device *pdev, int en) +{ + if (!en) { + writel(readl(RTC_RTSR) & ~RTC_RTSR_HZE, RTC_RTSR); + } else { + /* re-enable the device, and check it is ok */ + if ((readl(RTC_RTSR) & RTC_RTSR_HZE) == 0) { + dev_info(&pdev->dev, "rtc disabled, re-enabling\n"); + writel(readl(RTC_RTSR) | RTC_RTSR_HZE, RTC_RTSR); + } + } +} + +static int puv3_rtc_remove(struct platform_device *dev) +{ + struct rtc_device *rtc = platform_get_drvdata(dev); + + platform_set_drvdata(dev, NULL); + rtc_device_unregister(rtc); + + puv3_rtc_setpie(&dev->dev, 0); + puv3_rtc_setaie(0); + + release_resource(puv3_rtc_mem); + kfree(puv3_rtc_mem); + + return 0; +} + +static int puv3_rtc_probe(struct platform_device *pdev) +{ + struct rtc_device *rtc; + struct resource *res; + int ret; + + pr_debug("%s: probe=%p\n", __func__, pdev); + + /* find the IRQs */ + puv3_rtc_tickno = platform_get_irq(pdev, 1); + if (puv3_rtc_tickno < 0) { + dev_err(&pdev->dev, "no irq for rtc tick\n"); + return -ENOENT; + } + + puv3_rtc_alarmno = platform_get_irq(pdev, 0); + if (puv3_rtc_alarmno < 0) { + dev_err(&pdev->dev, "no irq for alarm\n"); + return -ENOENT; + } + + pr_debug("PKUnity_rtc: tick irq %d, alarm irq %d\n", + puv3_rtc_tickno, puv3_rtc_alarmno); + + /* get the memory region */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "failed to get memory region resource\n"); + return -ENOENT; + } + + puv3_rtc_mem = request_mem_region(res->start, + res->end-res->start+1, + pdev->name); + + if (puv3_rtc_mem == NULL) { + dev_err(&pdev->dev, "failed to reserve memory region\n"); + ret = -ENOENT; + goto err_nores; + } + + puv3_rtc_enable(pdev, 1); + + /* register RTC and exit */ + rtc = rtc_device_register("pkunity", &pdev->dev, &puv3_rtcops, + THIS_MODULE); + + if (IS_ERR(rtc)) { + dev_err(&pdev->dev, "cannot attach rtc\n"); + ret = PTR_ERR(rtc); + goto err_nortc; + } + + /* platform setup code should have handled this; sigh */ + if (!device_can_wakeup(&pdev->dev)) + device_init_wakeup(&pdev->dev, 1); + + platform_set_drvdata(pdev, rtc); + return 0; + + err_nortc: + puv3_rtc_enable(pdev, 0); + release_resource(puv3_rtc_mem); + + err_nores: + return ret; +} + +#ifdef CONFIG_PM + +static int ticnt_save; + +static int puv3_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + /* save RTAR for anyone using periodic interrupts */ + ticnt_save = readl(RTC_RTAR); + puv3_rtc_enable(pdev, 0); + return 0; +} + +static int puv3_rtc_resume(struct platform_device *pdev) +{ + puv3_rtc_enable(pdev, 1); + writel(ticnt_save, RTC_RTAR); + return 0; +} +#else +#define puv3_rtc_suspend NULL +#define puv3_rtc_resume NULL +#endif + +static struct platform_driver puv3_rtcdrv = { + .probe = puv3_rtc_probe, + .remove = __devexit_p(puv3_rtc_remove), + .suspend = puv3_rtc_suspend, + .resume = puv3_rtc_resume, + .driver = { + .name = "PKUnity-v3-RTC", + .owner = THIS_MODULE, + } +}; + +static char __initdata banner[] = "PKUnity-v3 RTC, (c) 2009 PKUnity Co.\n"; + +static int __init puv3_rtc_init(void) +{ + printk(banner); + return platform_driver_register(&puv3_rtcdrv); +} + +static void __exit puv3_rtc_exit(void) +{ + platform_driver_unregister(&puv3_rtcdrv); +} + +module_init(puv3_rtc_init); +module_exit(puv3_rtc_exit); + +MODULE_DESCRIPTION("RTC Driver for the PKUnity v3 chip"); +MODULE_AUTHOR("Hu Dongliang"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/rtc/rtc-rv3029c2.c b/drivers/rtc/rtc-rv3029c2.c new file mode 100644 index 000000000000..ea09ff211dc6 --- /dev/null +++ b/drivers/rtc/rtc-rv3029c2.c @@ -0,0 +1,454 @@ +/* + * Micro Crystal RV-3029C2 rtc class driver + * + * Author: Gregory Hermant <gregory.hermant@calao-systems.com> + * + * based on previously existing rtc class drivers + * + * 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. + * + * NOTE: Currently this driver only supports the bare minimum for read + * and write the RTC and alarms. The extra features provided by this chip + * (trickle charger, eeprom, T° compensation) are unavailable. + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/bcd.h> +#include <linux/rtc.h> + +/* Register map */ +/* control section */ +#define RV3029C2_ONOFF_CTRL 0x00 +#define RV3029C2_IRQ_CTRL 0x01 +#define RV3029C2_IRQ_CTRL_AIE (1 << 0) +#define RV3029C2_IRQ_FLAGS 0x02 +#define RV3029C2_IRQ_FLAGS_AF (1 << 0) +#define RV3029C2_STATUS 0x03 +#define RV3029C2_STATUS_VLOW1 (1 << 2) +#define RV3029C2_STATUS_VLOW2 (1 << 3) +#define RV3029C2_STATUS_SR (1 << 4) +#define RV3029C2_STATUS_PON (1 << 5) +#define RV3029C2_STATUS_EEBUSY (1 << 7) +#define RV3029C2_RST_CTRL 0x04 +#define RV3029C2_CONTROL_SECTION_LEN 0x05 + +/* watch section */ +#define RV3029C2_W_SEC 0x08 +#define RV3029C2_W_MINUTES 0x09 +#define RV3029C2_W_HOURS 0x0A +#define RV3029C2_REG_HR_12_24 (1<<6) /* 24h/12h mode */ +#define RV3029C2_REG_HR_PM (1<<5) /* PM/AM bit in 12h mode */ +#define RV3029C2_W_DATE 0x0B +#define RV3029C2_W_DAYS 0x0C +#define RV3029C2_W_MONTHS 0x0D +#define RV3029C2_W_YEARS 0x0E +#define RV3029C2_WATCH_SECTION_LEN 0x07 + +/* alarm section */ +#define RV3029C2_A_SC 0x10 +#define RV3029C2_A_MN 0x11 +#define RV3029C2_A_HR 0x12 +#define RV3029C2_A_DT 0x13 +#define RV3029C2_A_DW 0x14 +#define RV3029C2_A_MO 0x15 +#define RV3029C2_A_YR 0x16 +#define RV3029C2_ALARM_SECTION_LEN 0x07 + +/* timer section */ +#define RV3029C2_TIMER_LOW 0x18 +#define RV3029C2_TIMER_HIGH 0x19 + +/* temperature section */ +#define RV3029C2_TEMP_PAGE 0x20 + +/* eeprom data section */ +#define RV3029C2_E2P_EEDATA1 0x28 +#define RV3029C2_E2P_EEDATA2 0x29 + +/* eeprom control section */ +#define RV3029C2_CONTROL_E2P_EECTRL 0x30 +#define RV3029C2_TRICKLE_1K (1<<0) /* 1K resistance */ +#define RV3029C2_TRICKLE_5K (1<<1) /* 5K resistance */ +#define RV3029C2_TRICKLE_20K (1<<2) /* 20K resistance */ +#define RV3029C2_TRICKLE_80K (1<<3) /* 80K resistance */ +#define RV3029C2_CONTROL_E2P_XTALOFFSET 0x31 +#define RV3029C2_CONTROL_E2P_QCOEF 0x32 +#define RV3029C2_CONTROL_E2P_TURNOVER 0x33 + +/* user ram section */ +#define RV3029C2_USR1_RAM_PAGE 0x38 +#define RV3029C2_USR1_SECTION_LEN 0x04 +#define RV3029C2_USR2_RAM_PAGE 0x3C +#define RV3029C2_USR2_SECTION_LEN 0x04 + +static int +rv3029c2_i2c_read_regs(struct i2c_client *client, u8 reg, u8 *buf, + unsigned len) +{ + int ret; + + if ((reg > RV3029C2_USR1_RAM_PAGE + 7) || + (reg + len > RV3029C2_USR1_RAM_PAGE + 8)) + return -EINVAL; + + ret = i2c_smbus_read_i2c_block_data(client, reg, len, buf); + if (ret < 0) + return ret; + if (ret < len) + return -EIO; + return 0; +} + +static int +rv3029c2_i2c_write_regs(struct i2c_client *client, u8 reg, u8 const buf[], + unsigned len) +{ + if ((reg > RV3029C2_USR1_RAM_PAGE + 7) || + (reg + len > RV3029C2_USR1_RAM_PAGE + 8)) + return -EINVAL; + + return i2c_smbus_write_i2c_block_data(client, reg, len, buf); +} + +static int +rv3029c2_i2c_get_sr(struct i2c_client *client, u8 *buf) +{ + int ret = rv3029c2_i2c_read_regs(client, RV3029C2_STATUS, buf, 1); + + if (ret < 0) + return -EIO; + dev_dbg(&client->dev, "status = 0x%.2x (%d)\n", buf[0], buf[0]); + return 0; +} + +static int +rv3029c2_i2c_set_sr(struct i2c_client *client, u8 val) +{ + u8 buf[1]; + int sr; + + buf[0] = val; + sr = rv3029c2_i2c_write_regs(client, RV3029C2_STATUS, buf, 1); + dev_dbg(&client->dev, "status = 0x%.2x (%d)\n", buf[0], buf[0]); + if (sr < 0) + return -EIO; + return 0; +} + +static int +rv3029c2_i2c_read_time(struct i2c_client *client, struct rtc_time *tm) +{ + u8 buf[1]; + int ret; + u8 regs[RV3029C2_WATCH_SECTION_LEN] = { 0, }; + + ret = rv3029c2_i2c_get_sr(client, buf); + if (ret < 0) { + dev_err(&client->dev, "%s: reading SR failed\n", __func__); + return -EIO; + } + + ret = rv3029c2_i2c_read_regs(client, RV3029C2_W_SEC , regs, + RV3029C2_WATCH_SECTION_LEN); + if (ret < 0) { + dev_err(&client->dev, "%s: reading RTC section failed\n", + __func__); + return ret; + } + + tm->tm_sec = bcd2bin(regs[RV3029C2_W_SEC-RV3029C2_W_SEC]); + tm->tm_min = bcd2bin(regs[RV3029C2_W_MINUTES-RV3029C2_W_SEC]); + + /* HR field has a more complex interpretation */ + { + const u8 _hr = regs[RV3029C2_W_HOURS-RV3029C2_W_SEC]; + if (_hr & RV3029C2_REG_HR_12_24) { + /* 12h format */ + tm->tm_hour = bcd2bin(_hr & 0x1f); + if (_hr & RV3029C2_REG_HR_PM) /* PM flag set */ + tm->tm_hour += 12; + } else /* 24h format */ + tm->tm_hour = bcd2bin(_hr & 0x3f); + } + + tm->tm_mday = bcd2bin(regs[RV3029C2_W_DATE-RV3029C2_W_SEC]); + tm->tm_mon = bcd2bin(regs[RV3029C2_W_MONTHS-RV3029C2_W_SEC]) - 1; + tm->tm_year = bcd2bin(regs[RV3029C2_W_YEARS-RV3029C2_W_SEC]) + 100; + tm->tm_wday = bcd2bin(regs[RV3029C2_W_DAYS-RV3029C2_W_SEC]) - 1; + + return 0; +} + +static int rv3029c2_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + return rv3029c2_i2c_read_time(to_i2c_client(dev), tm); +} + +static int +rv3029c2_i2c_read_alarm(struct i2c_client *client, struct rtc_wkalrm *alarm) +{ + struct rtc_time *const tm = &alarm->time; + int ret; + u8 regs[8]; + + ret = rv3029c2_i2c_get_sr(client, regs); + if (ret < 0) { + dev_err(&client->dev, "%s: reading SR failed\n", __func__); + return -EIO; + } + + ret = rv3029c2_i2c_read_regs(client, RV3029C2_A_SC, regs, + RV3029C2_ALARM_SECTION_LEN); + + if (ret < 0) { + dev_err(&client->dev, "%s: reading alarm section failed\n", + __func__); + return ret; + } + + tm->tm_sec = bcd2bin(regs[RV3029C2_A_SC-RV3029C2_A_SC] & 0x7f); + tm->tm_min = bcd2bin(regs[RV3029C2_A_MN-RV3029C2_A_SC] & 0x7f); + tm->tm_hour = bcd2bin(regs[RV3029C2_A_HR-RV3029C2_A_SC] & 0x3f); + tm->tm_mday = bcd2bin(regs[RV3029C2_A_DT-RV3029C2_A_SC] & 0x3f); + tm->tm_mon = bcd2bin(regs[RV3029C2_A_MO-RV3029C2_A_SC] & 0x1f) - 1; + tm->tm_year = bcd2bin(regs[RV3029C2_A_YR-RV3029C2_A_SC] & 0x7f) + 100; + tm->tm_wday = bcd2bin(regs[RV3029C2_A_DW-RV3029C2_A_SC] & 0x07) - 1; + + return 0; +} + +static int +rv3029c2_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + return rv3029c2_i2c_read_alarm(to_i2c_client(dev), alarm); +} + +static int rv3029c2_rtc_i2c_alarm_set_irq(struct i2c_client *client, + int enable) +{ + int ret; + u8 buf[1]; + + /* enable AIE irq */ + ret = rv3029c2_i2c_read_regs(client, RV3029C2_IRQ_CTRL, buf, 1); + if (ret < 0) { + dev_err(&client->dev, "can't read INT reg\n"); + return ret; + } + if (enable) + buf[0] |= RV3029C2_IRQ_CTRL_AIE; + else + buf[0] &= ~RV3029C2_IRQ_CTRL_AIE; + + ret = rv3029c2_i2c_write_regs(client, RV3029C2_IRQ_CTRL, buf, 1); + if (ret < 0) { + dev_err(&client->dev, "can't set INT reg\n"); + return ret; + } + + return 0; +} + +static int rv3029c2_rtc_i2c_set_alarm(struct i2c_client *client, + struct rtc_wkalrm *alarm) +{ + struct rtc_time *const tm = &alarm->time; + int ret; + u8 regs[8]; + + /* + * The clock has an 8 bit wide bcd-coded register (they never learn) + * for the year. tm_year is an offset from 1900 and we are interested + * in the 2000-2099 range, so any value less than 100 is invalid. + */ + if (tm->tm_year < 100) + return -EINVAL; + + ret = rv3029c2_i2c_get_sr(client, regs); + if (ret < 0) { + dev_err(&client->dev, "%s: reading SR failed\n", __func__); + return -EIO; + } + regs[RV3029C2_A_SC-RV3029C2_A_SC] = bin2bcd(tm->tm_sec & 0x7f); + regs[RV3029C2_A_MN-RV3029C2_A_SC] = bin2bcd(tm->tm_min & 0x7f); + regs[RV3029C2_A_HR-RV3029C2_A_SC] = bin2bcd(tm->tm_hour & 0x3f); + regs[RV3029C2_A_DT-RV3029C2_A_SC] = bin2bcd(tm->tm_mday & 0x3f); + regs[RV3029C2_A_MO-RV3029C2_A_SC] = bin2bcd((tm->tm_mon & 0x1f) - 1); + regs[RV3029C2_A_DW-RV3029C2_A_SC] = bin2bcd((tm->tm_wday & 7) - 1); + regs[RV3029C2_A_YR-RV3029C2_A_SC] = bin2bcd((tm->tm_year & 0x7f) - 100); + + ret = rv3029c2_i2c_write_regs(client, RV3029C2_A_SC, regs, + RV3029C2_ALARM_SECTION_LEN); + if (ret < 0) + return ret; + + if (alarm->enabled) { + u8 buf[1]; + + /* clear AF flag */ + ret = rv3029c2_i2c_read_regs(client, RV3029C2_IRQ_FLAGS, + buf, 1); + if (ret < 0) { + dev_err(&client->dev, "can't read alarm flag\n"); + return ret; + } + buf[0] &= ~RV3029C2_IRQ_FLAGS_AF; + ret = rv3029c2_i2c_write_regs(client, RV3029C2_IRQ_FLAGS, + buf, 1); + if (ret < 0) { + dev_err(&client->dev, "can't set alarm flag\n"); + return ret; + } + /* enable AIE irq */ + ret = rv3029c2_rtc_i2c_alarm_set_irq(client, 1); + if (ret) + return ret; + + dev_dbg(&client->dev, "alarm IRQ armed\n"); + } else { + /* disable AIE irq */ + ret = rv3029c2_rtc_i2c_alarm_set_irq(client, 1); + if (ret) + return ret; + + dev_dbg(&client->dev, "alarm IRQ disabled\n"); + } + + return 0; +} + +static int rv3029c2_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + return rv3029c2_rtc_i2c_set_alarm(to_i2c_client(dev), alarm); +} + +static int +rv3029c2_i2c_set_time(struct i2c_client *client, struct rtc_time const *tm) +{ + u8 regs[8]; + int ret; + + /* + * The clock has an 8 bit wide bcd-coded register (they never learn) + * for the year. tm_year is an offset from 1900 and we are interested + * in the 2000-2099 range, so any value less than 100 is invalid. + */ + if (tm->tm_year < 100) + return -EINVAL; + + regs[RV3029C2_W_SEC-RV3029C2_W_SEC] = bin2bcd(tm->tm_sec); + regs[RV3029C2_W_MINUTES-RV3029C2_W_SEC] = bin2bcd(tm->tm_min); + regs[RV3029C2_W_HOURS-RV3029C2_W_SEC] = bin2bcd(tm->tm_hour); + regs[RV3029C2_W_DATE-RV3029C2_W_SEC] = bin2bcd(tm->tm_mday); + regs[RV3029C2_W_MONTHS-RV3029C2_W_SEC] = bin2bcd(tm->tm_mon+1); + regs[RV3029C2_W_DAYS-RV3029C2_W_SEC] = bin2bcd((tm->tm_wday & 7)+1); + regs[RV3029C2_W_YEARS-RV3029C2_W_SEC] = bin2bcd(tm->tm_year - 100); + + ret = rv3029c2_i2c_write_regs(client, RV3029C2_W_SEC, regs, + RV3029C2_WATCH_SECTION_LEN); + if (ret < 0) + return ret; + + ret = rv3029c2_i2c_get_sr(client, regs); + if (ret < 0) { + dev_err(&client->dev, "%s: reading SR failed\n", __func__); + return ret; + } + /* clear PON bit */ + ret = rv3029c2_i2c_set_sr(client, (regs[0] & ~RV3029C2_STATUS_PON)); + if (ret < 0) { + dev_err(&client->dev, "%s: reading SR failed\n", __func__); + return ret; + } + + return 0; +} + +static int rv3029c2_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + return rv3029c2_i2c_set_time(to_i2c_client(dev), tm); +} + +static const struct rtc_class_ops rv3029c2_rtc_ops = { + .read_time = rv3029c2_rtc_read_time, + .set_time = rv3029c2_rtc_set_time, + .read_alarm = rv3029c2_rtc_read_alarm, + .set_alarm = rv3029c2_rtc_set_alarm, +}; + +static struct i2c_device_id rv3029c2_id[] = { + { "rv3029c2", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rv3029c2_id); + +static int __devinit +rv3029c2_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct rtc_device *rtc; + int rc = 0; + u8 buf[1]; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_EMUL)) + return -ENODEV; + + rtc = rtc_device_register(client->name, + &client->dev, &rv3029c2_rtc_ops, + THIS_MODULE); + + if (IS_ERR(rtc)) + return PTR_ERR(rtc); + + i2c_set_clientdata(client, rtc); + + rc = rv3029c2_i2c_get_sr(client, buf); + if (rc < 0) { + dev_err(&client->dev, "reading status failed\n"); + goto exit_unregister; + } + + return 0; + +exit_unregister: + rtc_device_unregister(rtc); + + return rc; +} + +static int __devexit rv3029c2_remove(struct i2c_client *client) +{ + struct rtc_device *rtc = i2c_get_clientdata(client); + + rtc_device_unregister(rtc); + + return 0; +} + +static struct i2c_driver rv3029c2_driver = { + .driver = { + .name = "rtc-rv3029c2", + }, + .probe = rv3029c2_probe, + .remove = __devexit_p(rv3029c2_remove), + .id_table = rv3029c2_id, +}; + +static int __init rv3029c2_init(void) +{ + return i2c_add_driver(&rv3029c2_driver); +} + +static void __exit rv3029c2_exit(void) +{ + i2c_del_driver(&rv3029c2_driver); +} + +module_init(rv3029c2_init); +module_exit(rv3029c2_exit); + +MODULE_AUTHOR("Gregory Hermant <gregory.hermant@calao-systems.com>"); +MODULE_DESCRIPTION("Micro Crystal RV3029C2 RTC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-spear.c b/drivers/rtc/rtc-spear.c new file mode 100644 index 000000000000..893bac2bb21b --- /dev/null +++ b/drivers/rtc/rtc-spear.c @@ -0,0 +1,534 @@ +/* + * drivers/rtc/rtc-spear.c + * + * Copyright (C) 2010 ST Microelectronics + * Rajeev Kumar<rajeev-dlh.kumar@st.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/bcd.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +/* RTC registers */ +#define TIME_REG 0x00 +#define DATE_REG 0x04 +#define ALARM_TIME_REG 0x08 +#define ALARM_DATE_REG 0x0C +#define CTRL_REG 0x10 +#define STATUS_REG 0x14 + +/* TIME_REG & ALARM_TIME_REG */ +#define SECONDS_UNITS (0xf<<0) /* seconds units position */ +#define SECONDS_TENS (0x7<<4) /* seconds tens position */ +#define MINUTES_UNITS (0xf<<8) /* minutes units position */ +#define MINUTES_TENS (0x7<<12) /* minutes tens position */ +#define HOURS_UNITS (0xf<<16) /* hours units position */ +#define HOURS_TENS (0x3<<20) /* hours tens position */ + +/* DATE_REG & ALARM_DATE_REG */ +#define DAYS_UNITS (0xf<<0) /* days units position */ +#define DAYS_TENS (0x3<<4) /* days tens position */ +#define MONTHS_UNITS (0xf<<8) /* months units position */ +#define MONTHS_TENS (0x1<<12) /* months tens position */ +#define YEARS_UNITS (0xf<<16) /* years units position */ +#define YEARS_TENS (0xf<<20) /* years tens position */ +#define YEARS_HUNDREDS (0xf<<24) /* years hundereds position */ +#define YEARS_MILLENIUMS (0xf<<28) /* years millenium position */ + +/* MASK SHIFT TIME_REG & ALARM_TIME_REG*/ +#define SECOND_SHIFT 0x00 /* seconds units */ +#define MINUTE_SHIFT 0x08 /* minutes units position */ +#define HOUR_SHIFT 0x10 /* hours units position */ +#define MDAY_SHIFT 0x00 /* Month day shift */ +#define MONTH_SHIFT 0x08 /* Month shift */ +#define YEAR_SHIFT 0x10 /* Year shift */ + +#define SECOND_MASK 0x7F +#define MIN_MASK 0x7F +#define HOUR_MASK 0x3F +#define DAY_MASK 0x3F +#define MONTH_MASK 0x7F +#define YEAR_MASK 0xFFFF + +/* date reg equal to time reg, for debug only */ +#define TIME_BYP (1<<9) +#define INT_ENABLE (1<<31) /* interrupt enable */ + +/* STATUS_REG */ +#define CLK_UNCONNECTED (1<<0) +#define PEND_WR_TIME (1<<2) +#define PEND_WR_DATE (1<<3) +#define LOST_WR_TIME (1<<4) +#define LOST_WR_DATE (1<<5) +#define RTC_INT_MASK (1<<31) +#define STATUS_BUSY (PEND_WR_TIME | PEND_WR_DATE) +#define STATUS_FAIL (LOST_WR_TIME | LOST_WR_DATE) + +struct spear_rtc_config { + struct clk *clk; + spinlock_t lock; + void __iomem *ioaddr; +}; + +static inline void spear_rtc_clear_interrupt(struct spear_rtc_config *config) +{ + unsigned int val; + unsigned long flags; + + spin_lock_irqsave(&config->lock, flags); + val = readl(config->ioaddr + STATUS_REG); + val |= RTC_INT_MASK; + writel(val, config->ioaddr + STATUS_REG); + spin_unlock_irqrestore(&config->lock, flags); +} + +static inline void spear_rtc_enable_interrupt(struct spear_rtc_config *config) +{ + unsigned int val; + + val = readl(config->ioaddr + CTRL_REG); + if (!(val & INT_ENABLE)) { + spear_rtc_clear_interrupt(config); + val |= INT_ENABLE; + writel(val, config->ioaddr + CTRL_REG); + } +} + +static inline void spear_rtc_disable_interrupt(struct spear_rtc_config *config) +{ + unsigned int val; + + val = readl(config->ioaddr + CTRL_REG); + if (val & INT_ENABLE) { + val &= ~INT_ENABLE; + writel(val, config->ioaddr + CTRL_REG); + } +} + +static inline int is_write_complete(struct spear_rtc_config *config) +{ + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&config->lock, flags); + if ((readl(config->ioaddr + STATUS_REG)) & STATUS_FAIL) + ret = -EIO; + spin_unlock_irqrestore(&config->lock, flags); + + return ret; +} + +static void rtc_wait_not_busy(struct spear_rtc_config *config) +{ + int status, count = 0; + unsigned long flags; + + /* Assuming BUSY may stay active for 80 msec) */ + for (count = 0; count < 80; count++) { + spin_lock_irqsave(&config->lock, flags); + status = readl(config->ioaddr + STATUS_REG); + spin_unlock_irqrestore(&config->lock, flags); + if ((status & STATUS_BUSY) == 0) + break; + /* check status busy, after each msec */ + msleep(1); + } +} + +static irqreturn_t spear_rtc_irq(int irq, void *dev_id) +{ + struct rtc_device *rtc = (struct rtc_device *)dev_id; + struct spear_rtc_config *config = dev_get_drvdata(&rtc->dev); + unsigned long flags, events = 0; + unsigned int irq_data; + + spin_lock_irqsave(&config->lock, flags); + irq_data = readl(config->ioaddr + STATUS_REG); + spin_unlock_irqrestore(&config->lock, flags); + + if ((irq_data & RTC_INT_MASK)) { + spear_rtc_clear_interrupt(config); + events = RTC_IRQF | RTC_AF; + rtc_update_irq(rtc, 1, events); + return IRQ_HANDLED; + } else + return IRQ_NONE; + +} + +static int tm2bcd(struct rtc_time *tm) +{ + if (rtc_valid_tm(tm) != 0) + return -EINVAL; + tm->tm_sec = bin2bcd(tm->tm_sec); + tm->tm_min = bin2bcd(tm->tm_min); + tm->tm_hour = bin2bcd(tm->tm_hour); + tm->tm_mday = bin2bcd(tm->tm_mday); + tm->tm_mon = bin2bcd(tm->tm_mon + 1); + tm->tm_year = bin2bcd(tm->tm_year); + + return 0; +} + +static void bcd2tm(struct rtc_time *tm) +{ + tm->tm_sec = bcd2bin(tm->tm_sec); + tm->tm_min = bcd2bin(tm->tm_min); + tm->tm_hour = bcd2bin(tm->tm_hour); + tm->tm_mday = bcd2bin(tm->tm_mday); + tm->tm_mon = bcd2bin(tm->tm_mon) - 1; + /* epoch == 1900 */ + tm->tm_year = bcd2bin(tm->tm_year); +} + +/* + * spear_rtc_read_time - set the time + * @dev: rtc device in use + * @tm: holds date and time + * + * This function read time and date. On success it will return 0 + * otherwise -ve error is returned. + */ +static int spear_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_device *rtc = platform_get_drvdata(pdev); + struct spear_rtc_config *config = dev_get_drvdata(&rtc->dev); + unsigned int time, date; + + /* we don't report wday/yday/isdst ... */ + rtc_wait_not_busy(config); + + time = readl(config->ioaddr + TIME_REG); + date = readl(config->ioaddr + DATE_REG); + tm->tm_sec = (time >> SECOND_SHIFT) & SECOND_MASK; + tm->tm_min = (time >> MINUTE_SHIFT) & MIN_MASK; + tm->tm_hour = (time >> HOUR_SHIFT) & HOUR_MASK; + tm->tm_mday = (date >> MDAY_SHIFT) & DAY_MASK; + tm->tm_mon = (date >> MONTH_SHIFT) & MONTH_MASK; + tm->tm_year = (date >> YEAR_SHIFT) & YEAR_MASK; + + bcd2tm(tm); + return 0; +} + +/* + * spear_rtc_set_time - set the time + * @dev: rtc device in use + * @tm: holds date and time + * + * This function set time and date. On success it will return 0 + * otherwise -ve error is returned. + */ +static int spear_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_device *rtc = platform_get_drvdata(pdev); + struct spear_rtc_config *config = dev_get_drvdata(&rtc->dev); + unsigned int time, date, err = 0; + + if (tm2bcd(tm) < 0) + return -EINVAL; + + rtc_wait_not_busy(config); + time = (tm->tm_sec << SECOND_SHIFT) | (tm->tm_min << MINUTE_SHIFT) | + (tm->tm_hour << HOUR_SHIFT); + date = (tm->tm_mday << MDAY_SHIFT) | (tm->tm_mon << MONTH_SHIFT) | + (tm->tm_year << YEAR_SHIFT); + writel(time, config->ioaddr + TIME_REG); + writel(date, config->ioaddr + DATE_REG); + err = is_write_complete(config); + if (err < 0) + return err; + + return 0; +} + +/* + * spear_rtc_read_alarm - read the alarm time + * @dev: rtc device in use + * @alm: holds alarm date and time + * + * This function read alarm time and date. On success it will return 0 + * otherwise -ve error is returned. + */ +static int spear_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_device *rtc = platform_get_drvdata(pdev); + struct spear_rtc_config *config = dev_get_drvdata(&rtc->dev); + unsigned int time, date; + + rtc_wait_not_busy(config); + + time = readl(config->ioaddr + ALARM_TIME_REG); + date = readl(config->ioaddr + ALARM_DATE_REG); + alm->time.tm_sec = (time >> SECOND_SHIFT) & SECOND_MASK; + alm->time.tm_min = (time >> MINUTE_SHIFT) & MIN_MASK; + alm->time.tm_hour = (time >> HOUR_SHIFT) & HOUR_MASK; + alm->time.tm_mday = (date >> MDAY_SHIFT) & DAY_MASK; + alm->time.tm_mon = (date >> MONTH_SHIFT) & MONTH_MASK; + alm->time.tm_year = (date >> YEAR_SHIFT) & YEAR_MASK; + + bcd2tm(&alm->time); + alm->enabled = readl(config->ioaddr + CTRL_REG) & INT_ENABLE; + + return 0; +} + +/* + * spear_rtc_set_alarm - set the alarm time + * @dev: rtc device in use + * @alm: holds alarm date and time + * + * This function set alarm time and date. On success it will return 0 + * otherwise -ve error is returned. + */ +static int spear_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_device *rtc = platform_get_drvdata(pdev); + struct spear_rtc_config *config = dev_get_drvdata(&rtc->dev); + unsigned int time, date, err = 0; + + if (tm2bcd(&alm->time) < 0) + return -EINVAL; + + rtc_wait_not_busy(config); + + time = (alm->time.tm_sec << SECOND_SHIFT) | (alm->time.tm_min << + MINUTE_SHIFT) | (alm->time.tm_hour << HOUR_SHIFT); + date = (alm->time.tm_mday << MDAY_SHIFT) | (alm->time.tm_mon << + MONTH_SHIFT) | (alm->time.tm_year << YEAR_SHIFT); + + writel(time, config->ioaddr + ALARM_TIME_REG); + writel(date, config->ioaddr + ALARM_DATE_REG); + err = is_write_complete(config); + if (err < 0) + return err; + + if (alm->enabled) + spear_rtc_enable_interrupt(config); + else + spear_rtc_disable_interrupt(config); + + return 0; +} +static struct rtc_class_ops spear_rtc_ops = { + .read_time = spear_rtc_read_time, + .set_time = spear_rtc_set_time, + .read_alarm = spear_rtc_read_alarm, + .set_alarm = spear_rtc_set_alarm, +}; + +static int __devinit spear_rtc_probe(struct platform_device *pdev) +{ + struct resource *res; + struct rtc_device *rtc; + struct spear_rtc_config *config; + unsigned int status = 0; + int irq; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no resource defined\n"); + return -EBUSY; + } + if (!request_mem_region(res->start, resource_size(res), pdev->name)) { + dev_err(&pdev->dev, "rtc region already claimed\n"); + return -EBUSY; + } + + config = kzalloc(sizeof(*config), GFP_KERNEL); + if (!config) { + dev_err(&pdev->dev, "out of memory\n"); + status = -ENOMEM; + goto err_release_region; + } + + config->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(config->clk)) { + status = PTR_ERR(config->clk); + goto err_kfree; + } + + status = clk_enable(config->clk); + if (status < 0) + goto err_clk_put; + + config->ioaddr = ioremap(res->start, resource_size(res)); + if (!config->ioaddr) { + dev_err(&pdev->dev, "ioremap fail\n"); + status = -ENOMEM; + goto err_disable_clock; + } + + spin_lock_init(&config->lock); + + rtc = rtc_device_register(pdev->name, &pdev->dev, &spear_rtc_ops, + THIS_MODULE); + if (IS_ERR(rtc)) { + dev_err(&pdev->dev, "can't register RTC device, err %ld\n", + PTR_ERR(rtc)); + status = PTR_ERR(rtc); + goto err_iounmap; + } + + platform_set_drvdata(pdev, rtc); + dev_set_drvdata(&rtc->dev, config); + + /* alarm irqs */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no update irq?\n"); + status = irq; + goto err_clear_platdata; + } + + status = request_irq(irq, spear_rtc_irq, 0, pdev->name, rtc); + if (status) { + dev_err(&pdev->dev, "Alarm interrupt IRQ%d already \ + claimed\n", irq); + goto err_clear_platdata; + } + + if (!device_can_wakeup(&pdev->dev)) + device_init_wakeup(&pdev->dev, 1); + + return 0; + +err_clear_platdata: + platform_set_drvdata(pdev, NULL); + dev_set_drvdata(&rtc->dev, NULL); + rtc_device_unregister(rtc); +err_iounmap: + iounmap(config->ioaddr); +err_disable_clock: + clk_disable(config->clk); +err_clk_put: + clk_put(config->clk); +err_kfree: + kfree(config); +err_release_region: + release_mem_region(res->start, resource_size(res)); + + return status; +} + +static int __devexit spear_rtc_remove(struct platform_device *pdev) +{ + struct rtc_device *rtc = platform_get_drvdata(pdev); + struct spear_rtc_config *config = dev_get_drvdata(&rtc->dev); + int irq; + struct resource *res; + + /* leave rtc running, but disable irqs */ + spear_rtc_disable_interrupt(config); + device_init_wakeup(&pdev->dev, 0); + irq = platform_get_irq(pdev, 0); + if (irq) + free_irq(irq, pdev); + clk_disable(config->clk); + clk_put(config->clk); + iounmap(config->ioaddr); + kfree(config); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) + release_mem_region(res->start, resource_size(res)); + platform_set_drvdata(pdev, NULL); + dev_set_drvdata(&rtc->dev, NULL); + rtc_device_unregister(rtc); + + return 0; +} + +#ifdef CONFIG_PM + +static int spear_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct rtc_device *rtc = platform_get_drvdata(pdev); + struct spear_rtc_config *config = dev_get_drvdata(&rtc->dev); + int irq; + + irq = platform_get_irq(pdev, 0); + if (device_may_wakeup(&pdev->dev)) + enable_irq_wake(irq); + else { + spear_rtc_disable_interrupt(config); + clk_disable(config->clk); + } + + return 0; +} + +static int spear_rtc_resume(struct platform_device *pdev) +{ + struct rtc_device *rtc = platform_get_drvdata(pdev); + struct spear_rtc_config *config = dev_get_drvdata(&rtc->dev); + int irq; + + irq = platform_get_irq(pdev, 0); + + if (device_may_wakeup(&pdev->dev)) + disable_irq_wake(irq); + else { + clk_enable(config->clk); + spear_rtc_enable_interrupt(config); + } + + return 0; +} + +#else +#define spear_rtc_suspend NULL +#define spear_rtc_resume NULL +#endif + +static void spear_rtc_shutdown(struct platform_device *pdev) +{ + struct rtc_device *rtc = platform_get_drvdata(pdev); + struct spear_rtc_config *config = dev_get_drvdata(&rtc->dev); + + spear_rtc_disable_interrupt(config); + clk_disable(config->clk); +} + +static struct platform_driver spear_rtc_driver = { + .probe = spear_rtc_probe, + .remove = __devexit_p(spear_rtc_remove), + .suspend = spear_rtc_suspend, + .resume = spear_rtc_resume, + .shutdown = spear_rtc_shutdown, + .driver = { + .name = "rtc-spear", + }, +}; + +static int __init rtc_init(void) +{ + return platform_driver_register(&spear_rtc_driver); +} +module_init(rtc_init); + +static void __exit rtc_exit(void) +{ + platform_driver_unregister(&spear_rtc_driver); +} +module_exit(rtc_exit); + +MODULE_ALIAS("platform:rtc-spear"); +MODULE_AUTHOR("Rajeev Kumar <rajeev-dlh.kumar@st.com>"); +MODULE_DESCRIPTION("ST SPEAr Realtime Clock Driver (RTC)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-tile.c b/drivers/rtc/rtc-tile.c new file mode 100644 index 000000000000..eb65dafee66e --- /dev/null +++ b/drivers/rtc/rtc-tile.c @@ -0,0 +1,162 @@ +/* + * Copyright 2011 Tilera Corporation. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for + * more details. + * + * Tilera-specific RTC driver. + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/rtc.h> +#include <linux/platform_device.h> + +/* Platform device pointer. */ +static struct platform_device *tile_rtc_platform_device; + +/* + * RTC read routine. Gets time info from RTC chip via hypervisor syscall. + */ +static int read_rtc_time(struct device *dev, struct rtc_time *tm) +{ + HV_RTCTime hvtm = hv_get_rtc(); + + tm->tm_sec = hvtm.tm_sec; + tm->tm_min = hvtm.tm_min; + tm->tm_hour = hvtm.tm_hour; + tm->tm_mday = hvtm.tm_mday; + tm->tm_mon = hvtm.tm_mon; + tm->tm_year = hvtm.tm_year; + tm->tm_wday = 0; + tm->tm_yday = 0; + tm->tm_isdst = 0; + + if (rtc_valid_tm(tm) < 0) + dev_warn(dev, "Read invalid date/time from RTC\n"); + + return 0; +} + +/* + * RTC write routine. Sends time info to hypervisor via syscall, to be + * written to RTC chip. + */ +static int set_rtc_time(struct device *dev, struct rtc_time *tm) +{ + HV_RTCTime hvtm; + + hvtm.tm_sec = tm->tm_sec; + hvtm.tm_min = tm->tm_min; + hvtm.tm_hour = tm->tm_hour; + hvtm.tm_mday = tm->tm_mday; + hvtm.tm_mon = tm->tm_mon; + hvtm.tm_year = tm->tm_year; + + hv_set_rtc(hvtm); + + return 0; +} + +/* + * RTC read/write ops. + */ +static const struct rtc_class_ops tile_rtc_ops = { + .read_time = read_rtc_time, + .set_time = set_rtc_time, +}; + +/* + * Device probe routine. + */ +static int __devinit tile_rtc_probe(struct platform_device *dev) +{ + struct rtc_device *rtc; + + rtc = rtc_device_register("tile", + &dev->dev, &tile_rtc_ops, THIS_MODULE); + + if (IS_ERR(rtc)) + return PTR_ERR(rtc); + + platform_set_drvdata(dev, rtc); + + return 0; +} + +/* + * Device cleanup routine. + */ +static int __devexit tile_rtc_remove(struct platform_device *dev) +{ + struct rtc_device *rtc = platform_get_drvdata(dev); + + if (rtc) + rtc_device_unregister(rtc); + + platform_set_drvdata(dev, NULL); + + return 0; +} + +static struct platform_driver tile_rtc_platform_driver = { + .driver = { + .name = "rtc-tile", + .owner = THIS_MODULE, + }, + .probe = tile_rtc_probe, + .remove = __devexit_p(tile_rtc_remove), +}; + +/* + * Driver init routine. + */ +static int __init tile_rtc_driver_init(void) +{ + int err; + + err = platform_driver_register(&tile_rtc_platform_driver); + if (err) + return err; + + tile_rtc_platform_device = platform_device_alloc("rtc-tile", 0); + if (tile_rtc_platform_device == NULL) { + err = -ENOMEM; + goto exit_driver_unregister; + } + + err = platform_device_add(tile_rtc_platform_device); + if (err) + goto exit_device_put; + + return 0; + +exit_device_put: + platform_device_put(tile_rtc_platform_device); + +exit_driver_unregister: + platform_driver_unregister(&tile_rtc_platform_driver); + return err; +} + +/* + * Driver cleanup routine. + */ +static void __exit tile_rtc_driver_exit(void) +{ + platform_driver_unregister(&tile_rtc_platform_driver); +} + +module_init(tile_rtc_driver_init); +module_exit(tile_rtc_driver_exit); + +MODULE_DESCRIPTION("Tilera-specific Real Time Clock Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rtc-tile"); diff --git a/drivers/rtc/rtc-vt8500.c b/drivers/rtc/rtc-vt8500.c new file mode 100644 index 000000000000..b8bc862903ae --- /dev/null +++ b/drivers/rtc/rtc-vt8500.c @@ -0,0 +1,366 @@ +/* + * drivers/rtc/rtc-vt8500.c + * + * Copyright (C) 2010 Alexey Charkov <alchark@gmail.com> + * + * Based on rtc-pxa.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/rtc.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/bcd.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +/* + * Register definitions + */ +#define VT8500_RTC_TS 0x00 /* Time set */ +#define VT8500_RTC_DS 0x04 /* Date set */ +#define VT8500_RTC_AS 0x08 /* Alarm set */ +#define VT8500_RTC_CR 0x0c /* Control */ +#define VT8500_RTC_TR 0x10 /* Time read */ +#define VT8500_RTC_DR 0x14 /* Date read */ +#define VT8500_RTC_WS 0x18 /* Write status */ +#define VT8500_RTC_CL 0x20 /* Calibration */ +#define VT8500_RTC_IS 0x24 /* Interrupt status */ +#define VT8500_RTC_ST 0x28 /* Status */ + +#define INVALID_TIME_BIT (1 << 31) + +#define DATE_CENTURY_S 19 +#define DATE_YEAR_S 11 +#define DATE_YEAR_MASK (0xff << DATE_YEAR_S) +#define DATE_MONTH_S 6 +#define DATE_MONTH_MASK (0x1f << DATE_MONTH_S) +#define DATE_DAY_MASK 0x3f + +#define TIME_DOW_S 20 +#define TIME_DOW_MASK (0x07 << TIME_DOW_S) +#define TIME_HOUR_S 14 +#define TIME_HOUR_MASK (0x3f << TIME_HOUR_S) +#define TIME_MIN_S 7 +#define TIME_MIN_MASK (0x7f << TIME_MIN_S) +#define TIME_SEC_MASK 0x7f + +#define ALARM_DAY_S 20 +#define ALARM_DAY_MASK (0x3f << ALARM_DAY_S) + +#define ALARM_DAY_BIT (1 << 29) +#define ALARM_HOUR_BIT (1 << 28) +#define ALARM_MIN_BIT (1 << 27) +#define ALARM_SEC_BIT (1 << 26) + +#define ALARM_ENABLE_MASK (ALARM_DAY_BIT \ + | ALARM_HOUR_BIT \ + | ALARM_MIN_BIT \ + | ALARM_SEC_BIT) + +#define VT8500_RTC_CR_ENABLE (1 << 0) /* Enable RTC */ +#define VT8500_RTC_CR_24H (1 << 1) /* 24h time format */ +#define VT8500_RTC_CR_SM_ENABLE (1 << 2) /* Enable periodic irqs */ +#define VT8500_RTC_CR_SM_SEC (1 << 3) /* 0: 1Hz/60, 1: 1Hz */ +#define VT8500_RTC_CR_CALIB (1 << 4) /* Enable calibration */ + +struct vt8500_rtc { + void __iomem *regbase; + struct resource *res; + int irq_alarm; + int irq_hz; + struct rtc_device *rtc; + spinlock_t lock; /* Protects this structure */ +}; + +static irqreturn_t vt8500_rtc_irq(int irq, void *dev_id) +{ + struct vt8500_rtc *vt8500_rtc = dev_id; + u32 isr; + unsigned long events = 0; + + spin_lock(&vt8500_rtc->lock); + + /* clear interrupt sources */ + isr = readl(vt8500_rtc->regbase + VT8500_RTC_IS); + writel(isr, vt8500_rtc->regbase + VT8500_RTC_IS); + + spin_unlock(&vt8500_rtc->lock); + + if (isr & 1) + events |= RTC_AF | RTC_IRQF; + + /* Only second/minute interrupts are supported */ + if (isr & 2) + events |= RTC_UF | RTC_IRQF; + + rtc_update_irq(vt8500_rtc->rtc, 1, events); + + return IRQ_HANDLED; +} + +static int vt8500_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct vt8500_rtc *vt8500_rtc = dev_get_drvdata(dev); + u32 date, time; + + date = readl(vt8500_rtc->regbase + VT8500_RTC_DR); + time = readl(vt8500_rtc->regbase + VT8500_RTC_TR); + + tm->tm_sec = bcd2bin(time & TIME_SEC_MASK); + tm->tm_min = bcd2bin((time & TIME_MIN_MASK) >> TIME_MIN_S); + tm->tm_hour = bcd2bin((time & TIME_HOUR_MASK) >> TIME_HOUR_S); + tm->tm_mday = bcd2bin(date & DATE_DAY_MASK); + tm->tm_mon = bcd2bin((date & DATE_MONTH_MASK) >> DATE_MONTH_S); + tm->tm_year = bcd2bin((date & DATE_YEAR_MASK) >> DATE_YEAR_S) + + ((date >> DATE_CENTURY_S) & 1 ? 200 : 100); + tm->tm_wday = (time & TIME_DOW_MASK) >> TIME_DOW_S; + + return 0; +} + +static int vt8500_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct vt8500_rtc *vt8500_rtc = dev_get_drvdata(dev); + + if (tm->tm_year < 100) { + dev_warn(dev, "Only years 2000-2199 are supported by the " + "hardware!\n"); + return -EINVAL; + } + + writel((bin2bcd(tm->tm_year - 100) << DATE_YEAR_S) + | (bin2bcd(tm->tm_mon) << DATE_MONTH_S) + | (bin2bcd(tm->tm_mday)), + vt8500_rtc->regbase + VT8500_RTC_DS); + writel((bin2bcd(tm->tm_wday) << TIME_DOW_S) + | (bin2bcd(tm->tm_hour) << TIME_HOUR_S) + | (bin2bcd(tm->tm_min) << TIME_MIN_S) + | (bin2bcd(tm->tm_sec)), + vt8500_rtc->regbase + VT8500_RTC_TS); + + return 0; +} + +static int vt8500_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct vt8500_rtc *vt8500_rtc = dev_get_drvdata(dev); + u32 isr, alarm; + + alarm = readl(vt8500_rtc->regbase + VT8500_RTC_AS); + isr = readl(vt8500_rtc->regbase + VT8500_RTC_IS); + + alrm->time.tm_mday = bcd2bin((alarm & ALARM_DAY_MASK) >> ALARM_DAY_S); + alrm->time.tm_hour = bcd2bin((alarm & TIME_HOUR_MASK) >> TIME_HOUR_S); + alrm->time.tm_min = bcd2bin((alarm & TIME_MIN_MASK) >> TIME_MIN_S); + alrm->time.tm_sec = bcd2bin((alarm & TIME_SEC_MASK)); + + alrm->enabled = (alarm & ALARM_ENABLE_MASK) ? 1 : 0; + + alrm->pending = (isr & 1) ? 1 : 0; + return rtc_valid_tm(&alrm->time); +} + +static int vt8500_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct vt8500_rtc *vt8500_rtc = dev_get_drvdata(dev); + + writel((alrm->enabled ? ALARM_ENABLE_MASK : 0) + | (bin2bcd(alrm->time.tm_mday) << ALARM_DAY_S) + | (bin2bcd(alrm->time.tm_hour) << TIME_HOUR_S) + | (bin2bcd(alrm->time.tm_min) << TIME_MIN_S) + | (bin2bcd(alrm->time.tm_sec)), + vt8500_rtc->regbase + VT8500_RTC_AS); + + return 0; +} + +static int vt8500_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct vt8500_rtc *vt8500_rtc = dev_get_drvdata(dev); + unsigned long tmp = readl(vt8500_rtc->regbase + VT8500_RTC_AS); + + if (enabled) + tmp |= ALARM_ENABLE_MASK; + else + tmp &= ~ALARM_ENABLE_MASK; + + writel(tmp, vt8500_rtc->regbase + VT8500_RTC_AS); + return 0; +} + +static int vt8500_update_irq_enable(struct device *dev, unsigned int enabled) +{ + struct vt8500_rtc *vt8500_rtc = dev_get_drvdata(dev); + unsigned long tmp = readl(vt8500_rtc->regbase + VT8500_RTC_CR); + + if (enabled) + tmp |= VT8500_RTC_CR_SM_SEC | VT8500_RTC_CR_SM_ENABLE; + else + tmp &= ~VT8500_RTC_CR_SM_ENABLE; + + writel(tmp, vt8500_rtc->regbase + VT8500_RTC_CR); + return 0; +} + +static const struct rtc_class_ops vt8500_rtc_ops = { + .read_time = vt8500_rtc_read_time, + .set_time = vt8500_rtc_set_time, + .read_alarm = vt8500_rtc_read_alarm, + .set_alarm = vt8500_rtc_set_alarm, + .alarm_irq_enable = vt8500_alarm_irq_enable, + .update_irq_enable = vt8500_update_irq_enable, +}; + +static int __devinit vt8500_rtc_probe(struct platform_device *pdev) +{ + struct vt8500_rtc *vt8500_rtc; + int ret; + + vt8500_rtc = kzalloc(sizeof(struct vt8500_rtc), GFP_KERNEL); + if (!vt8500_rtc) + return -ENOMEM; + + spin_lock_init(&vt8500_rtc->lock); + platform_set_drvdata(pdev, vt8500_rtc); + + vt8500_rtc->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!vt8500_rtc->res) { + dev_err(&pdev->dev, "No I/O memory resource defined\n"); + ret = -ENXIO; + goto err_free; + } + + vt8500_rtc->irq_alarm = platform_get_irq(pdev, 0); + if (vt8500_rtc->irq_alarm < 0) { + dev_err(&pdev->dev, "No alarm IRQ resource defined\n"); + ret = -ENXIO; + goto err_free; + } + + vt8500_rtc->irq_hz = platform_get_irq(pdev, 1); + if (vt8500_rtc->irq_hz < 0) { + dev_err(&pdev->dev, "No 1Hz IRQ resource defined\n"); + ret = -ENXIO; + goto err_free; + } + + vt8500_rtc->res = request_mem_region(vt8500_rtc->res->start, + resource_size(vt8500_rtc->res), + "vt8500-rtc"); + if (vt8500_rtc->res == NULL) { + dev_err(&pdev->dev, "failed to request I/O memory\n"); + ret = -EBUSY; + goto err_free; + } + + vt8500_rtc->regbase = ioremap(vt8500_rtc->res->start, + resource_size(vt8500_rtc->res)); + if (!vt8500_rtc->regbase) { + dev_err(&pdev->dev, "Unable to map RTC I/O memory\n"); + ret = -EBUSY; + goto err_release; + } + + /* Enable the second/minute interrupt generation and enable RTC */ + writel(VT8500_RTC_CR_ENABLE | VT8500_RTC_CR_24H + | VT8500_RTC_CR_SM_ENABLE | VT8500_RTC_CR_SM_SEC, + vt8500_rtc->regbase + VT8500_RTC_CR); + + vt8500_rtc->rtc = rtc_device_register("vt8500-rtc", &pdev->dev, + &vt8500_rtc_ops, THIS_MODULE); + if (IS_ERR(vt8500_rtc->rtc)) { + ret = PTR_ERR(vt8500_rtc->rtc); + dev_err(&pdev->dev, + "Failed to register RTC device -> %d\n", ret); + goto err_unmap; + } + + ret = request_irq(vt8500_rtc->irq_hz, vt8500_rtc_irq, 0, + "rtc 1Hz", vt8500_rtc); + if (ret < 0) { + dev_err(&pdev->dev, "can't get irq %i, err %d\n", + vt8500_rtc->irq_hz, ret); + goto err_unreg; + } + + ret = request_irq(vt8500_rtc->irq_alarm, vt8500_rtc_irq, 0, + "rtc alarm", vt8500_rtc); + if (ret < 0) { + dev_err(&pdev->dev, "can't get irq %i, err %d\n", + vt8500_rtc->irq_alarm, ret); + goto err_free_hz; + } + + return 0; + +err_free_hz: + free_irq(vt8500_rtc->irq_hz, vt8500_rtc); +err_unreg: + rtc_device_unregister(vt8500_rtc->rtc); +err_unmap: + iounmap(vt8500_rtc->regbase); +err_release: + release_mem_region(vt8500_rtc->res->start, + resource_size(vt8500_rtc->res)); +err_free: + kfree(vt8500_rtc); + return ret; +} + +static int __devexit vt8500_rtc_remove(struct platform_device *pdev) +{ + struct vt8500_rtc *vt8500_rtc = platform_get_drvdata(pdev); + + free_irq(vt8500_rtc->irq_alarm, vt8500_rtc); + free_irq(vt8500_rtc->irq_hz, vt8500_rtc); + + rtc_device_unregister(vt8500_rtc->rtc); + + /* Disable alarm matching */ + writel(0, vt8500_rtc->regbase + VT8500_RTC_IS); + iounmap(vt8500_rtc->regbase); + release_mem_region(vt8500_rtc->res->start, + resource_size(vt8500_rtc->res)); + + kfree(vt8500_rtc); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver vt8500_rtc_driver = { + .probe = vt8500_rtc_probe, + .remove = __devexit_p(vt8500_rtc_remove), + .driver = { + .name = "vt8500-rtc", + .owner = THIS_MODULE, + }, +}; + +static int __init vt8500_rtc_init(void) +{ + return platform_driver_register(&vt8500_rtc_driver); +} +module_init(vt8500_rtc_init); + +static void __exit vt8500_rtc_exit(void) +{ + platform_driver_unregister(&vt8500_rtc_driver); +} +module_exit(vt8500_rtc_exit); + +MODULE_AUTHOR("Alexey Charkov <alchark@gmail.com>"); +MODULE_DESCRIPTION("VIA VT8500 SoC Realtime Clock Driver (RTC)"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:vt8500-rtc"); |