/* * rtc-ds13307.c - RTC driver for the DS1307 and DS1337 I2C chips. * * Copyright (C) 2018 Helmut Pozimski * * 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 #define M_NAME "rtc-ds13307" /* Module name */ #define DEVICE_DS1307 1 #define DEVICE_DS1337 2 #define COMMON_SEC 0x00 #define COMMON_MIN 0x01 #define COMMON_HOUR 0x02 #define COMMON_DAY 0x03 #define COMMON_DATE 0x04 #define COMMON_MONTH 0x05 #define COMMON_YEAR 0x06 #define COMMON_HIGH_BIT 0x80 #define DS1307_NVRAM_BASE 0x08 #define DS1307_MAX_ADDR 0x3F #define DS1337_CTL 0x0E #define DS1337_STAT 0x0F #define DS1337_ALRM1 0x07 #define IOCTL_DS13307_ALRM _IOR('p', 0x15, u8) /* ioctl to read alarm bit */ static int model_detected; static struct i2c_driver ds13307_driver; static struct nvmem_config ds1307_nvmem; static struct regmap_config regmap_config = { .reg_bits = 8, .val_bits = 8, }; /* Reads a specified number of bytes via i2c, returns 0 on success */ static int ds13307_read_bytes(struct regmap *regmap, u8 *addr, u8 *bytes, int length) { int r; r = regmap_bulk_read(regmap, (*addr), bytes, length); if (r == length) { return 0; } else { return r; } } /* Writes a specified number of bytes via 2ic, returns 0 on success */ static int ds13307_write_bytes(struct regmap *regmap, u8 *addr, u8 *bytes, int length) { int r; r = regmap_bulk_write(regmap, (*addr), bytes, length); if (r == length) { return 0; } else { return r; } } /* The oscillator is stopped for both chips when power is first applied, * therefore this function checks its status and clears the stop bit. */ static int ds13307_start_oscillator(struct regmap *regmap) { u8 data, addr, buf; int r, v = 0; if (model_detected == DEVICE_DS1307) { addr = COMMON_SEC; } else { addr = DS1337_STAT; } r = ds13307_read_bytes(regmap, &addr, &data, 1); if (data & COMMON_HIGH_BIT) { buf = data ^ COMMON_HIGH_BIT; v = ds13307_write_bytes(regmap, &addr, &buf, 1); if (!v) { printk(KERN_DEBUG "%s: oscillator stop bit successfully cleared\n", M_NAME); } } if ((r<0) || (v)) { return -EIO; } return 0; } /* Checks if the hour value is set to 12h format and checks if it * was AM or PM, returns 0 if the value was not in 12h format * or the value was AM so no conversion is necessary */ static int ds13307_check_12h_am_pm(u8 *byte) { if (*byte & 0x40) { (*byte) = *byte ^ 0x40; if (*byte & 0x20) { (*byte) = *byte ^ 0x20; return 1; } } return 0; } static int ds13307_read_time(struct device *dev, struct rtc_time *time) { struct regmap *regmap; int r, century = 1, h12 = 0; u8 buf[7], stopbit; u8 addr = COMMON_SEC; regmap = dev_get_drvdata(dev); r = ds13307_read_bytes(regmap, &addr, buf, 7); if (model_detected == DEVICE_DS1337) { addr = DS1337_STAT; r = ds13307_read_bytes(regmap, &addr, &stopbit, 1); stopbit = stopbit & COMMON_HIGH_BIT; } else { stopbit = buf[COMMON_SEC] & COMMON_HIGH_BIT; } if (r) { return -EIO; } if (stopbit) { printk(KERN_ERR "%s: Oscillator stop bit is set, values read from rtc device cannot be trusted\n", M_NAME); return -EINVAL; } h12 = ds13307_check_12h_am_pm(&buf[COMMON_HOUR]); if (model_detected == DEVICE_DS1337) { century = buf[COMMON_MONTH] & COMMON_HIGH_BIT; buf[COMMON_MONTH] = buf[COMMON_MONTH] & 0x7F; } time->tm_sec = bcd2bin(buf[COMMON_SEC]); time->tm_min = bcd2bin(buf[COMMON_MIN]); time->tm_hour = bcd2bin(buf[COMMON_HOUR]); if(h12) { time->tm_hour += 12; } time->tm_mday = bcd2bin(buf[COMMON_DATE]); time->tm_wday = bcd2bin(buf[COMMON_DAY]); time->tm_mon = bcd2bin(buf[COMMON_MONTH]) - 1 ; if (century) { time->tm_year = bcd2bin(buf[COMMON_YEAR]) + 100; } else { time->tm_year = bcd2bin(buf[COMMON_YEAR]); } return 0; } static int ds13307_set_time(struct device *dev, struct rtc_time *time) { struct regmap *regmap; u8 buf[7], addr; regmap = dev_get_drvdata(dev); if (ds13307_start_oscillator(regmap)) { printk(KERN_ERR "%s: failed to initialize the oscillator\n", M_NAME); return -EIO; } addr = COMMON_SEC; buf[0] = bin2bcd(time->tm_sec); buf[1] = bin2bcd(time->tm_min); buf[2] = bin2bcd(time->tm_hour); buf[3] = bin2bcd(time->tm_wday); buf[4] = bin2bcd(time->tm_mday); if ((model_detected == DEVICE_DS1337) && (time->tm_year >= 100)) { buf[5] = bin2bcd(time->tm_mon + 1) | COMMON_HIGH_BIT; } else if ((model_detected == DEVICE_DS1307) && (time->tm_year < 100)) { printk(KERN_ERR "%s: device does not support century information, dates before 2000 are not possible\n", M_NAME); return -EINVAL; } else { buf[5] = bin2bcd(time->tm_mon + 1); } buf[6] = bin2bcd(time->tm_year % 100); if(ds13307_write_bytes(regmap, &addr, buf, 7)) { return -EIO; } return 0; } static int ds13307_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) { struct regmap *regmap; u8 buf[9], addr = DS1337_ALRM1; int h12; regmap = dev_get_drvdata(dev); if (!ds13307_read_bytes(regmap, &addr, buf, 9)) { // select for A1IE bit alarm->enabled = buf[7] & 0x01; // select for A1F bit alarm->pending = buf[8] & 0x01; alarm->time.tm_sec = bcd2bin(buf[0] & 0x7F); alarm->time.tm_min = bcd2bin(buf[1] & 0x7F); h12 = ds13307_check_12h_am_pm(&buf[2]); alarm->time.tm_hour = bcd2bin(buf[2] & 0x7F); if(h12) { alarm->time.tm_hour += 12; } /* if bit is set to 1, it matches the day of the week, * otherwise the day of the month. */ if (buf[3] & 0x40) { alarm->time.tm_wday = bcd2bin(buf[3] & 0x3F); } else { alarm->time.tm_mday = bcd2bin(buf[3] & 0x3F); } return 0; } return -EIO; } /* Sets the alarm to the values passed to the function */ static int ds13307_write_alarm(struct device *dev, struct rtc_wkalrm *alarm) { struct regmap *regmap; u8 buf[9], addr = DS1337_ALRM1; regmap = dev_get_drvdata(dev); if(!ds13307_read_bytes(regmap, &addr, buf, 9)) { // enable or disable the alarm as requested if ((alarm->enabled && (!(buf[7] & 0x01))) || ((!alarm->enabled && (buf[7] & 0x01)))) { buf[7] ^= 0x01; } // disable alarm 2 if (buf[7] & 0x02) { buf[7] ^= 0x02; } // clear the status bit if (buf[8] & 0x01) { buf[8] ^= 0x01; } buf[0] = bin2bcd(alarm->time.tm_sec); buf[1] = bin2bcd(alarm->time.tm_min); buf[2] = bin2bcd(alarm->time.tm_hour); buf[3] = bin2bcd(alarm->time.tm_mday); if(!ds13307_write_bytes(regmap, &addr, buf, 9)) { return 0; } } return -EIO; } static int ds13307_alarm_irq_enable(struct device *dev, unsigned int enabled) { struct regmap *regmap; u8 buf, addr = DS1337_CTL; regmap = dev_get_drvdata(dev); if (ds13307_read_bytes(regmap, &addr, &buf, 1)) { return -EIO; } if ((!enabled && (buf & 0x01)) || (enabled & (!(buf & 0x01)))) { buf ^= 0x01; } if(!ds13307_write_bytes(regmap, &addr, &buf, 1)) { return 0; } return -EIO; } /* Reads the alarm bit and copies it to userspace */ static int ds13307_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) { struct regmap *regmap; u8 addr, buf; switch(cmd) { case IOCTL_DS13307_ALRM: addr = DS1337_STAT; regmap = dev_get_drvdata(dev); if (ds13307_read_bytes(regmap, &addr, &buf, 1)) { return -EIO; } buf &= 0x01; if (copy_to_user((void __user *)arg, &buf, sizeof(u8))) { return -EFAULT; } return 0; default: return -ENOIOCTLCMD; } } static struct rtc_class_ops ds13307_rtc_ops = { .read_time = ds13307_read_time, .set_time = ds13307_set_time }; /* Performs the device detection to distinguish between the * DS1307 and DS1337 chips. Returns the defined device ID or * -1 on failure */ static int ds13307_detect_device(struct regmap *regmap) { u8 data, addr = DS1307_MAX_ADDR; int result; result = ds13307_read_bytes(regmap, &addr, &data, 1); if (!result) { printk(KERN_INFO "%s: Detected device DS1307\n", M_NAME); return DEVICE_DS1307; } addr = DS1337_CTL; result = ds13307_read_bytes(regmap, &addr, &data, 1); if (!result) { printk(KERN_INFO "%s: Detected device DS1337\n", M_NAME); return DEVICE_DS1337; } printk(KERN_ERR "%s: Could not talk to I2C device at addr %x, is it connected?\n", M_NAME, 0x68); return -1; } static int ds13307_nvram_read(void *priv, unsigned int offset, void *buf, size_t count) { struct regmap *regmap = priv; return regmap_bulk_read(regmap, DS1307_NVRAM_BASE + offset, buf, count); } static int ds13307_nvram_write(void *priv, unsigned int offset, void *buf, size_t count) { struct regmap * regmap = priv; return regmap_bulk_write(regmap, DS1307_NVRAM_BASE + offset, buf, count); } static int ds13307_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct rtc_device *rtc; int error; struct regmap * regmap; regmap = devm_regmap_init_i2c(client, ®map_config); if (IS_ERR(regmap)) { return PTR_ERR(regmap); } model_detected = ds13307_detect_device(regmap); if ((model_detected != DEVICE_DS1307) && (model_detected != DEVICE_DS1337)) { return -EIO; } rtc = devm_rtc_allocate_device(&client->dev); if (IS_ERR(rtc)) { return PTR_ERR(rtc); } rtc->uie_unsupported = 1; device_set_wakeup_capable(&client->dev, 0); client->irq = 0; if (model_detected == DEVICE_DS1307) { ds1307_nvmem.name = "ds1307_nvram"; ds1307_nvmem.word_size = 1; ds1307_nvmem.size = 56; ds1307_nvmem.reg_read = ds13307_nvram_read; ds1307_nvmem.reg_write = ds13307_nvram_write; ds1307_nvmem.priv = regmap; #if LINUX_VERSION_CODE < KERNEL_VERSION(4,17,0) rtc->nvmem_config = &ds1307_nvmem; #else rtc_nvmem_register(rtc, &ds1307_nvmem); #endif } else { ds13307_rtc_ops.read_alarm = ds13307_read_alarm; ds13307_rtc_ops.set_alarm = ds13307_write_alarm; ds13307_rtc_ops.alarm_irq_enable = ds13307_alarm_irq_enable; ds13307_rtc_ops.ioctl = ds13307_ioctl; } rtc->ops = &ds13307_rtc_ops; i2c_set_clientdata(client,rtc); dev_set_drvdata(&client->dev, regmap); error = rtc_register_device(rtc); return error ? error: 0; } static struct i2c_device_id ds13307_idtable[] = { { "ds1307", 0 }, { "ds1337", 0 }, {} }; MODULE_DEVICE_TABLE(i2c, ds13307_idtable); static struct of_device_id ds13307_of_match[] = { { .compatible = "dallas,ds1307" }, { .compatible = "dallas,ds1337" }, {} }; MODULE_DEVICE_TABLE(of, ds13307_of_match); static struct i2c_driver ds13307_driver = { .driver = { .name = M_NAME, .of_match_table = of_match_ptr(ds13307_of_match), .owner = THIS_MODULE }, .id_table = ds13307_idtable, .probe = ds13307_probe, }; module_i2c_driver(ds13307_driver); MODULE_AUTHOR("Helmut Pozimski "); MODULE_DESCRIPTION("DS1307 and DS1337 RTC driver"); MODULE_LICENSE("GPL");