| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Driver for the RTC found in the SpacemiT P1 PMIC |
| * |
| * Copyright (C) 2025 by RISCstar Solutions Corporation. All rights reserved. |
| */ |
| |
| #include <linux/bits.h> |
| #include <linux/device.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/regmap.h> |
| #include <linux/rtc.h> |
| |
| #define MOD_NAME "spacemit-p1-rtc" |
| |
| /* |
| * Six consecutive 1-byte registers hold the seconds, minutes, hours, |
| * day-of-month, month, and year (respectively). |
| * |
| * The range of values in these registers is: |
| * seconds 0-59 |
| * minutes 0-59 |
| * hours 0-59 |
| * day 0-30 (struct tm is 1-31) |
| * month 0-11 |
| * year years since 2000 (struct tm is since 1900) |
| * |
| * Note that the day and month must be converted after reading and |
| * before writing. |
| */ |
| #define RTC_TIME 0x0d /* Offset of the seconds register */ |
| |
| #define RTC_CTRL 0x1d |
| #define RTC_EN BIT(2) |
| |
| /* Number of attempts to read a consistent time stamp before giving up */ |
| #define RTC_READ_TRIES 20 /* At least 1 */ |
| |
| struct p1_rtc { |
| struct regmap *regmap; |
| struct rtc_device *rtc; |
| }; |
| |
| /* |
| * The P1 hardware documentation states that the register values are |
| * latched to ensure a consistent time snapshot within the registers, |
| * but these are in fact unstable due to a bug in the hardware design. |
| * So we loop until we get two identical readings. |
| */ |
| static int p1_rtc_read_time(struct device *dev, struct rtc_time *t) |
| { |
| struct p1_rtc *p1 = dev_get_drvdata(dev); |
| struct regmap *regmap = p1->regmap; |
| u32 count = RTC_READ_TRIES; |
| u8 seconds; |
| u8 time[6]; |
| int ret; |
| |
| if (!regmap_test_bits(regmap, RTC_CTRL, RTC_EN)) |
| return -EINVAL; /* RTC is disabled */ |
| |
| ret = regmap_bulk_read(regmap, RTC_TIME, time, sizeof(time)); |
| if (ret) |
| return ret; |
| |
| do { |
| seconds = time[0]; |
| ret = regmap_bulk_read(regmap, RTC_TIME, time, sizeof(time)); |
| if (ret) |
| return ret; |
| } while (time[0] != seconds && --count); |
| |
| if (!count) |
| return -EIO; /* Unable to get a consistent result */ |
| |
| t->tm_sec = time[0] & GENMASK(5, 0); |
| t->tm_min = time[1] & GENMASK(5, 0); |
| t->tm_hour = time[2] & GENMASK(4, 0); |
| t->tm_mday = (time[3] & GENMASK(4, 0)) + 1; |
| t->tm_mon = time[4] & GENMASK(3, 0); |
| t->tm_year = (time[5] & GENMASK(5, 0)) + 100; |
| |
| return 0; |
| } |
| |
| /* |
| * The P1 hardware documentation states that values in the registers are |
| * latched so when written they represent a consistent time snapshot. |
| * Nevertheless, this is not guaranteed by the implementation, so we must |
| * disable the RTC while updating it. |
| */ |
| static int p1_rtc_set_time(struct device *dev, struct rtc_time *t) |
| { |
| struct p1_rtc *p1 = dev_get_drvdata(dev); |
| struct regmap *regmap = p1->regmap; |
| u8 time[6]; |
| int ret; |
| |
| time[0] = t->tm_sec; |
| time[1] = t->tm_min; |
| time[2] = t->tm_hour; |
| time[3] = t->tm_mday - 1; |
| time[4] = t->tm_mon; |
| time[5] = t->tm_year - 100; |
| |
| /* Disable the RTC to update; re-enable again when done */ |
| ret = regmap_clear_bits(regmap, RTC_CTRL, RTC_EN); |
| if (ret) |
| return ret; |
| |
| /* If something goes wrong, leave the RTC disabled */ |
| ret = regmap_bulk_write(regmap, RTC_TIME, time, sizeof(time)); |
| if (ret) |
| return ret; |
| |
| return regmap_set_bits(regmap, RTC_CTRL, RTC_EN); |
| } |
| |
| static const struct rtc_class_ops p1_rtc_class_ops = { |
| .read_time = p1_rtc_read_time, |
| .set_time = p1_rtc_set_time, |
| }; |
| |
| static int p1_rtc_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct rtc_device *rtc; |
| struct p1_rtc *p1; |
| |
| p1 = devm_kzalloc(dev, sizeof(*p1), GFP_KERNEL); |
| if (!p1) |
| return -ENOMEM; |
| dev_set_drvdata(dev, p1); |
| |
| p1->regmap = dev_get_regmap(dev->parent, NULL); |
| if (!p1->regmap) |
| return dev_err_probe(dev, -ENODEV, "failed to get regmap\n"); |
| |
| rtc = devm_rtc_allocate_device(dev); |
| if (IS_ERR(rtc)) |
| return dev_err_probe(dev, PTR_ERR(rtc), |
| "error allocating device\n"); |
| p1->rtc = rtc; |
| |
| rtc->ops = &p1_rtc_class_ops; |
| rtc->range_min = RTC_TIMESTAMP_BEGIN_2000; |
| rtc->range_max = RTC_TIMESTAMP_END_2063; |
| |
| clear_bit(RTC_FEATURE_ALARM, rtc->features); |
| clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->features); |
| |
| return devm_rtc_register_device(rtc); |
| } |
| |
| static struct platform_driver p1_rtc_driver = { |
| .probe = p1_rtc_probe, |
| .driver = { |
| .name = MOD_NAME, |
| }, |
| }; |
| |
| module_platform_driver(p1_rtc_driver); |
| |
| MODULE_DESCRIPTION("SpacemiT P1 RTC driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_ALIAS("platform:" MOD_NAME); |