diff --git a/arch/arm/configs/versatilepb_defconfig b/arch/arm/configs/versatilepb_defconfig index 58dfb8b..a447f93 100644 --- a/arch/arm/configs/versatilepb_defconfig +++ b/arch/arm/configs/versatilepb_defconfig @@ -51,6 +51,8 @@ CONFIG_I2C=y CONFIG_I2C_VERSATILE=y CONFIG_GPIO_PL061=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_DS1307=y CONFIG_FS_CRAMFS=y CONFIG_FS_TFTP=y CONFIG_SHA1=y diff --git a/arch/mips/configs/ritmix-rzx50_defconfig b/arch/mips/configs/ritmix-rzx50_defconfig index 232faa2..fb67014 100644 --- a/arch/mips/configs/ritmix-rzx50_defconfig +++ b/arch/mips/configs/ritmix-rzx50_defconfig @@ -10,6 +10,7 @@ CONFIG_AUTO_COMPLETE=y # CONFIG_DEFAULT_ENVIRONMENT is not set CONFIG_DEBUG_LL=y +CONFIG_LONGHELP=y CONFIG_CMD_IOMEM=y CONFIG_CMD_MEMINFO=y CONFIG_CMD_BOOTM_SHOW_TYPE=y @@ -42,6 +43,8 @@ CONFIG_LED_GPIO_OF=y CONFIG_LED_TRIGGERS=y CONFIG_GPIO_JZ4740=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_JZ4740=y CONFIG_SHA1=y CONFIG_SHA224=y CONFIG_SHA256=y diff --git a/arch/mips/dts/jz4755.dtsi b/arch/mips/dts/jz4755.dtsi index 7184635..b83d884 100644 --- a/arch/mips/dts/jz4755.dtsi +++ b/arch/mips/dts/jz4755.dtsi @@ -13,6 +13,11 @@ reg = <0xb0002000 0x10>; }; + rtc: rtc@10003000 { + compatible = "ingenic,jz4740-rtc"; + reg = <0xb0003000 0x38>; + }; + serial0: serial@b0030000 { compatible = "ingenic,jz4740-uart"; reg = <0xb0030000 0x20>; diff --git a/commands/Kconfig b/commands/Kconfig index 71cd1ee..8ee0805 100644 --- a/commands/Kconfig +++ b/commands/Kconfig @@ -1700,6 +1700,14 @@ Usage: gpio_set_value GPIO VALUE +config CMD_HWCLOCK + bool + depends on RTC_CLASS + prompt "hwclock command" + default y + help + The hwclock command allows to query or set the hardware clock (RTC). + config CMD_I2C bool depends on I2C diff --git a/commands/Makefile b/commands/Makefile index 4474136..ca02897 100644 --- a/commands/Makefile +++ b/commands/Makefile @@ -101,3 +101,4 @@ obj-$(CONFIG_CMD_REGULATOR) += regulator.o obj-$(CONFIG_CMD_LSPCI) += lspci.o obj-$(CONFIG_CMD_IMD) += imd.o +obj-$(CONFIG_CMD_HWCLOCK) += hwclock.o diff --git a/commands/hwclock.c b/commands/hwclock.c new file mode 100644 index 0000000..a1f5293 --- /dev/null +++ b/commands/hwclock.c @@ -0,0 +1,161 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static char *strchrnul(const char *s, int c) +{ + while (*s != '\0' && *s != c) + s++; + + return (char *)s; +} + +static int sscanf_two_digits(char *s, int *res) +{ + char buf[3]; + unsigned long t; + + if (!isdigit(s[0]) || !isdigit(s[1])) { + return -EINVAL; + } + + buf[0] = s[0]; + buf[1] = s[1]; + buf[2] = '\0'; + + t = simple_strtoul(buf, NULL, 10); + *res = t; + + return 0; +} + +static int parse_datestr(char *date_str, struct rtc_time *ptm) +{ + char end = '\0'; + int len = strchrnul(date_str, '.') - date_str; + int year; + + /* ccyymmddHHMM[.SS] */ + if (len != 12) { + return -EINVAL; + } + + if (sscanf_two_digits(date_str, &year) || + sscanf_two_digits(&date_str[2], &ptm->tm_year)) { + return -EINVAL; + } + + ptm->tm_year = year * 100 + ptm->tm_year; + + /* Adjust years */ + ptm->tm_year -= 1900; + + if (sscanf_two_digits(&date_str[4], &ptm->tm_mon) || + sscanf_two_digits(&date_str[6], &ptm->tm_mday) || + sscanf_two_digits(&date_str[8], &ptm->tm_hour) || + sscanf_two_digits(&date_str[10], &ptm->tm_min)) { + return -EINVAL; + } + + /* Adjust month from 1-12 to 0-11 */ + ptm->tm_mon -= 1; + + end = date_str[12]; + + if (end == '.') { + /* xxx.SS */ + if (!sscanf_two_digits(&date_str[13], &ptm->tm_sec)) { + end = '\0'; + } + /* else end != NUL and we error out */ + } + + if (end != '\0') { + return -EINVAL; + } + + return 0; +} + +static int do_hwclock(int argc, char *argv[]) +{ + struct rtc_device *r; + struct rtc_time tm; + struct rtc_time stm; + char rtc_name[16] = "rtc0"; + char *env_name = NULL; + int opt; + int set = 0; + + while ((opt = getopt(argc, argv, "f:s:e:")) > 0) { + int ret; + + switch (opt) { + case 'f': + strncpy(rtc_name, optarg, 16); + break; + case 's': + memset(&stm, 0, sizeof(stm)); + + ret = parse_datestr(optarg, &stm); + if (ret) + return ret; + + ret = rtc_valid_tm(&stm); + if (ret) + return ret; + + set = 1; + break; + case 'e': + env_name = optarg; + break; + } + } + + r = rtc_lookup(rtc_name); + if (IS_ERR(r)) + return PTR_ERR(r); + + if (set) { + rtc_set_time(r, &stm); + return 0; + } + + rtc_read_time(r, &tm); + + if (env_name) { + unsigned long time; + char t[12]; + + rtc_tm_to_time(&tm, &time); + snprintf(t, 12, "%lu", time); + setenv(env_name, t); + } else { + printf("%02d:%02d:%02d %02d-%02d-%04d\n", + tm.tm_hour, tm.tm_min, tm.tm_sec, + tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900); + } + + return 0; +} + +BAREBOX_CMD_HELP_START(hwclock) +BAREBOX_CMD_HELP_TEXT("Options:") +BAREBOX_CMD_HELP_OPT ("-f NAME\t\t\t", "RTC device name (default rtc0)") +BAREBOX_CMD_HELP_OPT ("-e VARNAME\t\t", "store RTC readout into variable VARNAME") +BAREBOX_CMD_HELP_OPT ("-s ccyymmddHHMM[.SS]\t", "set time") +BAREBOX_CMD_HELP_END + +BAREBOX_CMD_START(hwclock) + .cmd = do_hwclock, + BAREBOX_CMD_DESC("query or set the hardware clock (RTC)") + BAREBOX_CMD_GROUP(CMD_GRP_HWMANIP) + BAREBOX_CMD_HELP(cmd_hwclock_help) +BAREBOX_CMD_END diff --git a/drivers/Kconfig b/drivers/Kconfig index 12a9d8c..d38032c 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -28,5 +28,6 @@ source "drivers/regulator/Kconfig" source "drivers/reset/Kconfig" source "drivers/pci/Kconfig" +source "drivers/rtc/Kconfig" endmenu diff --git a/drivers/Makefile b/drivers/Makefile index 1990e86..4591f9a 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -27,3 +27,4 @@ obj-$(CONFIG_REGULATOR) += regulator/ obj-$(CONFIG_RESET_CONTROLLER) += reset/ obj-$(CONFIG_PCI) += pci/ +obj-y += rtc/ diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig new file mode 100644 index 0000000..957fee7 --- /dev/null +++ b/drivers/rtc/Kconfig @@ -0,0 +1,44 @@ +# +# RTC class/drivers configuration +# + +config RTC_LIB + bool + +menuconfig RTC_CLASS + bool "Real Time Clock" + default n + select RTC_LIB + help + Generic RTC class support. If you say yes here, you will + be allowed to plug one or more RTCs to your system. You will + probably want to enable one or more of the interfaces below. + +if RTC_CLASS + +comment "I2C RTC drivers" + depends on I2C + +if I2C + +config RTC_DRV_DS1307 + tristate "Dallas/Maxim DS1307/38" + help + If you say yes here you get support for various compatible RTC + chips (often with battery backup) connected with I2C. This driver + should handle DS1307, DS1338 and probably other chips. + + The first seven registers on these chips hold an RTC, and other + registers may add features such as NVRAM, a trickle charger for + the RTC/NVRAM backup power, and alarms. + +endif # I2C + +config RTC_DRV_JZ4740 + tristate "Ingenic JZ4740 RTC" + depends on MACH_MIPS_XBURST + help + If you say yes here you get support for the Ingenic JZ4740 SoC RTC + controller. + +endif # RTC_CLASS diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile new file mode 100644 index 0000000..1cc9bb8 --- /dev/null +++ b/drivers/rtc/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for RTC class/drivers. +# + +obj-$(CONFIG_RTC_LIB) += rtc-lib.o +obj-$(CONFIG_RTC_CLASS) += class.o + +# Keep the list ordered. + +obj-$(CONFIG_RTC_DRV_DS1307) += rtc-ds1307.o +obj-$(CONFIG_RTC_DRV_JZ4740) += rtc-jz4740.o diff --git a/drivers/rtc/class.c b/drivers/rtc/class.c new file mode 100644 index 0000000..356707b --- /dev/null +++ b/drivers/rtc/class.c @@ -0,0 +1,70 @@ +/* + * RTC barebox subsystem, base class + * + * Copyright (C) 2014 Antony Pavlov + * + * 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 +#include +#include + +LIST_HEAD(rtc_list); +EXPORT_SYMBOL(rtc_list); + +struct rtc_device *rtc_lookup(const char *name) +{ + struct rtc_device *r; + + if (!name) + return ERR_PTR(-ENODEV); + + list_for_each_entry(r, &rtc_list, list) { + if (!strcmp(dev_name(&r->class_dev), name)) + return r; + } + + return ERR_PTR(-ENODEV); +} +EXPORT_SYMBOL(rtc_lookup); + +int rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm) +{ + return rtc->ops->read_time(rtc, tm); +} +EXPORT_SYMBOL(rtc_read_time); + +int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm) +{ + return rtc->ops->set_time(rtc, tm); +} +EXPORT_SYMBOL(rtc_set_time); + +int rtc_register(struct rtc_device *rtcdev) +{ + struct device_d *dev = &rtcdev->class_dev; + + if (!rtcdev->ops) + return -EINVAL; + + dev->id = DEVICE_ID_DYNAMIC; + strcpy(dev->name, "rtc"); + if (rtcdev->dev) + dev->parent = rtcdev->dev; + platform_device_register(dev); + + list_add_tail(&rtcdev->list, &rtc_list); + + return 0; +} +EXPORT_SYMBOL(rtc_register); diff --git a/drivers/rtc/rtc-ds1307.c b/drivers/rtc/rtc-ds1307.c new file mode 100644 index 0000000..725f15a --- /dev/null +++ b/drivers/rtc/rtc-ds1307.c @@ -0,0 +1,347 @@ +/* + * rtc-ds1307.c - RTC driver for some mostly-compatible I2C chips. + * + * This code was ported from linux-3.15 kernel by Antony Pavlov. + * + * Copyright (C) 2005 James Chapman (ds1337 core) + * Copyright (C) 2006 David Brownell + * Copyright (C) 2009 Matthias Fuchs (rx8025 support) + * Copyright (C) 2012 Bertrand Achard (nvram access fixes) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * We can't determine type by probing, but if we expect pre-Linux code + * to have set the chip up as a clock (turning on the oscillator and + * setting the date and time), Linux can ignore the non-clock features. + * That's a natural job for a factory or repair bench. + */ +enum ds_type { + ds_1307, + ds_1338, + last_ds_type /* always last */ +}; + +/* RTC registers don't differ much, except for the century flag */ +#define DS1307_REG_SECS 0x00 /* 00-59 */ +# define DS1307_BIT_CH 0x80 +# define DS1340_BIT_nEOSC 0x80 +#define DS1307_REG_MIN 0x01 /* 00-59 */ +#define DS1307_REG_HOUR 0x02 /* 00-23, or 1-12{am,pm} */ +# define DS1307_BIT_12HR 0x40 /* in REG_HOUR */ +# define DS1307_BIT_PM 0x20 /* in REG_HOUR */ +# define DS1340_BIT_CENTURY_EN 0x80 /* in REG_HOUR */ +# define DS1340_BIT_CENTURY 0x40 /* in REG_HOUR */ +#define DS1307_REG_WDAY 0x03 /* 01-07 */ +#define DS1307_REG_MDAY 0x04 /* 01-31 */ +#define DS1307_REG_MONTH 0x05 /* 01-12 */ +# define DS1337_BIT_CENTURY 0x80 /* in REG_MONTH */ +#define DS1307_REG_YEAR 0x06 /* 00-99 */ + +/* + * Other registers (control, status, alarms, trickle charge, NVRAM, etc) + * start at 7, and they differ a LOT. Only control and status matter for + * basic RTC date and time functionality; be careful using them. + */ +#define DS1307_REG_CONTROL 0x07 /* or ds1338 */ +# define DS1307_BIT_OUT 0x80 +# define DS1338_BIT_OSF 0x20 +# define DS1307_BIT_SQWE 0x10 +# define DS1307_BIT_RS1 0x02 +# define DS1307_BIT_RS0 0x01 + +struct ds1307 { + struct rtc_device rtc; + u8 offset; /* register's offset */ + u8 regs[11]; + enum ds_type type; + unsigned long flags; + struct i2c_client *client; + s32 (*read_block_data)(const struct i2c_client *client, u8 command, + u8 length, u8 *values); + s32 (*write_block_data)(const struct i2c_client *client, u8 command, + u8 length, const u8 *values); +}; + +static struct platform_device_id ds1307_id[] = { + { "ds1307", ds_1307 }, + { "ds1338", ds_1338 }, + { "pt7c4338", ds_1307 }, + { } +}; + +/*----------------------------------------------------------------------*/ + +#define BLOCK_DATA_MAX_TRIES 10 + +static s32 ds1307_read_block_data_once(const struct i2c_client *client, + u8 command, u8 length, u8 *values) +{ + s32 i, data; + + for (i = 0; i < length; i++) { + data = i2c_smbus_read_byte_data(client, command + i); + if (data < 0) + return data; + values[i] = data; + } + return i; +} + +static s32 ds1307_read_block_data(const struct i2c_client *client, u8 command, + u8 length, u8 *values) +{ + u8 oldvalues[255]; + s32 ret; + int tries = 0; + + dev_dbg(&client->dev, "ds1307_read_block_data (length=%d)\n", length); + ret = ds1307_read_block_data_once(client, command, length, values); + if (ret < 0) + return ret; + do { + if (++tries > BLOCK_DATA_MAX_TRIES) { + dev_err(&client->dev, + "ds1307_read_block_data failed\n"); + return -EIO; + } + memcpy(oldvalues, values, length); + ret = ds1307_read_block_data_once(client, command, length, + values); + if (ret < 0) + return ret; + } while (memcmp(oldvalues, values, length)); + return length; +} + +static s32 ds1307_write_block_data(const struct i2c_client *client, u8 command, + u8 length, const u8 *values) +{ + u8 currvalues[255]; + int tries = 0; + + dev_dbg(&client->dev, "ds1307_write_block_data (length=%d)\n", length); + do { + s32 i, ret; + + if (++tries > BLOCK_DATA_MAX_TRIES) { + dev_err(&client->dev, + "ds1307_write_block_data failed\n"); + return -EIO; + } + for (i = 0; i < length; i++) { + ret = i2c_smbus_write_byte_data(client, command + i, + values[i]); + if (ret < 0) + return ret; + } + ret = ds1307_read_block_data_once(client, command, length, + currvalues); + if (ret < 0) + return ret; + } while (memcmp(currvalues, values, length)); + return length; +} + +static inline struct ds1307 *to_ds1307_priv(struct rtc_device *rtcdev) +{ + return container_of(rtcdev, struct ds1307, rtc); +} + +static int ds1307_get_time(struct rtc_device *rtcdev, struct rtc_time *t) +{ + struct device_d *dev = rtcdev->dev; + struct ds1307 *ds1307 = to_ds1307_priv(rtcdev); + int tmp; + + /* read the RTC date and time registers all at once */ + tmp = ds1307->read_block_data(ds1307->client, + ds1307->offset, 7, ds1307->regs); + if (tmp != 7) { + dev_err(dev, "%s error %d\n", "read", tmp); + return -EIO; + } + + dev_dbg(dev, "%s: %7ph\n", "read", ds1307->regs); + + t->tm_sec = bcd2bin(ds1307->regs[DS1307_REG_SECS] & 0x7f); + t->tm_min = bcd2bin(ds1307->regs[DS1307_REG_MIN] & 0x7f); + tmp = ds1307->regs[DS1307_REG_HOUR] & 0x3f; + t->tm_hour = bcd2bin(tmp); + t->tm_wday = bcd2bin(ds1307->regs[DS1307_REG_WDAY] & 0x07) - 1; + t->tm_mday = bcd2bin(ds1307->regs[DS1307_REG_MDAY] & 0x3f); + tmp = ds1307->regs[DS1307_REG_MONTH] & 0x1f; + t->tm_mon = bcd2bin(tmp) - 1; + + /* assume 20YY not 19YY, and ignore DS1337_BIT_CENTURY */ + t->tm_year = bcd2bin(ds1307->regs[DS1307_REG_YEAR]) + 100; + + dev_dbg(dev, "%s secs=%d, mins=%d, " + "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", + "read", t->tm_sec, t->tm_min, + t->tm_hour, t->tm_mday, + t->tm_mon, t->tm_year, t->tm_wday); + + /* initial clock setting can be undefined */ + return rtc_valid_tm(t); +} + +static int ds1307_set_time(struct rtc_device *rtcdev, struct rtc_time *t) +{ + struct device_d *dev = rtcdev->dev; + struct ds1307 *ds1307 = to_ds1307_priv(rtcdev); + int result; + int tmp; + u8 *buf = ds1307->regs; + + dev_dbg(dev, "%s secs=%d, mins=%d, " + "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", + "write", t->tm_sec, t->tm_min, + t->tm_hour, t->tm_mday, + t->tm_mon, t->tm_year, t->tm_wday); + + buf[DS1307_REG_SECS] = bin2bcd(t->tm_sec); + buf[DS1307_REG_MIN] = bin2bcd(t->tm_min); + buf[DS1307_REG_HOUR] = bin2bcd(t->tm_hour); + buf[DS1307_REG_WDAY] = bin2bcd(t->tm_wday + 1); + buf[DS1307_REG_MDAY] = bin2bcd(t->tm_mday); + buf[DS1307_REG_MONTH] = bin2bcd(t->tm_mon + 1); + + /* assume 20YY not 19YY */ + tmp = t->tm_year - 100; + buf[DS1307_REG_YEAR] = bin2bcd(tmp); + + dev_dbg(dev, "%s: %7ph\n", "write", buf); + + result = ds1307->write_block_data(ds1307->client, + ds1307->offset, 7, buf); + if (result < 0) { + dev_err(dev, "%s error %d\n", "write", result); + return result; + } + return 0; +} + +static const struct rtc_class_ops ds13xx_rtc_ops = { + .read_time = ds1307_get_time, + .set_time = ds1307_set_time, +}; + +static int ds1307_probe(struct device_d *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ds1307 *ds1307; + int err = -ENODEV; + int tmp; + unsigned char *buf; + unsigned long driver_data; + + ds1307 = xzalloc(sizeof(struct ds1307)); + + err = dev_get_drvdata(dev, &driver_data); + if (err) + goto exit; + + ds1307->client = client; + ds1307->type = driver_data; + + buf = ds1307->regs; + + ds1307->read_block_data = ds1307_read_block_data; + ds1307->write_block_data = ds1307_write_block_data; + +read_rtc: + /* read RTC registers */ + tmp = ds1307->read_block_data(client, ds1307->offset, 8, buf); + if (tmp != 8) { + dev_dbg(&client->dev, "read error %d\n", tmp); + err = -EIO; + goto exit; + } + + /* + * minimal sanity checking; some chips (like DS1340) don't + * specify the extra bits as must-be-zero, but there are + * still a few values that are clearly out-of-range. + */ + tmp = ds1307->regs[DS1307_REG_SECS]; + switch (ds1307->type) { + case ds_1307: + /* clock halted? turn it on, so clock can tick. */ + if (tmp & DS1307_BIT_CH) { + i2c_smbus_write_byte_data(client, DS1307_REG_SECS, 0); + dev_warn(&client->dev, "SET TIME!\n"); + goto read_rtc; + } + break; + case ds_1338: + /* clock halted? turn it on, so clock can tick. */ + if (tmp & DS1307_BIT_CH) + i2c_smbus_write_byte_data(client, DS1307_REG_SECS, 0); + + /* oscillator fault? clear flag, and warn */ + if (ds1307->regs[DS1307_REG_CONTROL] & DS1338_BIT_OSF) { + i2c_smbus_write_byte_data(client, DS1307_REG_CONTROL, + ds1307->regs[DS1307_REG_CONTROL] + & ~DS1338_BIT_OSF); + dev_warn(&client->dev, "SET TIME!\n"); + goto read_rtc; + } + break; + default: + break; + } + + tmp = ds1307->regs[DS1307_REG_HOUR]; + switch (ds1307->type) { + default: + if (!(tmp & DS1307_BIT_12HR)) + break; + + /* + * Be sure we're in 24 hour mode. Multi-master systems + * take note... + */ + tmp = bcd2bin(tmp & 0x1f); + if (tmp == 12) + tmp = 0; + if (ds1307->regs[DS1307_REG_HOUR] & DS1307_BIT_PM) + tmp += 12; + i2c_smbus_write_byte_data(client, + ds1307->offset + DS1307_REG_HOUR, + bin2bcd(tmp)); + } + + ds1307->rtc.ops = &ds13xx_rtc_ops; + ds1307->rtc.dev = dev; + + err = rtc_register(&ds1307->rtc); + +exit: + return err; +} + +static struct driver_d ds1307_driver = { + .name = "rtc-ds1307", + .probe = ds1307_probe, + .id_table = ds1307_id, +}; + +static int __init ds1307_init(void) +{ + return i2c_driver_register(&ds1307_driver); +} +device_initcall(ds1307_init); diff --git a/drivers/rtc/rtc-jz4740.c b/drivers/rtc/rtc-jz4740.c new file mode 100644 index 0000000..8194050 --- /dev/null +++ b/drivers/rtc/rtc-jz4740.c @@ -0,0 +1,165 @@ +/* + * JZ4740 SoC RTC driver + * + * This code was ported from linux-3.15 kernel by Antony Pavlov. + * + * Copyright (C) 2009-2010, Lars-Peter Clausen + * Copyright (C) 2010, Paul Cercueil + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define JZ_REG_RTC_CTRL 0x00 +#define JZ_REG_RTC_SEC 0x04 +#define JZ_REG_RTC_SEC_ALARM 0x08 +#define JZ_REG_RTC_REGULATOR 0x0C +#define JZ_REG_RTC_HIBERNATE 0x20 +#define JZ_REG_RTC_SCRATCHPAD 0x34 + +#define JZ_RTC_CTRL_WRDY BIT(7) + +struct jz4740_rtc { + struct rtc_device rtc; + + void __iomem *base; +}; + +static inline uint32_t jz4740_rtc_reg_read(struct jz4740_rtc *rtc, size_t reg) +{ + return readl(rtc->base + reg); +} + +static int jz4740_rtc_wait_write_ready(struct jz4740_rtc *rtc) +{ + uint32_t ctrl; + int timeout = 1000; + + do { + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL); + } while (!(ctrl & JZ_RTC_CTRL_WRDY) && --timeout); + + return timeout ? 0 : -EIO; +} + +static inline int jz4740_rtc_reg_write(struct jz4740_rtc *rtc, size_t reg, + uint32_t val) +{ + int ret; + ret = jz4740_rtc_wait_write_ready(rtc); + if (ret == 0) + writel(val, rtc->base + reg); + + return ret; +} + +static inline struct jz4740_rtc *to_jz4740_rtc_priv(struct rtc_device *rtcdev) +{ + return container_of(rtcdev, struct jz4740_rtc, rtc); +} + +static int jz4740_rtc_read_time(struct rtc_device *rtcdev, struct rtc_time *time) +{ + struct jz4740_rtc *rtc = to_jz4740_rtc_priv(rtcdev); + uint32_t secs, secs2; + int timeout = 5; + + /* If the seconds register is read while it is updated, it can contain a + * bogus value. This can be avoided by making sure that two consecutive + * reads have the same value. + */ + secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC); + secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC); + + while (secs != secs2 && --timeout) { + secs = secs2; + secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC); + } + + if (timeout == 0) + return -EIO; + + rtc_time_to_tm(secs, time); + + return rtc_valid_tm(time); +} + +static int jz4740_rtc_set_time(struct rtc_device *rtcdev, struct rtc_time *t) +{ + struct jz4740_rtc *rtc = to_jz4740_rtc_priv(rtcdev); + unsigned long secs; + + rtc_tm_to_time(t, &secs); + + return jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, secs); +} + +static struct rtc_class_ops jz4740_rtc_ops = { + .read_time = jz4740_rtc_read_time, + .set_time = jz4740_rtc_set_time, +}; + +static int jz4740_rtc_probe(struct device_d *dev) +{ + int ret; + struct jz4740_rtc *rtc; + uint32_t scratchpad; + void __iomem *base; + + base = dev_request_mem_region(dev, 0); + if (!base) { + dev_err(dev, "could not get memory region\n"); + return -ENODEV; + } + + rtc = xzalloc(sizeof(*rtc)); + + rtc->base = base; + rtc->rtc.ops = &jz4740_rtc_ops; + rtc->rtc.dev = dev; + + ret = rtc_register(&rtc->rtc); + if (ret) { + dev_err(dev, "Failed to register rtc device: %d\n", ret); + return ret; + } + + scratchpad = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SCRATCHPAD); + if (scratchpad != 0x12345678) { + ret = jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SCRATCHPAD, 0x12345678); + ret = jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, 0); + if (ret) { + dev_err(dev, "Could not write write to RTC registers\n"); + return ret; + } + } + + return 0; +} + +static __maybe_unused struct of_device_id jz4740_rtc_dt_ids[] = { + { + .compatible = "ingenic,jz4740-rtc", + }, { + /* sentinel */ + } +}; + +static struct driver_d jz4740_rtc_driver = { + .name = "jz4740-rtc", + .probe = jz4740_rtc_probe, + .of_compatible = DRV_OF_COMPAT(jz4740_rtc_dt_ids), +}; +device_platform_driver(jz4740_rtc_driver); diff --git a/drivers/rtc/rtc-lib.c b/drivers/rtc/rtc-lib.c new file mode 100644 index 0000000..1b23458 --- /dev/null +++ b/drivers/rtc/rtc-lib.c @@ -0,0 +1,113 @@ +/* + * rtc and date/time utility functions + * + * This code was ported from linux-3.15 kernel by Antony Pavlov. + * + * Copyright (C) 2005-06 Tower Technologies + * Author: Alessandro Zummo + * + * based on arch/arm/common/rtctime.c and other bits + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include +#include + +static const unsigned char rtc_days_in_month[] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + +static const unsigned short rtc_ydays[2][13] = { + /* Normal years */ + { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, + /* Leap years */ + { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } +}; + +#define LEAPS_THRU_END_OF(y) ((y)/4 - (y)/100 + (y)/400) + +/* + * The number of days in the month. + */ +int rtc_month_days(unsigned int month, unsigned int year) +{ + return rtc_days_in_month[month] + (is_leap_year(year) && month == 1); +} +EXPORT_SYMBOL(rtc_month_days); + +/* + * Convert seconds since 01-01-1970 00:00:00 to Gregorian date. + */ +void rtc_time_to_tm(unsigned long time, struct rtc_time *tm) +{ + unsigned int month, year; + int days; + + days = time / 86400; + time -= (unsigned int) days * 86400; + + /* day of the week, 1970-01-01 was a Thursday */ + tm->tm_wday = (days + 4) % 7; + + year = 1970 + days / 365; + days -= (year - 1970) * 365 + + LEAPS_THRU_END_OF(year - 1) + - LEAPS_THRU_END_OF(1970 - 1); + if (days < 0) { + year -= 1; + days += 365 + is_leap_year(year); + } + tm->tm_year = year - 1900; + tm->tm_yday = days + 1; + + for (month = 0; month < 11; month++) { + int newdays; + + newdays = days - rtc_month_days(month, year); + if (newdays < 0) + break; + days = newdays; + } + tm->tm_mon = month; + tm->tm_mday = days + 1; + + tm->tm_hour = time / 3600; + time -= tm->tm_hour * 3600; + tm->tm_min = time / 60; + tm->tm_sec = time - tm->tm_min * 60; + + tm->tm_isdst = 0; +} +EXPORT_SYMBOL(rtc_time_to_tm); + +/* + * Does the rtc_time represent a valid date/time? + */ +int rtc_valid_tm(struct rtc_time *tm) +{ + if (tm->tm_year < 70 + || ((unsigned)tm->tm_mon) >= 12 + || tm->tm_mday < 1 + || tm->tm_mday > rtc_month_days(tm->tm_mon, tm->tm_year + 1900) + || ((unsigned)tm->tm_hour) >= 24 + || ((unsigned)tm->tm_min) >= 60 + || ((unsigned)tm->tm_sec) >= 60) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL(rtc_valid_tm); + +/* + * Convert Gregorian date to seconds since 01-01-1970 00:00:00. + */ +int rtc_tm_to_time(struct rtc_time *tm, unsigned long *time) +{ + *time = mktime(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + return 0; +} +EXPORT_SYMBOL(rtc_tm_to_time); diff --git a/include/linux/bcd.h b/include/linux/bcd.h new file mode 100644 index 0000000..18fff11 --- /dev/null +++ b/include/linux/bcd.h @@ -0,0 +1,22 @@ +#ifndef _BCD_H +#define _BCD_H + +#include + +#define bcd2bin(x) \ + (__builtin_constant_p((u8 )(x)) ? \ + const_bcd2bin(x) : \ + _bcd2bin(x)) + +#define bin2bcd(x) \ + (__builtin_constant_p((u8 )(x)) ? \ + const_bin2bcd(x) : \ + _bin2bcd(x)) + +#define const_bcd2bin(x) (((x) & 0x0f) + ((x) >> 4) * 10) +#define const_bin2bcd(x) ((((x) / 10) << 4) + (x) % 10) + +unsigned _bcd2bin(unsigned char val) __attribute_const__; +unsigned char _bin2bcd(unsigned val) __attribute_const__; + +#endif /* _BCD_H */ diff --git a/include/linux/rtc.h b/include/linux/rtc.h new file mode 100644 index 0000000..2dacb14 --- /dev/null +++ b/include/linux/rtc.h @@ -0,0 +1,47 @@ +/* + * Generic RTC interface. + * This version contains the part of the user interface to the Real Time Clock + * service. It is used with both the legacy mc146818 and also EFI + * Struct rtc_time and first 12 ioctl by Paul Gortmaker, 1996 - separated out + * from to this file for 2.4 kernels. + * + * Copyright (C) 1999 Hewlett-Packard Co. + * Copyright (C) 1999 Stephane Eranian + */ +#ifndef _LINUX_RTC_H_ +#define _LINUX_RTC_H_ + +#include +#include + +extern int rtc_month_days(unsigned int month, unsigned int year); +extern int rtc_valid_tm(struct rtc_time *tm); +extern int rtc_tm_to_time(struct rtc_time *tm, unsigned long *time); +extern void rtc_time_to_tm(unsigned long time, struct rtc_time *tm); + +struct rtc_class_ops; + +struct rtc_device { + struct device_d *dev; + struct device_d class_dev; + struct list_head list; + + const struct rtc_class_ops *ops; +}; + +struct rtc_class_ops { + int (*read_time)(struct rtc_device *, struct rtc_time *); + int (*set_time)(struct rtc_device *, struct rtc_time *); +}; + +extern int rtc_register(struct rtc_device *rtcdev); + +extern int rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm); +extern int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm); + +static inline bool is_leap_year(unsigned int year) +{ + return (!(year % 4) && (year % 100)) || !(year % 400); +} + +#endif /* _LINUX_RTC_H_ */ diff --git a/include/rtc.h b/include/rtc.h index c2d8bce..e2414fb 100644 --- a/include/rtc.h +++ b/include/rtc.h @@ -53,4 +53,6 @@ unsigned long mktime (unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int); +extern struct rtc_device *rtc_lookup(const char *name); + #endif /* _RTC_H_ */ diff --git a/lib/Makefile b/lib/Makefile index 77207dc..1a34544 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -1,3 +1,4 @@ +obj-y += bcd.o obj-$(CONFIG_BOOTSTRAP) += bootstrap/ obj-y += ctype.o obj-y += rbtree.o diff --git a/lib/bcd.c b/lib/bcd.c new file mode 100644 index 0000000..b072d50 --- /dev/null +++ b/lib/bcd.c @@ -0,0 +1,14 @@ +#include +#include + +unsigned _bcd2bin(unsigned char val) +{ + return (val & 0x0f) + (val >> 4) * 10; +} +EXPORT_SYMBOL(_bcd2bin); + +unsigned char _bin2bcd(unsigned val) +{ + return ((val / 10) << 4) + val % 10; +} +EXPORT_SYMBOL(_bin2bcd);