| 1 | |
| 2 | |
| 3 | * Loongson RTC driver
|
| 4 | *
|
| 5 | * Maintained out-of-tree by Huacai Chen <[email protected]>.
|
| 6 | * Rewritten for mainline by WANG Xuerui <[email protected]>.
|
| 7 | * Binbin Zhou <[email protected]>
|
| 8 |
|
| 9 |
|
| 10 | #include <linux/bitfield.h>
|
| 11 | #include <linux/kernel.h>
|
| 12 | #include <linux/module.h>
|
| 13 | #include <linux/platform_device.h>
|
| 14 | #include <linux/regmap.h>
|
| 15 | #include <linux/rtc.h>
|
| 16 | #include <linux/acpi.h>
|
| 17 |
|
| 18 |
|
| 19 | #define TOY_TRIM_REG 0x20
|
| 20 | #define TOY_WRITE0_REG 0x24
|
| 21 | #define TOY_WRITE1_REG 0x28
|
| 22 | #define TOY_READ0_REG 0x2c
|
| 23 | #define TOY_READ1_REG 0x30
|
| 24 | #define TOY_MATCH0_REG 0x34
|
| 25 | #define TOY_MATCH1_REG 0x38
|
| 26 | #define TOY_MATCH2_REG 0x3c
|
| 27 |
|
| 28 |
|
| 29 | #define RTC_CTRL_REG 0x40
|
| 30 | #define RTC_TRIM_REG 0x60
|
| 31 | #define RTC_WRITE0_REG 0x64
|
| 32 | #define RTC_READ0_REG 0x68
|
| 33 | #define RTC_MATCH0_REG 0x6c
|
| 34 | #define RTC_MATCH1_REG 0x70
|
| 35 | #define RTC_MATCH2_REG 0x74
|
| 36 |
|
| 37 |
|
| 38 | #define TOY_MON GENMASK(31, 26)
|
| 39 | #define TOY_DAY GENMASK(25, 21)
|
| 40 | #define TOY_HOUR GENMASK(20, 16)
|
| 41 | #define TOY_MIN GENMASK(15, 10)
|
| 42 | #define TOY_SEC GENMASK(9, 4)
|
| 43 | #define TOY_MSEC GENMASK(3, 0)
|
| 44 |
|
| 45 |
|
| 46 | #define TOY_MATCH_YEAR GENMASK(31, 26)
|
| 47 | #define TOY_MATCH_MON GENMASK(25, 22)
|
| 48 | #define TOY_MATCH_DAY GENMASK(21, 17)
|
| 49 | #define TOY_MATCH_HOUR GENMASK(16, 12)
|
| 50 | #define TOY_MATCH_MIN GENMASK(11, 6)
|
| 51 | #define TOY_MATCH_SEC GENMASK(5, 0)
|
| 52 |
|
| 53 |
|
| 54 | #define RTC_ENABLE BIT(13)
|
| 55 | #define TOY_ENABLE BIT(11)
|
| 56 | #define OSC_ENABLE BIT(8)
|
| 57 | #define TOY_ENABLE_MASK (TOY_ENABLE | OSC_ENABLE)
|
| 58 |
|
| 59 |
|
| 60 | #define PM1_STS_REG 0x0c
|
| 61 | #define RTC_STS BIT(10)
|
| 62 | #define PM1_EN_REG 0x10
|
| 63 | #define RTC_EN BIT(10)
|
| 64 |
|
| 65 | |
| 66 | * According to the LS1C manual, RTC_CTRL and alarm-related registers are not defined.
|
| 67 | * Accessing the relevant registers will cause the system to hang.
|
| 68 |
|
| 69 | #define LOONGSON_RTC_CTRL_WORKAROUND BIT(0)
|
| 70 | #define LOONGSON_RTC_ALARM_WORKAROUND BIT(1)
|
| 71 |
|
| 72 | struct loongson_rtc_config {
|
| 73 | u32 pm_offset;
|
| 74 | u32 flags;
|
| 75 | };
|
| 76 |
|
| 77 | struct loongson_rtc_priv {
|
| 78 | spinlock_t lock;
|
| 79 | u32 fix_year;
|
| 80 | struct rtc_device *rtcdev;
|
| 81 | struct regmap *regmap;
|
| 82 | void __iomem *pm_base;
|
| 83 | const struct loongson_rtc_config *config;
|
| 84 | };
|
| 85 |
|
| 86 | static const struct loongson_rtc_config ls1b_rtc_config = {
|
| 87 | .pm_offset = 0,
|
| 88 | .flags = 0,
|
| 89 | };
|
| 90 |
|
| 91 | static const struct loongson_rtc_config ls1c_rtc_config = {
|
| 92 | .pm_offset = 0,
|
| 93 | .flags = LOONGSON_RTC_CTRL_WORKAROUND | LOONGSON_RTC_ALARM_WORKAROUND,
|
| 94 | };
|
| 95 |
|
| 96 | static const struct loongson_rtc_config generic_rtc_config = {
|
| 97 | .pm_offset = 0x100,
|
| 98 | .flags = 0,
|
| 99 | };
|
| 100 |
|
| 101 | static const struct loongson_rtc_config ls2k0300_rtc_config = {
|
| 102 | .pm_offset = 0x0,
|
| 103 | .flags = LOONGSON_RTC_ALARM_WORKAROUND,
|
| 104 | };
|
| 105 |
|
| 106 | static const struct loongson_rtc_config ls2k1000_rtc_config = {
|
| 107 | .pm_offset = 0x800,
|
| 108 | .flags = 0,
|
| 109 | };
|
| 110 |
|
| 111 | static const struct regmap_config loongson_rtc_regmap_config = {
|
| 112 | .reg_bits = 32,
|
| 113 | .val_bits = 32,
|
| 114 | .reg_stride = 4,
|
| 115 | };
|
| 116 |
|
| 117 |
|
| 118 | static irqreturn_t loongson_rtc_isr(int irq, void *id)
|
| 119 | {
|
| 120 | struct loongson_rtc_priv *priv = (struct loongson_rtc_priv *)id;
|
| 121 |
|
| 122 | rtc_update_irq(priv->rtcdev, 1, RTC_AF | RTC_IRQF);
|
| 123 |
|
| 124 | |
| 125 | * The TOY_MATCH0_REG should be cleared 0 here,
|
| 126 | * otherwise the interrupt cannot be cleared.
|
| 127 |
|
| 128 | regmap_write(priv->regmap, TOY_MATCH0_REG, 0);
|
| 129 |
|
| 130 | return IRQ_HANDLED;
|
| 131 | }
|
| 132 |
|
| 133 |
|
| 134 | static u32 loongson_rtc_handler(void *id)
|
| 135 | {
|
| 136 | struct loongson_rtc_priv *priv = (struct loongson_rtc_priv *)id;
|
| 137 |
|
| 138 | rtc_update_irq(priv->rtcdev, 1, RTC_AF | RTC_IRQF);
|
| 139 |
|
| 140 | |
| 141 | * The TOY_MATCH0_REG should be cleared 0 here,
|
| 142 | * otherwise the interrupt cannot be cleared.
|
| 143 |
|
| 144 | regmap_write(priv->regmap, TOY_MATCH0_REG, 0);
|
| 145 |
|
| 146 | spin_lock(&priv->lock);
|
| 147 |
|
| 148 | writel(readl(priv->pm_base + PM1_EN_REG) & ~RTC_EN,
|
| 149 | priv->pm_base + PM1_EN_REG);
|
| 150 |
|
| 151 |
|
| 152 | writel(RTC_STS, priv->pm_base + PM1_STS_REG);
|
| 153 | spin_unlock(&priv->lock);
|
| 154 |
|
| 155 | return ACPI_INTERRUPT_HANDLED;
|
| 156 | }
|
| 157 |
|
| 158 | static int loongson_rtc_set_enabled(struct device *dev)
|
| 159 | {
|
| 160 | struct loongson_rtc_priv *priv = dev_get_drvdata(dev);
|
| 161 |
|
| 162 | if (priv->config->flags & LOONGSON_RTC_CTRL_WORKAROUND)
|
| 163 | return 0;
|
| 164 |
|
| 165 |
|
| 166 | return regmap_update_bits(priv->regmap, RTC_CTRL_REG, TOY_ENABLE_MASK,
|
| 167 | TOY_ENABLE_MASK);
|
| 168 | }
|
| 169 |
|
| 170 | static bool loongson_rtc_get_enabled(struct device *dev)
|
| 171 | {
|
| 172 | int ret;
|
| 173 | u32 ctrl_data;
|
| 174 | struct loongson_rtc_priv *priv = dev_get_drvdata(dev);
|
| 175 |
|
| 176 | if (priv->config->flags & LOONGSON_RTC_CTRL_WORKAROUND)
|
| 177 | return true;
|
| 178 |
|
| 179 | ret = regmap_read(priv->regmap, RTC_CTRL_REG, &ctrl_data);
|
| 180 | if (ret < 0)
|
| 181 | return false;
|
| 182 |
|
| 183 | return ctrl_data & TOY_ENABLE_MASK;
|
| 184 | }
|
| 185 |
|
| 186 | static int loongson_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
| 187 | {
|
| 188 | int ret;
|
| 189 | u32 rtc_data[2];
|
| 190 | struct loongson_rtc_priv *priv = dev_get_drvdata(dev);
|
| 191 |
|
| 192 | if (!loongson_rtc_get_enabled(dev))
|
| 193 | return -EINVAL;
|
| 194 |
|
| 195 | ret = regmap_bulk_read(priv->regmap, TOY_READ0_REG, rtc_data,
|
| 196 | ARRAY_SIZE(rtc_data));
|
| 197 | if (ret < 0)
|
| 198 | return ret;
|
| 199 |
|
| 200 | tm->tm_sec = FIELD_GET(TOY_SEC, rtc_data[0]);
|
| 201 | tm->tm_min = FIELD_GET(TOY_MIN, rtc_data[0]);
|
| 202 | tm->tm_hour = FIELD_GET(TOY_HOUR, rtc_data[0]);
|
| 203 | tm->tm_mday = FIELD_GET(TOY_DAY, rtc_data[0]);
|
| 204 | tm->tm_mon = FIELD_GET(TOY_MON, rtc_data[0]) - 1;
|
| 205 | tm->tm_year = rtc_data[1];
|
| 206 |
|
| 207 |
|
| 208 | priv->fix_year = tm->tm_year / 64 * 64;
|
| 209 | return 0;
|
| 210 | }
|
| 211 |
|
| 212 | static int loongson_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
| 213 | {
|
| 214 | int ret;
|
| 215 | u32 rtc_data[2];
|
| 216 | struct loongson_rtc_priv *priv = dev_get_drvdata(dev);
|
| 217 |
|
| 218 | rtc_data[0] = FIELD_PREP(TOY_SEC, tm->tm_sec)
|
| 219 | | FIELD_PREP(TOY_MIN, tm->tm_min)
|
| 220 | | FIELD_PREP(TOY_HOUR, tm->tm_hour)
|
| 221 | | FIELD_PREP(TOY_DAY, tm->tm_mday)
|
| 222 | | FIELD_PREP(TOY_MON, tm->tm_mon + 1);
|
| 223 | rtc_data[1] = tm->tm_year;
|
| 224 |
|
| 225 | ret = regmap_bulk_write(priv->regmap, TOY_WRITE0_REG, rtc_data,
|
| 226 | ARRAY_SIZE(rtc_data));
|
| 227 | if (ret < 0)
|
| 228 | return ret;
|
| 229 |
|
| 230 | return loongson_rtc_set_enabled(dev);
|
| 231 | }
|
| 232 |
|
| 233 | static int loongson_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
| 234 | {
|
| 235 | int ret;
|
| 236 | u32 alarm_data;
|
| 237 | struct loongson_rtc_priv *priv = dev_get_drvdata(dev);
|
| 238 |
|
| 239 | ret = regmap_read(priv->regmap, TOY_MATCH0_REG, &alarm_data);
|
| 240 | if (ret < 0)
|
| 241 | return ret;
|
| 242 |
|
| 243 | alrm->time.tm_sec = FIELD_GET(TOY_MATCH_SEC, alarm_data);
|
| 244 | alrm->time.tm_min = FIELD_GET(TOY_MATCH_MIN, alarm_data);
|
| 245 | alrm->time.tm_hour = FIELD_GET(TOY_MATCH_HOUR, alarm_data);
|
| 246 | alrm->time.tm_mday = FIELD_GET(TOY_MATCH_DAY, alarm_data);
|
| 247 | alrm->time.tm_mon = FIELD_GET(TOY_MATCH_MON, alarm_data) - 1;
|
| 248 | |
| 249 | * This is a hardware bug: the year field of SYS_TOYMATCH is only 6 bits,
|
| 250 | * making it impossible to save year values larger than 64.
|
| 251 | *
|
| 252 | * SYS_TOYMATCH is used to match the alarm time value and determine if
|
| 253 | * an alarm is triggered, so we must keep the lower 6 bits of the year
|
| 254 | * value constant during the value conversion.
|
| 255 | *
|
| 256 | * In summary, we need to manually add 64(or a multiple of 64) to the
|
| 257 | * year value to avoid the invalid alarm prompt at startup.
|
| 258 |
|
| 259 | alrm->time.tm_year = FIELD_GET(TOY_MATCH_YEAR, alarm_data) + priv->fix_year;
|
| 260 |
|
| 261 | alrm->enabled = !!(readl(priv->pm_base + PM1_EN_REG) & RTC_EN);
|
| 262 | return 0;
|
| 263 | }
|
| 264 |
|
| 265 | static int loongson_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
|
| 266 | {
|
| 267 | u32 val;
|
| 268 | struct loongson_rtc_priv *priv = dev_get_drvdata(dev);
|
| 269 |
|
| 270 | spin_lock(&priv->lock);
|
| 271 | val = readl(priv->pm_base + PM1_EN_REG);
|
| 272 |
|
| 273 | writel(enabled ? val | RTC_EN : val & ~RTC_EN,
|
| 274 | priv->pm_base + PM1_EN_REG);
|
| 275 | spin_unlock(&priv->lock);
|
| 276 |
|
| 277 | return 0;
|
| 278 | }
|
| 279 |
|
| 280 | static int loongson_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
| 281 | {
|
| 282 | int ret;
|
| 283 | u32 alarm_data;
|
| 284 | struct loongson_rtc_priv *priv = dev_get_drvdata(dev);
|
| 285 |
|
| 286 | alarm_data = FIELD_PREP(TOY_MATCH_SEC, alrm->time.tm_sec)
|
| 287 | | FIELD_PREP(TOY_MATCH_MIN, alrm->time.tm_min)
|
| 288 | | FIELD_PREP(TOY_MATCH_HOUR, alrm->time.tm_hour)
|
| 289 | | FIELD_PREP(TOY_MATCH_DAY, alrm->time.tm_mday)
|
| 290 | | FIELD_PREP(TOY_MATCH_MON, alrm->time.tm_mon + 1)
|
| 291 | | FIELD_PREP(TOY_MATCH_YEAR, alrm->time.tm_year - priv->fix_year);
|
| 292 |
|
| 293 | ret = regmap_write(priv->regmap, TOY_MATCH0_REG, alarm_data);
|
| 294 | if (ret < 0)
|
| 295 | return ret;
|
| 296 |
|
| 297 | return loongson_rtc_alarm_irq_enable(dev, alrm->enabled);
|
| 298 | }
|
| 299 |
|
| 300 | static const struct rtc_class_ops loongson_rtc_ops = {
|
| 301 | .read_time = loongson_rtc_read_time,
|
| 302 | .set_time = loongson_rtc_set_time,
|
| 303 | .read_alarm = loongson_rtc_read_alarm,
|
| 304 | .set_alarm = loongson_rtc_set_alarm,
|
| 305 | .alarm_irq_enable = loongson_rtc_alarm_irq_enable,
|
| 306 | };
|
| 307 |
|
| 308 | static int loongson_rtc_alarm_setting(struct platform_device *pdev, void __iomem *regs)
|
| 309 | {
|
| 310 | int ret = 0, alarm_irq;
|
| 311 | struct device *dev = &pdev->dev;
|
| 312 | struct loongson_rtc_priv *priv = dev_get_drvdata(dev);
|
| 313 |
|
| 314 | if (priv->config->flags & LOONGSON_RTC_ALARM_WORKAROUND) {
|
| 315 |
|
| 316 | clear_bit(RTC_FEATURE_ALARM, priv->rtcdev->features);
|
| 317 | return 0;
|
| 318 | }
|
| 319 |
|
| 320 |
|
| 321 | alarm_irq = platform_get_irq(pdev, 0);
|
| 322 | if (alarm_irq < 0)
|
| 323 | return alarm_irq;
|
| 324 |
|
| 325 | ret = devm_request_irq(dev, alarm_irq, loongson_rtc_isr, 0, "loongson-alarm",
|
| 326 | priv);
|
| 327 | if (ret < 0)
|
| 328 | return ret;
|
| 329 |
|
| 330 | priv->pm_base = regs - priv->config->pm_offset;
|
| 331 | device_init_wakeup(dev, true);
|
| 332 |
|
| 333 | if (has_acpi_companion(dev))
|
| 334 | acpi_install_fixed_event_handler(ACPI_EVENT_RTC,
|
| 335 | loongson_rtc_handler, priv);
|
| 336 |
|
| 337 | return ret;
|
| 338 | }
|
| 339 |
|
| 340 | static int loongson_rtc_probe(struct platform_device *pdev)
|
| 341 | {
|
| 342 | int ret;
|
| 343 | void __iomem *regs;
|
| 344 | struct loongson_rtc_priv *priv;
|
| 345 | struct device *dev = &pdev->dev;
|
| 346 |
|
| 347 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
| 348 | if (!priv)
|
| 349 | return -ENOMEM;
|
| 350 |
|
| 351 | regs = devm_platform_ioremap_resource(pdev, 0);
|
| 352 | if (IS_ERR(regs))
|
| 353 | return dev_err_probe(dev, PTR_ERR(regs),
|
| 354 | "devm_platform_ioremap_resource failed\n");
|
| 355 |
|
| 356 | priv->regmap = devm_regmap_init_mmio(dev, regs,
|
| 357 | &loongson_rtc_regmap_config);
|
| 358 | if (IS_ERR(priv->regmap))
|
| 359 | return dev_err_probe(dev, PTR_ERR(priv->regmap),
|
| 360 | "devm_regmap_init_mmio failed\n");
|
| 361 |
|
| 362 | priv->config = device_get_match_data(dev);
|
| 363 | spin_lock_init(&priv->lock);
|
| 364 | platform_set_drvdata(pdev, priv);
|
| 365 |
|
| 366 | priv->rtcdev = devm_rtc_allocate_device(dev);
|
| 367 | if (IS_ERR(priv->rtcdev))
|
| 368 | return dev_err_probe(dev, PTR_ERR(priv->rtcdev),
|
| 369 | "devm_rtc_allocate_device failed\n");
|
| 370 |
|
| 371 | ret = loongson_rtc_alarm_setting(pdev, regs);
|
| 372 | if (ret)
|
| 373 | return ret;
|
| 374 |
|
| 375 |
|
| 376 | clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, priv->rtcdev->features);
|
| 377 | priv->rtcdev->ops = &loongson_rtc_ops;
|
| 378 | priv->rtcdev->range_min = RTC_TIMESTAMP_BEGIN_2000;
|
| 379 | priv->rtcdev->range_max = RTC_TIMESTAMP_END_2099;
|
| 380 |
|
| 381 | return devm_rtc_register_device(priv->rtcdev);
|
| 382 | }
|
| 383 |
|
| 384 | static void loongson_rtc_remove(struct platform_device *pdev)
|
| 385 | {
|
| 386 | struct device *dev = &pdev->dev;
|
| 387 | struct loongson_rtc_priv *priv = dev_get_drvdata(dev);
|
| 388 |
|
| 389 | if (!test_bit(RTC_FEATURE_ALARM, priv->rtcdev->features))
|
| 390 | return;
|
| 391 |
|
| 392 | if (has_acpi_companion(dev))
|
| 393 | acpi_remove_fixed_event_handler(ACPI_EVENT_RTC,
|
| 394 | loongson_rtc_handler);
|
| 395 |
|
| 396 | device_init_wakeup(dev, false);
|
| 397 | loongson_rtc_alarm_irq_enable(dev, 0);
|
| 398 | }
|
| 399 |
|
| 400 | static const struct of_device_id loongson_rtc_of_match[] = {
|
| 401 | { .compatible = "loongson,ls1b-rtc", .data = &ls1b_rtc_config },
|
| 402 | { .compatible = "loongson,ls1c-rtc", .data = &ls1c_rtc_config },
|
| 403 | { .compatible = "loongson,ls7a-rtc", .data = &generic_rtc_config },
|
| 404 | { .compatible = "loongson,ls2k0300-rtc", .data = &ls2k0300_rtc_config },
|
| 405 | { .compatible = "loongson,ls2k1000-rtc", .data = &ls2k1000_rtc_config },
|
| 406 | { }
|
| 407 | };
|
| 408 | MODULE_DEVICE_TABLE(of, loongson_rtc_of_match);
|
| 409 |
|
| 410 | static const struct acpi_device_id loongson_rtc_acpi_match[] = {
|
| 411 | { "LOON0001", .driver_data = (kernel_ulong_t)&generic_rtc_config },
|
| 412 | { }
|
| 413 | };
|
| 414 | MODULE_DEVICE_TABLE(acpi, loongson_rtc_acpi_match);
|
| 415 |
|
| 416 | static struct platform_driver loongson_rtc_driver = {
|
| 417 | .probe = loongson_rtc_probe,
|
| 418 | .remove = loongson_rtc_remove,
|
| 419 | .driver = {
|
| 420 | .name = "loongson-rtc",
|
| 421 | .of_match_table = loongson_rtc_of_match,
|
| 422 | .acpi_match_table = loongson_rtc_acpi_match,
|
| 423 | },
|
| 424 | };
|
| 425 | module_platform_driver(loongson_rtc_driver);
|
| 426 |
|
| 427 | MODULE_DESCRIPTION("Loongson RTC driver");
|
| 428 | MODULE_AUTHOR("Binbin Zhou <[email protected]>");
|
| 429 | MODULE_AUTHOR("WANG Xuerui <[email protected]>");
|
| 430 | MODULE_AUTHOR("Huacai Chen <[email protected]>");
|
| 431 | MODULE_LICENSE("GPL");
|