|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | +/* |
| 3 | + * Nuvoton NCT6694 RTC driver based on USB interface. |
| 4 | + * |
| 5 | + * Copyright (C) 2025 Nuvoton Technology Corp. |
| 6 | + */ |
| 7 | + |
| 8 | +#include <linux/bcd.h> |
| 9 | +#include <linux/irqdomain.h> |
| 10 | +#include <linux/kernel.h> |
| 11 | +#include <linux/mfd/nct6694.h> |
| 12 | +#include <linux/module.h> |
| 13 | +#include <linux/platform_device.h> |
| 14 | +#include <linux/rtc.h> |
| 15 | +#include <linux/slab.h> |
| 16 | + |
| 17 | +/* |
| 18 | + * USB command module type for NCT6694 RTC controller. |
| 19 | + * This defines the module type used for communication with the NCT6694 |
| 20 | + * RTC controller over the USB interface. |
| 21 | + */ |
| 22 | +#define NCT6694_RTC_MOD 0x08 |
| 23 | + |
| 24 | +/* Command 00h - RTC Time */ |
| 25 | +#define NCT6694_RTC_TIME 0x0000 |
| 26 | +#define NCT6694_RTC_TIME_SEL 0x00 |
| 27 | + |
| 28 | +/* Command 01h - RTC Alarm */ |
| 29 | +#define NCT6694_RTC_ALARM 0x01 |
| 30 | +#define NCT6694_RTC_ALARM_SEL 0x00 |
| 31 | + |
| 32 | +/* Command 02h - RTC Status */ |
| 33 | +#define NCT6694_RTC_STATUS 0x02 |
| 34 | +#define NCT6694_RTC_STATUS_SEL 0x00 |
| 35 | + |
| 36 | +#define NCT6694_RTC_IRQ_INT_EN BIT(0) /* Transmit a USB INT-in when RTC alarm */ |
| 37 | +#define NCT6694_RTC_IRQ_GPO_EN BIT(5) /* Trigger a GPO Low Pulse when RTC alarm */ |
| 38 | + |
| 39 | +#define NCT6694_RTC_IRQ_EN (NCT6694_RTC_IRQ_INT_EN | NCT6694_RTC_IRQ_GPO_EN) |
| 40 | +#define NCT6694_RTC_IRQ_STS BIT(0) /* Write 1 clear IRQ status */ |
| 41 | + |
| 42 | +struct __packed nct6694_rtc_time { |
| 43 | + u8 sec; |
| 44 | + u8 min; |
| 45 | + u8 hour; |
| 46 | + u8 week; |
| 47 | + u8 day; |
| 48 | + u8 month; |
| 49 | + u8 year; |
| 50 | +}; |
| 51 | + |
| 52 | +struct __packed nct6694_rtc_alarm { |
| 53 | + u8 sec; |
| 54 | + u8 min; |
| 55 | + u8 hour; |
| 56 | + u8 alarm_en; |
| 57 | + u8 alarm_pend; |
| 58 | +}; |
| 59 | + |
| 60 | +struct __packed nct6694_rtc_status { |
| 61 | + u8 irq_en; |
| 62 | + u8 irq_pend; |
| 63 | +}; |
| 64 | + |
| 65 | +union __packed nct6694_rtc_msg { |
| 66 | + struct nct6694_rtc_time time; |
| 67 | + struct nct6694_rtc_alarm alarm; |
| 68 | + struct nct6694_rtc_status sts; |
| 69 | +}; |
| 70 | + |
| 71 | +struct nct6694_rtc_data { |
| 72 | + struct nct6694 *nct6694; |
| 73 | + struct rtc_device *rtc; |
| 74 | + union nct6694_rtc_msg *msg; |
| 75 | + int irq; |
| 76 | +}; |
| 77 | + |
| 78 | +static int nct6694_rtc_read_time(struct device *dev, struct rtc_time *tm) |
| 79 | +{ |
| 80 | + struct nct6694_rtc_data *data = dev_get_drvdata(dev); |
| 81 | + struct nct6694_rtc_time *time = &data->msg->time; |
| 82 | + static const struct nct6694_cmd_header cmd_hd = { |
| 83 | + .mod = NCT6694_RTC_MOD, |
| 84 | + .cmd = NCT6694_RTC_TIME, |
| 85 | + .sel = NCT6694_RTC_TIME_SEL, |
| 86 | + .len = cpu_to_le16(sizeof(*time)) |
| 87 | + }; |
| 88 | + int ret; |
| 89 | + |
| 90 | + ret = nct6694_read_msg(data->nct6694, &cmd_hd, time); |
| 91 | + if (ret) |
| 92 | + return ret; |
| 93 | + |
| 94 | + tm->tm_sec = bcd2bin(time->sec); /* tm_sec expect 0 ~ 59 */ |
| 95 | + tm->tm_min = bcd2bin(time->min); /* tm_min expect 0 ~ 59 */ |
| 96 | + tm->tm_hour = bcd2bin(time->hour); /* tm_hour expect 0 ~ 23 */ |
| 97 | + tm->tm_wday = bcd2bin(time->week) - 1; /* tm_wday expect 0 ~ 6 */ |
| 98 | + tm->tm_mday = bcd2bin(time->day); /* tm_mday expect 1 ~ 31 */ |
| 99 | + tm->tm_mon = bcd2bin(time->month) - 1; /* tm_month expect 0 ~ 11 */ |
| 100 | + tm->tm_year = bcd2bin(time->year) + 100; /* tm_year expect since 1900 */ |
| 101 | + |
| 102 | + return ret; |
| 103 | +} |
| 104 | + |
| 105 | +static int nct6694_rtc_set_time(struct device *dev, struct rtc_time *tm) |
| 106 | +{ |
| 107 | + struct nct6694_rtc_data *data = dev_get_drvdata(dev); |
| 108 | + struct nct6694_rtc_time *time = &data->msg->time; |
| 109 | + static const struct nct6694_cmd_header cmd_hd = { |
| 110 | + .mod = NCT6694_RTC_MOD, |
| 111 | + .cmd = NCT6694_RTC_TIME, |
| 112 | + .sel = NCT6694_RTC_TIME_SEL, |
| 113 | + .len = cpu_to_le16(sizeof(*time)) |
| 114 | + }; |
| 115 | + |
| 116 | + time->sec = bin2bcd(tm->tm_sec); |
| 117 | + time->min = bin2bcd(tm->tm_min); |
| 118 | + time->hour = bin2bcd(tm->tm_hour); |
| 119 | + time->week = bin2bcd(tm->tm_wday + 1); |
| 120 | + time->day = bin2bcd(tm->tm_mday); |
| 121 | + time->month = bin2bcd(tm->tm_mon + 1); |
| 122 | + time->year = bin2bcd(tm->tm_year - 100); |
| 123 | + |
| 124 | + return nct6694_write_msg(data->nct6694, &cmd_hd, time); |
| 125 | +} |
| 126 | + |
| 127 | +static int nct6694_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) |
| 128 | +{ |
| 129 | + struct nct6694_rtc_data *data = dev_get_drvdata(dev); |
| 130 | + struct nct6694_rtc_alarm *alarm = &data->msg->alarm; |
| 131 | + static const struct nct6694_cmd_header cmd_hd = { |
| 132 | + .mod = NCT6694_RTC_MOD, |
| 133 | + .cmd = NCT6694_RTC_ALARM, |
| 134 | + .sel = NCT6694_RTC_ALARM_SEL, |
| 135 | + .len = cpu_to_le16(sizeof(*alarm)) |
| 136 | + }; |
| 137 | + int ret; |
| 138 | + |
| 139 | + ret = nct6694_read_msg(data->nct6694, &cmd_hd, alarm); |
| 140 | + if (ret) |
| 141 | + return ret; |
| 142 | + |
| 143 | + alrm->time.tm_sec = bcd2bin(alarm->sec); |
| 144 | + alrm->time.tm_min = bcd2bin(alarm->min); |
| 145 | + alrm->time.tm_hour = bcd2bin(alarm->hour); |
| 146 | + alrm->enabled = alarm->alarm_en; |
| 147 | + alrm->pending = alarm->alarm_pend; |
| 148 | + |
| 149 | + return ret; |
| 150 | +} |
| 151 | + |
| 152 | +static int nct6694_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) |
| 153 | +{ |
| 154 | + struct nct6694_rtc_data *data = dev_get_drvdata(dev); |
| 155 | + struct nct6694_rtc_alarm *alarm = &data->msg->alarm; |
| 156 | + static const struct nct6694_cmd_header cmd_hd = { |
| 157 | + .mod = NCT6694_RTC_MOD, |
| 158 | + .cmd = NCT6694_RTC_ALARM, |
| 159 | + .sel = NCT6694_RTC_ALARM_SEL, |
| 160 | + .len = cpu_to_le16(sizeof(*alarm)) |
| 161 | + }; |
| 162 | + |
| 163 | + alarm->sec = bin2bcd(alrm->time.tm_sec); |
| 164 | + alarm->min = bin2bcd(alrm->time.tm_min); |
| 165 | + alarm->hour = bin2bcd(alrm->time.tm_hour); |
| 166 | + alarm->alarm_en = alrm->enabled ? NCT6694_RTC_IRQ_EN : 0; |
| 167 | + alarm->alarm_pend = 0; |
| 168 | + |
| 169 | + return nct6694_write_msg(data->nct6694, &cmd_hd, alarm); |
| 170 | +} |
| 171 | + |
| 172 | +static int nct6694_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) |
| 173 | +{ |
| 174 | + struct nct6694_rtc_data *data = dev_get_drvdata(dev); |
| 175 | + struct nct6694_rtc_status *sts = &data->msg->sts; |
| 176 | + static const struct nct6694_cmd_header cmd_hd = { |
| 177 | + .mod = NCT6694_RTC_MOD, |
| 178 | + .cmd = NCT6694_RTC_STATUS, |
| 179 | + .sel = NCT6694_RTC_STATUS_SEL, |
| 180 | + .len = cpu_to_le16(sizeof(*sts)) |
| 181 | + }; |
| 182 | + |
| 183 | + if (enabled) |
| 184 | + sts->irq_en |= NCT6694_RTC_IRQ_EN; |
| 185 | + else |
| 186 | + sts->irq_en &= ~NCT6694_RTC_IRQ_EN; |
| 187 | + |
| 188 | + sts->irq_pend = 0; |
| 189 | + |
| 190 | + return nct6694_write_msg(data->nct6694, &cmd_hd, sts); |
| 191 | +} |
| 192 | + |
| 193 | +static const struct rtc_class_ops nct6694_rtc_ops = { |
| 194 | + .read_time = nct6694_rtc_read_time, |
| 195 | + .set_time = nct6694_rtc_set_time, |
| 196 | + .read_alarm = nct6694_rtc_read_alarm, |
| 197 | + .set_alarm = nct6694_rtc_set_alarm, |
| 198 | + .alarm_irq_enable = nct6694_rtc_alarm_irq_enable, |
| 199 | +}; |
| 200 | + |
| 201 | +static irqreturn_t nct6694_irq(int irq, void *dev_id) |
| 202 | +{ |
| 203 | + struct nct6694_rtc_data *data = dev_id; |
| 204 | + struct nct6694_rtc_status *sts = &data->msg->sts; |
| 205 | + static const struct nct6694_cmd_header cmd_hd = { |
| 206 | + .mod = NCT6694_RTC_MOD, |
| 207 | + .cmd = NCT6694_RTC_STATUS, |
| 208 | + .sel = NCT6694_RTC_STATUS_SEL, |
| 209 | + .len = cpu_to_le16(sizeof(*sts)) |
| 210 | + }; |
| 211 | + int ret; |
| 212 | + |
| 213 | + rtc_lock(data->rtc); |
| 214 | + |
| 215 | + sts->irq_en = NCT6694_RTC_IRQ_EN; |
| 216 | + sts->irq_pend = NCT6694_RTC_IRQ_STS; |
| 217 | + ret = nct6694_write_msg(data->nct6694, &cmd_hd, sts); |
| 218 | + if (ret) { |
| 219 | + rtc_unlock(data->rtc); |
| 220 | + return IRQ_NONE; |
| 221 | + } |
| 222 | + |
| 223 | + rtc_update_irq(data->rtc, 1, RTC_IRQF | RTC_AF); |
| 224 | + |
| 225 | + rtc_unlock(data->rtc); |
| 226 | + |
| 227 | + return IRQ_HANDLED; |
| 228 | +} |
| 229 | + |
| 230 | +static void nct6694_irq_dispose_mapping(void *d) |
| 231 | +{ |
| 232 | + struct nct6694_rtc_data *data = d; |
| 233 | + |
| 234 | + irq_dispose_mapping(data->irq); |
| 235 | +} |
| 236 | + |
| 237 | +static int nct6694_rtc_probe(struct platform_device *pdev) |
| 238 | +{ |
| 239 | + struct nct6694_rtc_data *data; |
| 240 | + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent); |
| 241 | + int ret; |
| 242 | + |
| 243 | + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); |
| 244 | + if (!data) |
| 245 | + return -ENOMEM; |
| 246 | + |
| 247 | + data->msg = devm_kzalloc(&pdev->dev, sizeof(union nct6694_rtc_msg), |
| 248 | + GFP_KERNEL); |
| 249 | + if (!data->msg) |
| 250 | + return -ENOMEM; |
| 251 | + |
| 252 | + data->irq = irq_create_mapping(nct6694->domain, NCT6694_IRQ_RTC); |
| 253 | + if (!data->irq) |
| 254 | + return -EINVAL; |
| 255 | + |
| 256 | + ret = devm_add_action_or_reset(&pdev->dev, nct6694_irq_dispose_mapping, |
| 257 | + data); |
| 258 | + if (ret) |
| 259 | + return ret; |
| 260 | + |
| 261 | + ret = devm_device_init_wakeup(&pdev->dev); |
| 262 | + if (ret) |
| 263 | + return dev_err_probe(&pdev->dev, ret, "Failed to init wakeup\n"); |
| 264 | + |
| 265 | + data->rtc = devm_rtc_allocate_device(&pdev->dev); |
| 266 | + if (IS_ERR(data->rtc)) |
| 267 | + return PTR_ERR(data->rtc); |
| 268 | + |
| 269 | + data->nct6694 = nct6694; |
| 270 | + data->rtc->ops = &nct6694_rtc_ops; |
| 271 | + data->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000; |
| 272 | + data->rtc->range_max = RTC_TIMESTAMP_END_2099; |
| 273 | + |
| 274 | + platform_set_drvdata(pdev, data); |
| 275 | + |
| 276 | + ret = devm_request_threaded_irq(&pdev->dev, data->irq, NULL, |
| 277 | + nct6694_irq, IRQF_ONESHOT, |
| 278 | + "rtc-nct6694", data); |
| 279 | + if (ret < 0) |
| 280 | + return dev_err_probe(&pdev->dev, ret, "Failed to request irq\n"); |
| 281 | + |
| 282 | + return devm_rtc_register_device(data->rtc); |
| 283 | +} |
| 284 | + |
| 285 | +static struct platform_driver nct6694_rtc_driver = { |
| 286 | + .driver = { |
| 287 | + .name = "nct6694-rtc", |
| 288 | + }, |
| 289 | + .probe = nct6694_rtc_probe, |
| 290 | +}; |
| 291 | + |
| 292 | +module_platform_driver(nct6694_rtc_driver); |
| 293 | + |
| 294 | +MODULE_DESCRIPTION("USB-RTC driver for NCT6694"); |
| 295 | +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>"); |
| 296 | +MODULE_LICENSE("GPL"); |
| 297 | +MODULE_ALIAS("platform:nct6694-rtc"); |
0 commit comments