This adds support control with GPIO lines connected to the DS1302 which can communicate with three wires. Signed-off-by: Akinobu Mita <akinobu.mita@xxxxxxxxx> Cc: Sergey Yanovich <ynvich@xxxxxxxxx> Cc: Alessandro Zummo <a.zummo@xxxxxxxxxxxx> Cc: Alexandre Belloni <alexandre.belloni@xxxxxxxxxxxxxxxxxx> --- .../devicetree/bindings/rtc/maxim-ds1302.txt | 13 + drivers/rtc/Kconfig | 17 +- drivers/rtc/rtc-ds1302.c | 340 ++++++++++++++++++++- 3 files changed, 351 insertions(+), 19 deletions(-) diff --git a/Documentation/devicetree/bindings/rtc/maxim-ds1302.txt b/Documentation/devicetree/bindings/rtc/maxim-ds1302.txt index ba470c5..d489753 100644 --- a/Documentation/devicetree/bindings/rtc/maxim-ds1302.txt +++ b/Documentation/devicetree/bindings/rtc/maxim-ds1302.txt @@ -27,6 +27,12 @@ Required SPI properties: - spi-cs-high: DS-1302 has active high chip select line. This is required unless inverted in hardware. +Required properties when using GPIO lines: + +- gpio-ce: GPIO connected to CE pin +- gpio-io: GPIO connected to I/O pin +- gpio-reset: GPIO connected to SCLK pin + Example: spi@901c { @@ -44,3 +50,10 @@ spi@901c { spi-cs-high; }; }; + + rtc: ds1302 { + compatible = "maxim,ds1302"; + gpio-ce = <&gpio2 6 GPIO_ACTIVE_HIGH>; + gpio-io = <&gpio2 7 GPIO_ACTIVE_HIGH>; + gpio-sclk = <&gpio2 8 GPIO_ACTIVE_HIGH>; + }; diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index a86a562..734dc1b 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -636,15 +636,6 @@ config RTC_DRV_M41T94 This driver can also be built as a module. If so, the module will be called rtc-m41t94. -config RTC_DRV_DS1302 - tristate "Dallas/Maxim DS1302" - depends on SPI - help - If you say yes here you get support for the Dallas DS1302 RTC chips. - - This driver can also be built as a module. If so, the module - will be called rtc-ds1302. - config RTC_DRV_DS1305 tristate "Dallas/Maxim DS1305/DS1306" help @@ -1639,6 +1630,14 @@ config RTC_DRV_XGENE This driver can also be built as a module, if so, the module will be called "rtc-xgene". +config RTC_DRV_DS1302 + tristate "Dallas/Maxim DS1302" + help + If you say yes here you get support for the Dallas DS1302 RTC chips. + + This driver can also be built as a module. If so, the module + will be called rtc-ds1302. + comment "HID Sensor RTC drivers" config RTC_DRV_HID_SENSOR_TIME diff --git a/drivers/rtc/rtc-ds1302.c b/drivers/rtc/rtc-ds1302.c index 1647848..778f39a 100644 --- a/drivers/rtc/rtc-ds1302.c +++ b/drivers/rtc/rtc-ds1302.c @@ -19,6 +19,10 @@ #include <linux/of.h> #include <linux/rtc.h> #include <linux/spi/spi.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/of_gpio.h> +#include <linux/gpio/consumer.h> #define DRV_NAME "rtc-ds1302" #define DRV_VERSION "1.0.0" @@ -195,6 +199,16 @@ static int ds1302_probe(struct ds1302 *ds1302) return 0; } +#ifdef CONFIG_OF +static const struct of_device_id ds1302_dt_ids[] = { + { .compatible = "maxim,ds1302", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ds1302_dt_ids); +#endif + +#if IS_ENABLED(CONFIG_SPI_MASTER) + static int ds1302_spi_readbyte(struct ds1302 *ds1302, u8 addr) { struct spi_device *spi = to_spi_device(ds1302->dev); @@ -285,21 +299,327 @@ static int ds1302_spi_probe(struct spi_device *spi) return ds1302_probe(ds1302); } -#ifdef CONFIG_OF -static const struct of_device_id ds1302_dt_ids[] = { - { .compatible = "maxim,ds1302", }, - { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, ds1302_dt_ids); -#endif - -static struct spi_driver ds1302_driver = { +static struct spi_driver ds1302_spi_driver = { .driver.name = "rtc-ds1302", .driver.of_match_table = of_match_ptr(ds1302_dt_ids), .probe = ds1302_spi_probe, }; -module_spi_driver(ds1302_driver); +static int ds1302_spi_register_driver(void) +{ + return spi_register_driver(&ds1302_spi_driver); +} + +static void ds1302_spi_unregister_driver(void) +{ + spi_unregister_driver(&ds1302_spi_driver); +} + +#else + +static int ds1302_spi_register_driver(void) +{ + return 0; +} + +static void ds1302_spi_unregister_driver(void) +{ +} + +#endif + +/* + * ds1302 driver using three GPIO lines + * + * The information to implement this is gleaned from + * http://playground.arduino.cc/Main/DS1302 + */ +struct ds1302_gpio { + struct gpio_desc *gpiod_ce; + struct gpio_desc *gpiod_io; + struct gpio_desc *gpiod_sclk; + + struct ds1302 ds1302; +}; + +static struct ds1302_gpio *to_ds1302_gpio(struct ds1302 *ds1302) +{ + return container_of(ds1302, struct ds1302_gpio, ds1302); +} + +static int ds1302_gpio_reset(struct ds1302 *ds1302) +{ + struct ds1302_gpio *gpio = to_ds1302_gpio(ds1302); + int ret; + + ret = gpiod_direction_output(gpio->gpiod_ce, 0); + if (ret) + return ret; + + ret = gpiod_direction_output(gpio->gpiod_io, 0); + if (ret) + return ret; + + return gpiod_direction_output(gpio->gpiod_sclk, 0); +} + +static void ds1302_gpio_chip_enable(struct ds1302 *ds1302, int enable) +{ + struct ds1302_gpio *gpio = to_ds1302_gpio(ds1302); + + gpiod_set_value(gpio->gpiod_ce, enable); + if (enable) { + /* + * tCC (CE to CLK Setup): 4us + */ + udelay(4); + } else { + /* + * tCWH (CE Inactive Time): 4us + */ + udelay(4); + } +} + +static int ds1302_gpio_sendbits(struct ds1302 *ds1302, u8 val) +{ + struct ds1302_gpio *gpio = to_ds1302_gpio(ds1302); + int i; + + for (i = 0; i < 8; i++, val >>= 1) { + gpiod_set_value(gpio->gpiod_sclk, 0); + /* tCL (CLK Low Time): 1000ns */ + udelay(1); + + gpiod_set_value(gpio->gpiod_io, val & 0x1); + /* tDC (Data to CLK Setup): 200ns */ + udelay(1); + + gpiod_set_value(gpio->gpiod_sclk, 1); + /* + * tCH (CLK High Time): 1000ns + * tCDH (CLK to Data Hold): 800ns + */ + udelay(1); + } + + return 0; +} + +static unsigned int ds1302_gpio_recvbits(struct ds1302 *ds1302) +{ + unsigned int val; + int i; + struct ds1302_gpio *gpio = to_ds1302_gpio(ds1302); + + for (i = 0, val = 0; i < 8; i++) { + int bit; + + gpiod_set_value(gpio->gpiod_sclk, 0); + /* + * tCL (CLK Low Time): 1000ns + * tCDD (CLK to Data Delay): 800ns + */ + udelay(1); + + bit = gpiod_get_value(gpio->gpiod_io); + if (bit < 0) + return bit; + val |= bit << i; + + gpiod_set_value(gpio->gpiod_sclk, 1); + /* tCH (CLK High Time): 1000ns */ + udelay(1); + } + + return val; +} + +static int ds1302_gpio_readburst(struct ds1302 *ds1302, u8 addr, u8 *buf, + int size) +{ + int i; + int ret; + struct ds1302_gpio *gpio = to_ds1302_gpio(ds1302); + + ret = ds1302_gpio_reset(ds1302); + if (ret) + return ret; + + ds1302_gpio_chip_enable(ds1302, true); + + ds1302_gpio_sendbits(ds1302, ((addr & 0x3f) << 1) | RTC_CMD_READ); + + ret = gpiod_direction_input(gpio->gpiod_io); + if (ret) + return ret; + + for (i = 0; i < size; i++) { + ret = ds1302_gpio_recvbits(ds1302); + if (ret < 0) + break; + buf[i] = ret; + } + + ds1302_gpio_chip_enable(ds1302, false); + + return ret < 0 ? ret : 0; +} + +static int ds1302_gpio_readbyte(struct ds1302 *ds1302, u8 addr) +{ + u8 val; + int err; + + err = ds1302_gpio_readburst(ds1302, addr, &val, 1); + if (err) + return err; + + return val; +} + +static int ds1302_gpio_writeburst(struct ds1302 *ds1302, u8 addr, const u8 *buf, + int size) +{ + int err; + int i; + + err = ds1302_gpio_reset(ds1302); + if (err) + return err; + + ds1302_gpio_chip_enable(ds1302, true); + + ds1302_gpio_sendbits(ds1302, ((addr & 0x3f) << 1) | RTC_CMD_WRITE); + + for (i = 0; i < size; i++) { + err = ds1302_gpio_sendbits(ds1302, buf[i]); + if (err) + break; + } + + ds1302_gpio_chip_enable(ds1302, false); + + return err; +} + +static int ds1302_gpio_writebyte(struct ds1302 *ds1302, u8 addr, u8 val) +{ + return ds1302_gpio_writeburst(ds1302, addr, &val, 1); +} + +static const struct ds1302_ops ds1302_gpio_ops = { + .readbyte = ds1302_gpio_readbyte, + .writebyte = ds1302_gpio_writebyte, + .readburst = ds1302_gpio_readburst, + .writeburst = ds1302_gpio_writeburst, +}; + +static struct gpio_desc *ds1302_gpiod_request(struct device *dev, + const char *name) +{ + int gpio; + int ret; + + if (!dev->of_node) + return ERR_PTR(-ENODEV); + + gpio = of_get_named_gpio(dev->of_node, name, 0); + if (!gpio_is_valid(gpio)) + return ERR_PTR(gpio); + + ret = devm_gpio_request_one(dev, gpio, 0, name); + if (ret) + return ERR_PTR(ret); + + return gpio_to_desc(gpio); +} + +static struct ds1302 *ds1302_gpio_init(struct device *dev) +{ + struct ds1302_gpio *ds1302_gpio; + struct gpio_desc *gpiod; + + ds1302_gpio = devm_kzalloc(dev, sizeof(*ds1302_gpio), GFP_KERNEL); + if (!ds1302_gpio) + return ERR_PTR(-ENOMEM); + + ds1302_gpio->ds1302.ops = &ds1302_gpio_ops; + ds1302_gpio->ds1302.dev = dev; + + gpiod = ds1302_gpiod_request(dev, "gpio-ce"); + if (IS_ERR(gpiod)) + return ERR_CAST(gpiod); + ds1302_gpio->gpiod_ce = gpiod; + + gpiod = ds1302_gpiod_request(dev, "gpio-io"); + if (IS_ERR(gpiod)) + return ERR_CAST(gpiod); + ds1302_gpio->gpiod_io = gpiod; + + gpiod = ds1302_gpiod_request(dev, "gpio-sclk"); + if (IS_ERR(gpiod)) + return ERR_CAST(gpiod); + ds1302_gpio->gpiod_sclk = gpiod; + + return &ds1302_gpio->ds1302; +} + +static int ds1302_gpio_probe(struct platform_device *pdev) + { + struct ds1302 *ds1302; + + ds1302 = ds1302_gpio_init(&pdev->dev); + if (IS_ERR(ds1302)) + return PTR_ERR(ds1302);; + + return ds1302_probe(ds1302); +} + +static struct platform_driver ds1302_gpio_driver = { + .probe = ds1302_gpio_probe, + .driver = { + .name = "rtc-ds1302", + .of_match_table = of_match_ptr(ds1302_dt_ids), + }, +}; + +static int ds1302_gpio_register_driver(void) +{ + return platform_driver_register(&ds1302_gpio_driver); +} + +static void ds1302_gpio_unregister_driver(void) +{ + return platform_driver_unregister(&ds1302_gpio_driver); +} + +static int __init ds1302_init(void) +{ + int ret; + + ret = ds1302_spi_register_driver(); + if (ret) { + pr_err("Failed to register ds1302 spi driver: %d\n", ret); + return ret; + } + + ret = ds1302_gpio_register_driver(); + if (ret) { + pr_err("Failed to register ds1302 gpio driver: %d\n", ret); + ds1302_spi_unregister_driver(); + } + + return ret; +} +module_init(ds1302_init) + +static void __exit ds1302_exit(void) +{ + ds1302_spi_unregister_driver(); + ds1302_gpio_unregister_driver(); +} +module_exit(ds1302_exit) MODULE_DESCRIPTION("Dallas DS1302 RTC driver"); MODULE_VERSION(DRV_VERSION); -- 2.5.0 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html