at master 7.8 kB view raw
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 42struct __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 52struct __packed nct6694_rtc_alarm { 53 u8 sec; 54 u8 min; 55 u8 hour; 56 u8 alarm_en; 57 u8 alarm_pend; 58}; 59 60struct __packed nct6694_rtc_status { 61 u8 irq_en; 62 u8 irq_pend; 63}; 64 65union __packed nct6694_rtc_msg { 66 struct nct6694_rtc_time time; 67 struct nct6694_rtc_alarm alarm; 68 struct nct6694_rtc_status sts; 69}; 70 71struct nct6694_rtc_data { 72 struct nct6694 *nct6694; 73 struct rtc_device *rtc; 74 union nct6694_rtc_msg *msg; 75 int irq; 76}; 77 78static 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 105static 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 127static 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 152static 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 172static 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 193static 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 201static 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 230static void nct6694_irq_dispose_mapping(void *d) 231{ 232 struct nct6694_rtc_data *data = d; 233 234 irq_dispose_mapping(data->irq); 235} 236 237static 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 285static struct platform_driver nct6694_rtc_driver = { 286 .driver = { 287 .name = "nct6694-rtc", 288 }, 289 .probe = nct6694_rtc_probe, 290}; 291 292module_platform_driver(nct6694_rtc_driver); 293 294MODULE_DESCRIPTION("USB-RTC driver for NCT6694"); 295MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>"); 296MODULE_LICENSE("GPL"); 297MODULE_ALIAS("platform:nct6694-rtc");