diff options
author | Linus Torvalds | 2014-01-23 18:49:36 -0800 |
---|---|---|
committer | Linus Torvalds | 2014-01-23 18:49:36 -0800 |
commit | 0ba3307a8ec35252f7b1e222e32889a6f3d9ceb3 (patch) | |
tree | 26126ed7a2080a706f0488c215549fc9f5f76a59 /drivers/rtc | |
parent | 903a9f77d1d00c8621bc37afd959ac45a4b3ebec (diff) | |
parent | cd2f43a1f7400a74a084094502f70df2e169c6e8 (diff) |
Merge tag 'drivers-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
Pull ARM driver updates from Olof Johansson:
"Updates of SoC-near drivers and other driver updates that makes more
sense to take through our tree.
The largest part of this is a conversion of device registration for
some renesas shmobile/sh devices over to use resources. This has
required coordination with the corresponding arch/sh changes, and
we've agreed to merge the arch/sh changes through our tree.
Added in this branch is support for Trusted Foundations secure
firmware, which is what is used on many of the commercial Nvidia Tegra
products that are in the market, including the Nvidia Shield. The
code is local to arch/arm at this time since it's uncertain whether it
will be shared with arm64 longer-term, if needed we will refactor
later.
A couple of new RTC drivers used on ARM boards, merged through our
tree on request by the RTC maintainer.
... plus a bunch of smaller updates across the board, gpio conversions
for davinci, etc"
* tag 'drivers-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (45 commits)
watchdog: davinci: rename platform driver to davinci-wdt
tty: serial: Limit msm_serial_hs driver to platforms that use it
mmc: msm_sdcc: Limit driver to platforms that use it
usb: phy: msm: Move mach dependent code to platform data
clk: versatile: fixup IM-PD1 clock implementation
clk: versatile: pass a name to ICST clock provider
ARM: integrator: pass parent IRQ to the SIC
irqchip: versatile FPGA: support cascaded interrupts from DT
gpio: davinci: don't create irq_domain in case of unbanked irqs
gpio: davinci: use chained_irq_enter/chained_irq_exit API
gpio: davinci: add OF support
gpio: davinci: remove unused variable intc_irq_num
gpio: davinci: convert to use irqdomain support.
gpio: introduce GPIO_DAVINCI kconfig option
gpio: davinci: get rid of DAVINCI_N_GPIO
gpio: davinci: use {readl|writel}_relaxed() instead of __raw_*
serial: sh-sci: Add OF support
serial: sh-sci: Add device tree bindings documentation
serial: sh-sci: Remove platform data mapbase and irqs fields
serial: sh-sci: Remove platform data scbrr_algo_id field
...
Diffstat (limited to 'drivers/rtc')
-rw-r--r-- | drivers/rtc/Kconfig | 18 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 2 | ||||
-rw-r--r-- | drivers/rtc/rtc-isl12057.c | 310 | ||||
-rw-r--r-- | drivers/rtc/rtc-sunxi.c | 523 |
4 files changed, 853 insertions, 0 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 007730222116..b1328a45b095 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -304,6 +304,17 @@ config RTC_DRV_ISL12022 This driver can also be built as a module. If so, the module will be called rtc-isl12022. +config RTC_DRV_ISL12057 + depends on I2C + select REGMAP_I2C + tristate "Intersil ISL12057" + help + If you say yes here you get support for the Intersil ISL12057 + I2C RTC chip. + + This driver can also be built as a module. If so, the module + will be called rtc-isl12057. + config RTC_DRV_X1205 tristate "Xicor/Intersil X1205" help @@ -1104,6 +1115,13 @@ config RTC_DRV_SUN4V If you say Y here you will get support for the Hypervisor based RTC on SUN4V systems. +config RTC_DRV_SUNXI + tristate "Allwinner sun4i/sun7i RTC" + depends on ARCH_SUNXI + help + If you say Y here you will get support for the RTC found on + Allwinner A10/A20. + config RTC_DRV_STARFIRE bool "Starfire RTC" depends on SPARC64 diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 27b4bd884066..c00741a0bf10 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_RTC_DRV_HID_SENSOR_TIME) += rtc-hid-sensor-time.o 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_ISL12057) += rtc-isl12057.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 @@ -117,6 +118,7 @@ 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_SUNXI) += rtc-sunxi.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 diff --git a/drivers/rtc/rtc-isl12057.c b/drivers/rtc/rtc-isl12057.c new file mode 100644 index 000000000000..7854a656628f --- /dev/null +++ b/drivers/rtc/rtc-isl12057.c @@ -0,0 +1,310 @@ +/* + * rtc-isl12057 - Driver for Intersil ISL12057 I2C Real Time Clock + * + * Copyright (C) 2013, Arnaud EBALARD <arno@natisbad.org> + * + * This work is largely based on Intersil ISL1208 driver developed by + * Hebert Valerio Riedel <hvr@gnu.org>. + * + * Detailed datasheet on which this development is based is available here: + * + * http://natisbad.org/NAS2/refs/ISL12057.pdf + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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/mutex.h> +#include <linux/rtc.h> +#include <linux/i2c.h> +#include <linux/bcd.h> +#include <linux/rtc.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regmap.h> + +#define DRV_NAME "rtc-isl12057" + +/* RTC section */ +#define ISL12057_REG_RTC_SC 0x00 /* Seconds */ +#define ISL12057_REG_RTC_MN 0x01 /* Minutes */ +#define ISL12057_REG_RTC_HR 0x02 /* Hours */ +#define ISL12057_REG_RTC_HR_PM BIT(5) /* AM/PM bit in 12h format */ +#define ISL12057_REG_RTC_HR_MIL BIT(6) /* 24h/12h format */ +#define ISL12057_REG_RTC_DW 0x03 /* Day of the Week */ +#define ISL12057_REG_RTC_DT 0x04 /* Date */ +#define ISL12057_REG_RTC_MO 0x05 /* Month */ +#define ISL12057_REG_RTC_YR 0x06 /* Year */ +#define ISL12057_RTC_SEC_LEN 7 + +/* Alarm 1 section */ +#define ISL12057_REG_A1_SC 0x07 /* Alarm 1 Seconds */ +#define ISL12057_REG_A1_MN 0x08 /* Alarm 1 Minutes */ +#define ISL12057_REG_A1_HR 0x09 /* Alarm 1 Hours */ +#define ISL12057_REG_A1_HR_PM BIT(5) /* AM/PM bit in 12h format */ +#define ISL12057_REG_A1_HR_MIL BIT(6) /* 24h/12h format */ +#define ISL12057_REG_A1_DWDT 0x0A /* Alarm 1 Date / Day of the week */ +#define ISL12057_REG_A1_DWDT_B BIT(6) /* DW / DT selection bit */ +#define ISL12057_A1_SEC_LEN 4 + +/* Alarm 2 section */ +#define ISL12057_REG_A2_MN 0x0B /* Alarm 2 Minutes */ +#define ISL12057_REG_A2_HR 0x0C /* Alarm 2 Hours */ +#define ISL12057_REG_A2_DWDT 0x0D /* Alarm 2 Date / Day of the week */ +#define ISL12057_A2_SEC_LEN 3 + +/* Control/Status registers */ +#define ISL12057_REG_INT 0x0E +#define ISL12057_REG_INT_A1IE BIT(0) /* Alarm 1 interrupt enable bit */ +#define ISL12057_REG_INT_A2IE BIT(1) /* Alarm 2 interrupt enable bit */ +#define ISL12057_REG_INT_INTCN BIT(2) /* Interrupt control enable bit */ +#define ISL12057_REG_INT_RS1 BIT(3) /* Freq out control bit 1 */ +#define ISL12057_REG_INT_RS2 BIT(4) /* Freq out control bit 2 */ +#define ISL12057_REG_INT_EOSC BIT(7) /* Oscillator enable bit */ + +#define ISL12057_REG_SR 0x0F +#define ISL12057_REG_SR_A1F BIT(0) /* Alarm 1 interrupt bit */ +#define ISL12057_REG_SR_A2F BIT(1) /* Alarm 2 interrupt bit */ +#define ISL12057_REG_SR_OSF BIT(7) /* Oscillator failure bit */ + +/* Register memory map length */ +#define ISL12057_MEM_MAP_LEN 0x10 + +struct isl12057_rtc_data { + struct regmap *regmap; + struct mutex lock; +}; + +static void isl12057_rtc_regs_to_tm(struct rtc_time *tm, u8 *regs) +{ + tm->tm_sec = bcd2bin(regs[ISL12057_REG_RTC_SC]); + tm->tm_min = bcd2bin(regs[ISL12057_REG_RTC_MN]); + + if (regs[ISL12057_REG_RTC_HR] & ISL12057_REG_RTC_HR_MIL) { /* AM/PM */ + tm->tm_hour = bcd2bin(regs[ISL12057_REG_RTC_HR] & 0x0f); + if (regs[ISL12057_REG_RTC_HR] & ISL12057_REG_RTC_HR_PM) + tm->tm_hour += 12; + } else { /* 24 hour mode */ + tm->tm_hour = bcd2bin(regs[ISL12057_REG_RTC_HR] & 0x3f); + } + + tm->tm_mday = bcd2bin(regs[ISL12057_REG_RTC_DT]); + tm->tm_wday = bcd2bin(regs[ISL12057_REG_RTC_DW]) - 1; /* starts at 1 */ + tm->tm_mon = bcd2bin(regs[ISL12057_REG_RTC_MO]) - 1; /* starts at 1 */ + tm->tm_year = bcd2bin(regs[ISL12057_REG_RTC_YR]) + 100; +} + +static int isl12057_rtc_tm_to_regs(u8 *regs, struct rtc_time *tm) +{ + /* + * The clock has an 8 bit wide bcd-coded register 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[ISL12057_REG_RTC_SC] = bin2bcd(tm->tm_sec); + regs[ISL12057_REG_RTC_MN] = bin2bcd(tm->tm_min); + regs[ISL12057_REG_RTC_HR] = bin2bcd(tm->tm_hour); /* 24-hour format */ + regs[ISL12057_REG_RTC_DT] = bin2bcd(tm->tm_mday); + regs[ISL12057_REG_RTC_MO] = bin2bcd(tm->tm_mon + 1); + regs[ISL12057_REG_RTC_YR] = bin2bcd(tm->tm_year - 100); + regs[ISL12057_REG_RTC_DW] = bin2bcd(tm->tm_wday + 1); + + return 0; +} + +/* + * Try and match register bits w/ fixed null values to see whether we + * are dealing with an ISL12057. Note: this function is called early + * during init and hence does need mutex protection. + */ +static int isl12057_i2c_validate_chip(struct regmap *regmap) +{ + u8 regs[ISL12057_MEM_MAP_LEN]; + static const u8 mask[ISL12057_MEM_MAP_LEN] = { 0x80, 0x80, 0x80, 0xf8, + 0xc0, 0x60, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x60, 0x7c }; + int ret, i; + + ret = regmap_bulk_read(regmap, 0, regs, ISL12057_MEM_MAP_LEN); + if (ret) + return ret; + + for (i = 0; i < ISL12057_MEM_MAP_LEN; ++i) { + if (regs[i] & mask[i]) /* check if bits are cleared */ + return -ENODEV; + } + + return 0; +} + +static int isl12057_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct isl12057_rtc_data *data = dev_get_drvdata(dev); + u8 regs[ISL12057_RTC_SEC_LEN]; + int ret; + + mutex_lock(&data->lock); + ret = regmap_bulk_read(data->regmap, ISL12057_REG_RTC_SC, regs, + ISL12057_RTC_SEC_LEN); + mutex_unlock(&data->lock); + + if (ret) { + dev_err(dev, "%s: RTC read failed\n", __func__); + return ret; + } + + isl12057_rtc_regs_to_tm(tm, regs); + + return rtc_valid_tm(tm); +} + +static int isl12057_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct isl12057_rtc_data *data = dev_get_drvdata(dev); + u8 regs[ISL12057_RTC_SEC_LEN]; + int ret; + + ret = isl12057_rtc_tm_to_regs(regs, tm); + if (ret) + return ret; + + mutex_lock(&data->lock); + ret = regmap_bulk_write(data->regmap, ISL12057_REG_RTC_SC, regs, + ISL12057_RTC_SEC_LEN); + mutex_unlock(&data->lock); + + if (ret) + dev_err(dev, "%s: RTC write failed\n", __func__); + + return ret; +} + +/* + * Check current RTC status and enable/disable what needs to be. Return 0 if + * everything went ok and a negative value upon error. Note: this function + * is called early during init and hence does need mutex protection. + */ +static int isl12057_check_rtc_status(struct device *dev, struct regmap *regmap) +{ + int ret; + + /* Enable oscillator if not already running */ + ret = regmap_update_bits(regmap, ISL12057_REG_INT, + ISL12057_REG_INT_EOSC, 0); + if (ret < 0) { + dev_err(dev, "Unable to enable oscillator\n"); + return ret; + } + + /* Clear oscillator failure bit if needed */ + ret = regmap_update_bits(regmap, ISL12057_REG_SR, + ISL12057_REG_SR_OSF, 0); + if (ret < 0) { + dev_err(dev, "Unable to clear oscillator failure bit\n"); + return ret; + } + + /* Clear alarm bit if needed */ + ret = regmap_update_bits(regmap, ISL12057_REG_SR, + ISL12057_REG_SR_A1F, 0); + if (ret < 0) { + dev_err(dev, "Unable to clear alarm bit\n"); + return ret; + } + + return 0; +} + +static const struct rtc_class_ops rtc_ops = { + .read_time = isl12057_rtc_read_time, + .set_time = isl12057_rtc_set_time, +}; + +static struct regmap_config isl12057_rtc_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int isl12057_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct isl12057_rtc_data *data; + struct rtc_device *rtc; + struct regmap *regmap; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK)) + return -ENODEV; + + regmap = devm_regmap_init_i2c(client, &isl12057_rtc_regmap_config); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(dev, "regmap allocation failed: %d\n", ret); + return ret; + } + + ret = isl12057_i2c_validate_chip(regmap); + if (ret) + return ret; + + ret = isl12057_check_rtc_status(dev, regmap); + if (ret) + return ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + mutex_init(&data->lock); + data->regmap = regmap; + dev_set_drvdata(dev, data); + + rtc = devm_rtc_device_register(dev, DRV_NAME, &rtc_ops, THIS_MODULE); + if (IS_ERR(rtc)) + return PTR_ERR(rtc); + + return 0; +} + +#ifdef CONFIG_OF +static struct of_device_id isl12057_dt_match[] = { + { .compatible = "isl,isl12057" }, + { }, +}; +#endif + +static const struct i2c_device_id isl12057_id[] = { + { "isl12057", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, isl12057_id); + +static struct i2c_driver isl12057_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(isl12057_dt_match), + }, + .probe = isl12057_probe, + .id_table = isl12057_id, +}; +module_i2c_driver(isl12057_driver); + +MODULE_AUTHOR("Arnaud EBALARD <arno@natisbad.org>"); +MODULE_DESCRIPTION("Intersil ISL12057 RTC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-sunxi.c b/drivers/rtc/rtc-sunxi.c new file mode 100644 index 000000000000..68a35284e5ad --- /dev/null +++ b/drivers/rtc/rtc-sunxi.c @@ -0,0 +1,523 @@ +/* + * An RTC driver for Allwinner A10/A20 + * + * Copyright (c) 2013, Carlo Caione <carlo.caione@gmail.com> + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/types.h> + +#define SUNXI_LOSC_CTRL 0x0000 +#define SUNXI_LOSC_CTRL_RTC_HMS_ACC BIT(8) +#define SUNXI_LOSC_CTRL_RTC_YMD_ACC BIT(7) + +#define SUNXI_RTC_YMD 0x0004 + +#define SUNXI_RTC_HMS 0x0008 + +#define SUNXI_ALRM_DHMS 0x000c + +#define SUNXI_ALRM_EN 0x0014 +#define SUNXI_ALRM_EN_CNT_EN BIT(8) + +#define SUNXI_ALRM_IRQ_EN 0x0018 +#define SUNXI_ALRM_IRQ_EN_CNT_IRQ_EN BIT(0) + +#define SUNXI_ALRM_IRQ_STA 0x001c +#define SUNXI_ALRM_IRQ_STA_CNT_IRQ_PEND BIT(0) + +#define SUNXI_MASK_DH 0x0000001f +#define SUNXI_MASK_SM 0x0000003f +#define SUNXI_MASK_M 0x0000000f +#define SUNXI_MASK_LY 0x00000001 +#define SUNXI_MASK_D 0x00000ffe +#define SUNXI_MASK_M 0x0000000f + +#define SUNXI_GET(x, mask, shift) (((x) & ((mask) << (shift))) \ + >> (shift)) + +#define SUNXI_SET(x, mask, shift) (((x) & (mask)) << (shift)) + +/* + * Get date values + */ +#define SUNXI_DATE_GET_DAY_VALUE(x) SUNXI_GET(x, SUNXI_MASK_DH, 0) +#define SUNXI_DATE_GET_MON_VALUE(x) SUNXI_GET(x, SUNXI_MASK_M, 8) +#define SUNXI_DATE_GET_YEAR_VALUE(x, mask) SUNXI_GET(x, mask, 16) + +/* + * Get time values + */ +#define SUNXI_TIME_GET_SEC_VALUE(x) SUNXI_GET(x, SUNXI_MASK_SM, 0) +#define SUNXI_TIME_GET_MIN_VALUE(x) SUNXI_GET(x, SUNXI_MASK_SM, 8) +#define SUNXI_TIME_GET_HOUR_VALUE(x) SUNXI_GET(x, SUNXI_MASK_DH, 16) + +/* + * Get alarm values + */ +#define SUNXI_ALRM_GET_SEC_VALUE(x) SUNXI_GET(x, SUNXI_MASK_SM, 0) +#define SUNXI_ALRM_GET_MIN_VALUE(x) SUNXI_GET(x, SUNXI_MASK_SM, 8) +#define SUNXI_ALRM_GET_HOUR_VALUE(x) SUNXI_GET(x, SUNXI_MASK_DH, 16) + +/* + * Set date values + */ +#define SUNXI_DATE_SET_DAY_VALUE(x) SUNXI_DATE_GET_DAY_VALUE(x) +#define SUNXI_DATE_SET_MON_VALUE(x) SUNXI_SET(x, SUNXI_MASK_M, 8) +#define SUNXI_DATE_SET_YEAR_VALUE(x, mask) SUNXI_SET(x, mask, 16) +#define SUNXI_LEAP_SET_VALUE(x, shift) SUNXI_SET(x, SUNXI_MASK_LY, shift) + +/* + * Set time values + */ +#define SUNXI_TIME_SET_SEC_VALUE(x) SUNXI_TIME_GET_SEC_VALUE(x) +#define SUNXI_TIME_SET_MIN_VALUE(x) SUNXI_SET(x, SUNXI_MASK_SM, 8) +#define SUNXI_TIME_SET_HOUR_VALUE(x) SUNXI_SET(x, SUNXI_MASK_DH, 16) + +/* + * Set alarm values + */ +#define SUNXI_ALRM_SET_SEC_VALUE(x) SUNXI_ALRM_GET_SEC_VALUE(x) +#define SUNXI_ALRM_SET_MIN_VALUE(x) SUNXI_SET(x, SUNXI_MASK_SM, 8) +#define SUNXI_ALRM_SET_HOUR_VALUE(x) SUNXI_SET(x, SUNXI_MASK_DH, 16) +#define SUNXI_ALRM_SET_DAY_VALUE(x) SUNXI_SET(x, SUNXI_MASK_D, 21) + +/* + * Time unit conversions + */ +#define SEC_IN_MIN 60 +#define SEC_IN_HOUR (60 * SEC_IN_MIN) +#define SEC_IN_DAY (24 * SEC_IN_HOUR) + +/* + * The year parameter passed to the driver is usually an offset relative to + * the year 1900. This macro is used to convert this offset to another one + * relative to the minimum year allowed by the hardware. + */ +#define SUNXI_YEAR_OFF(x) ((x)->min - 1900) + +/* + * min and max year are arbitrary set considering the limited range of the + * hardware register field + */ +struct sunxi_rtc_data_year { + unsigned int min; /* min year allowed */ + unsigned int max; /* max year allowed */ + unsigned int mask; /* mask for the year field */ + unsigned char leap_shift; /* bit shift to get the leap year */ +}; + +static struct sunxi_rtc_data_year data_year_param[] = { + [0] = { + .min = 2010, + .max = 2073, + .mask = 0x3f, + .leap_shift = 22, + }, + [1] = { + .min = 1970, + .max = 2225, + .mask = 0xff, + .leap_shift = 24, + }, +}; + +struct sunxi_rtc_dev { + struct rtc_device *rtc; + struct device *dev; + struct sunxi_rtc_data_year *data_year; + void __iomem *base; + int irq; +}; + +static irqreturn_t sunxi_rtc_alarmirq(int irq, void *id) +{ + struct sunxi_rtc_dev *chip = (struct sunxi_rtc_dev *) id; + u32 val; + + val = readl(chip->base + SUNXI_ALRM_IRQ_STA); + + if (val & SUNXI_ALRM_IRQ_STA_CNT_IRQ_PEND) { + val |= SUNXI_ALRM_IRQ_STA_CNT_IRQ_PEND; + writel(val, chip->base + SUNXI_ALRM_IRQ_STA); + + rtc_update_irq(chip->rtc, 1, RTC_AF | RTC_IRQF); + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static void sunxi_rtc_setaie(int to, struct sunxi_rtc_dev *chip) +{ + u32 alrm_val = 0; + u32 alrm_irq_val = 0; + + if (to) { + alrm_val = readl(chip->base + SUNXI_ALRM_EN); + alrm_val |= SUNXI_ALRM_EN_CNT_EN; + + alrm_irq_val = readl(chip->base + SUNXI_ALRM_IRQ_EN); + alrm_irq_val |= SUNXI_ALRM_IRQ_EN_CNT_IRQ_EN; + } else { + writel(SUNXI_ALRM_IRQ_STA_CNT_IRQ_PEND, + chip->base + SUNXI_ALRM_IRQ_STA); + } + + writel(alrm_val, chip->base + SUNXI_ALRM_EN); + writel(alrm_irq_val, chip->base + SUNXI_ALRM_IRQ_EN); +} + +static int sunxi_rtc_getalarm(struct device *dev, struct rtc_wkalrm *wkalrm) +{ + struct sunxi_rtc_dev *chip = dev_get_drvdata(dev); + struct rtc_time *alrm_tm = &wkalrm->time; + u32 alrm; + u32 alrm_en; + u32 date; + + alrm = readl(chip->base + SUNXI_ALRM_DHMS); + date = readl(chip->base + SUNXI_RTC_YMD); + + alrm_tm->tm_sec = SUNXI_ALRM_GET_SEC_VALUE(alrm); + alrm_tm->tm_min = SUNXI_ALRM_GET_MIN_VALUE(alrm); + alrm_tm->tm_hour = SUNXI_ALRM_GET_HOUR_VALUE(alrm); + + alrm_tm->tm_mday = SUNXI_DATE_GET_DAY_VALUE(date); + alrm_tm->tm_mon = SUNXI_DATE_GET_MON_VALUE(date); + alrm_tm->tm_year = SUNXI_DATE_GET_YEAR_VALUE(date, + chip->data_year->mask); + + alrm_tm->tm_mon -= 1; + + /* + * switch from (data_year->min)-relative offset to + * a (1900)-relative one + */ + alrm_tm->tm_year += SUNXI_YEAR_OFF(chip->data_year); + + alrm_en = readl(chip->base + SUNXI_ALRM_IRQ_EN); + if (alrm_en & SUNXI_ALRM_EN_CNT_EN) + wkalrm->enabled = 1; + + return 0; +} + +static int sunxi_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm) +{ + struct sunxi_rtc_dev *chip = dev_get_drvdata(dev); + u32 date, time; + + /* + * read again in case it changes + */ + do { + date = readl(chip->base + SUNXI_RTC_YMD); + time = readl(chip->base + SUNXI_RTC_HMS); + } while ((date != readl(chip->base + SUNXI_RTC_YMD)) || + (time != readl(chip->base + SUNXI_RTC_HMS))); + + rtc_tm->tm_sec = SUNXI_TIME_GET_SEC_VALUE(time); + rtc_tm->tm_min = SUNXI_TIME_GET_MIN_VALUE(time); + rtc_tm->tm_hour = SUNXI_TIME_GET_HOUR_VALUE(time); + + rtc_tm->tm_mday = SUNXI_DATE_GET_DAY_VALUE(date); + rtc_tm->tm_mon = SUNXI_DATE_GET_MON_VALUE(date); + rtc_tm->tm_year = SUNXI_DATE_GET_YEAR_VALUE(date, + chip->data_year->mask); + + rtc_tm->tm_mon -= 1; + + /* + * switch from (data_year->min)-relative offset to + * a (1900)-relative one + */ + rtc_tm->tm_year += SUNXI_YEAR_OFF(chip->data_year); + + return rtc_valid_tm(rtc_tm); +} + +static int sunxi_rtc_setalarm(struct device *dev, struct rtc_wkalrm *wkalrm) +{ + struct sunxi_rtc_dev *chip = dev_get_drvdata(dev); + struct rtc_time *alrm_tm = &wkalrm->time; + struct rtc_time tm_now; + u32 alrm = 0; + unsigned long time_now = 0; + unsigned long time_set = 0; + unsigned long time_gap = 0; + unsigned long time_gap_day = 0; + unsigned long time_gap_hour = 0; + unsigned long time_gap_min = 0; + int ret = 0; + + ret = sunxi_rtc_gettime(dev, &tm_now); + if (ret < 0) { + dev_err(dev, "Error in getting time\n"); + return -EINVAL; + } + + rtc_tm_to_time(alrm_tm, &time_set); + rtc_tm_to_time(&tm_now, &time_now); + if (time_set <= time_now) { + dev_err(dev, "Date to set in the past\n"); + return -EINVAL; + } + + time_gap = time_set - time_now; + time_gap_day = time_gap / SEC_IN_DAY; + time_gap -= time_gap_day * SEC_IN_DAY; + time_gap_hour = time_gap / SEC_IN_HOUR; + time_gap -= time_gap_hour * SEC_IN_HOUR; + time_gap_min = time_gap / SEC_IN_MIN; + time_gap -= time_gap_min * SEC_IN_MIN; + + if (time_gap_day > 255) { + dev_err(dev, "Day must be in the range 0 - 255\n"); + return -EINVAL; + } + + sunxi_rtc_setaie(0, chip); + writel(0, chip->base + SUNXI_ALRM_DHMS); + usleep_range(100, 300); + + alrm = SUNXI_ALRM_SET_SEC_VALUE(time_gap) | + SUNXI_ALRM_SET_MIN_VALUE(time_gap_min) | + SUNXI_ALRM_SET_HOUR_VALUE(time_gap_hour) | + SUNXI_ALRM_SET_DAY_VALUE(time_gap_day); + writel(alrm, chip->base + SUNXI_ALRM_DHMS); + + writel(0, chip->base + SUNXI_ALRM_IRQ_EN); + writel(SUNXI_ALRM_IRQ_EN_CNT_IRQ_EN, chip->base + SUNXI_ALRM_IRQ_EN); + + sunxi_rtc_setaie(wkalrm->enabled, chip); + + return 0; +} + +static int sunxi_rtc_wait(struct sunxi_rtc_dev *chip, int offset, + unsigned int mask, unsigned int ms_timeout) +{ + const unsigned long timeout = jiffies + msecs_to_jiffies(ms_timeout); + u32 reg; + + do { + reg = readl(chip->base + offset); + reg &= mask; + + if (reg == mask) + return 0; + + } while (time_before(jiffies, timeout)); + + return -ETIMEDOUT; +} + +static int sunxi_rtc_settime(struct device *dev, struct rtc_time *rtc_tm) +{ + struct sunxi_rtc_dev *chip = dev_get_drvdata(dev); + u32 date = 0; + u32 time = 0; + int year; + + /* + * the input rtc_tm->tm_year is the offset relative to 1900. We use + * the SUNXI_YEAR_OFF macro to rebase it with respect to the min year + * allowed by the hardware + */ + + year = rtc_tm->tm_year + 1900; + if (year < chip->data_year->min || year > chip->data_year->max) { + dev_err(dev, "rtc only supports year in range %d - %d\n", + chip->data_year->min, chip->data_year->max); + return -EINVAL; + } + + rtc_tm->tm_year -= SUNXI_YEAR_OFF(chip->data_year); + rtc_tm->tm_mon += 1; + + date = SUNXI_DATE_SET_DAY_VALUE(rtc_tm->tm_mday) | + SUNXI_DATE_SET_MON_VALUE(rtc_tm->tm_mon) | + SUNXI_DATE_SET_YEAR_VALUE(rtc_tm->tm_year, + chip->data_year->mask); + + if (is_leap_year(year)) + date |= SUNXI_LEAP_SET_VALUE(1, chip->data_year->leap_shift); + + time = SUNXI_TIME_SET_SEC_VALUE(rtc_tm->tm_sec) | + SUNXI_TIME_SET_MIN_VALUE(rtc_tm->tm_min) | + SUNXI_TIME_SET_HOUR_VALUE(rtc_tm->tm_hour); + + writel(0, chip->base + SUNXI_RTC_HMS); + writel(0, chip->base + SUNXI_RTC_YMD); + + writel(time, chip->base + SUNXI_RTC_HMS); + + /* + * After writing the RTC HH-MM-SS register, the + * SUNXI_LOSC_CTRL_RTC_HMS_ACC bit is set and it will not + * be cleared until the real writing operation is finished + */ + + if (sunxi_rtc_wait(chip, SUNXI_LOSC_CTRL, + SUNXI_LOSC_CTRL_RTC_HMS_ACC, 50)) { + dev_err(dev, "Failed to set rtc time.\n"); + return -1; + } + + writel(date, chip->base + SUNXI_RTC_YMD); + + /* + * After writing the RTC YY-MM-DD register, the + * SUNXI_LOSC_CTRL_RTC_YMD_ACC bit is set and it will not + * be cleared until the real writing operation is finished + */ + + if (sunxi_rtc_wait(chip, SUNXI_LOSC_CTRL, + SUNXI_LOSC_CTRL_RTC_YMD_ACC, 50)) { + dev_err(dev, "Failed to set rtc time.\n"); + return -1; + } + + return 0; +} + +static int sunxi_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct sunxi_rtc_dev *chip = dev_get_drvdata(dev); + + if (!enabled) + sunxi_rtc_setaie(enabled, chip); + + return 0; +} + +static const struct rtc_class_ops sunxi_rtc_ops = { + .read_time = sunxi_rtc_gettime, + .set_time = sunxi_rtc_settime, + .read_alarm = sunxi_rtc_getalarm, + .set_alarm = sunxi_rtc_setalarm, + .alarm_irq_enable = sunxi_rtc_alarm_irq_enable +}; + +static const struct of_device_id sunxi_rtc_dt_ids[] = { + { .compatible = "allwinner,sun4i-rtc", .data = &data_year_param[0] }, + { .compatible = "allwinner,sun7i-a20-rtc", .data = &data_year_param[1] }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, sunxi_rtc_dt_ids); + +static int sunxi_rtc_probe(struct platform_device *pdev) +{ + struct sunxi_rtc_dev *chip; + struct resource *res; + const struct of_device_id *of_id; + int ret; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + platform_set_drvdata(pdev, chip); + chip->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + chip->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(chip->base)) + return PTR_ERR(chip->base); + + chip->irq = platform_get_irq(pdev, 0); + if (chip->irq < 0) { + dev_err(&pdev->dev, "No IRQ resource\n"); + return chip->irq; + } + ret = devm_request_irq(&pdev->dev, chip->irq, sunxi_rtc_alarmirq, + 0, dev_name(&pdev->dev), chip); + if (ret) { + dev_err(&pdev->dev, "Could not request IRQ\n"); + return ret; + } + + of_id = of_match_device(sunxi_rtc_dt_ids, &pdev->dev); + if (!of_id) { + dev_err(&pdev->dev, "Unable to setup RTC data\n"); + return -ENODEV; + } + chip->data_year = (struct sunxi_rtc_data_year *) of_id->data; + + /* clear the alarm count value */ + writel(0, chip->base + SUNXI_ALRM_DHMS); + + /* disable alarm, not generate irq pending */ + writel(0, chip->base + SUNXI_ALRM_EN); + + /* disable alarm week/cnt irq, unset to cpu */ + writel(0, chip->base + SUNXI_ALRM_IRQ_EN); + + /* clear alarm week/cnt irq pending */ + writel(SUNXI_ALRM_IRQ_STA_CNT_IRQ_PEND, chip->base + + SUNXI_ALRM_IRQ_STA); + + chip->rtc = rtc_device_register("rtc-sunxi", &pdev->dev, + &sunxi_rtc_ops, THIS_MODULE); + if (IS_ERR(chip->rtc)) { + dev_err(&pdev->dev, "unable to register device\n"); + return PTR_ERR(chip->rtc); + } + + dev_info(&pdev->dev, "RTC enabled\n"); + + return 0; +} + +static int sunxi_rtc_remove(struct platform_device *pdev) +{ + struct sunxi_rtc_dev *chip = platform_get_drvdata(pdev); + + rtc_device_unregister(chip->rtc); + + return 0; +} + +static struct platform_driver sunxi_rtc_driver = { + .probe = sunxi_rtc_probe, + .remove = sunxi_rtc_remove, + .driver = { + .name = "sunxi-rtc", + .owner = THIS_MODULE, + .of_match_table = sunxi_rtc_dt_ids, + }, +}; + +module_platform_driver(sunxi_rtc_driver); + +MODULE_DESCRIPTION("sunxi RTC driver"); +MODULE_AUTHOR("Carlo Caione <carlo.caione@gmail.com>"); +MODULE_LICENSE("GPL"); |