Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Anirudh Ghayal | 902 | 38.30% | 1 | 3.12% |
Johan Hovold | 634 | 26.92% | 13 | 40.62% |
Stanimir Varbanov | 373 | 15.84% | 1 | 3.12% |
Julia Cartwright | 233 | 9.89% | 4 | 12.50% |
satya priya | 45 | 1.91% | 1 | 3.12% |
Loic Poulain | 42 | 1.78% | 1 | 3.12% |
Mohit Aggarwal | 37 | 1.57% | 1 | 3.12% |
Alexandre Belloni | 27 | 1.15% | 3 | 9.38% |
Jingoo Han | 19 | 0.81% | 1 | 3.12% |
Guixiong Wei | 19 | 0.81% | 1 | 3.12% |
韩科才 | 17 | 0.72% | 1 | 3.12% |
Andy Shevchenko | 4 | 0.17% | 1 | 3.12% |
Bartosz Golaszewski | 1 | 0.04% | 1 | 3.12% |
Thomas Gleixner | 1 | 0.04% | 1 | 3.12% |
Axel Lin | 1 | 0.04% | 1 | 3.12% |
Total | 2355 | 32 |
// SPDX-License-Identifier: GPL-2.0-only /* * pm8xxx RTC driver * * Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. * Copyright (c) 2023, Linaro Limited */ #include <linux/of.h> #include <linux/module.h> #include <linux/nvmem-consumer.h> #include <linux/init.h> #include <linux/rtc.h> #include <linux/platform_device.h> #include <linux/pm.h> #include <linux/pm_wakeirq.h> #include <linux/regmap.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <asm/unaligned.h> /* RTC_CTRL register bit fields */ #define PM8xxx_RTC_ENABLE BIT(7) #define PM8xxx_RTC_ALARM_CLEAR BIT(0) #define PM8xxx_RTC_ALARM_ENABLE BIT(7) #define NUM_8_BIT_RTC_REGS 0x4 /** * struct pm8xxx_rtc_regs - describe RTC registers per PMIC versions * @ctrl: address of control register * @write: base address of write registers * @read: base address of read registers * @alarm_ctrl: address of alarm control register * @alarm_ctrl2: address of alarm control2 register * @alarm_rw: base address of alarm read-write registers * @alarm_en: alarm enable mask */ struct pm8xxx_rtc_regs { unsigned int ctrl; unsigned int write; unsigned int read; unsigned int alarm_ctrl; unsigned int alarm_ctrl2; unsigned int alarm_rw; unsigned int alarm_en; }; /** * struct pm8xxx_rtc - RTC driver internal structure * @rtc: RTC device * @regmap: regmap used to access registers * @allow_set_time: whether the time can be set * @alarm_irq: alarm irq number * @regs: register description * @dev: device structure * @nvmem_cell: nvmem cell for offset * @offset: offset from epoch in seconds */ struct pm8xxx_rtc { struct rtc_device *rtc; struct regmap *regmap; bool allow_set_time; int alarm_irq; const struct pm8xxx_rtc_regs *regs; struct device *dev; struct nvmem_cell *nvmem_cell; u32 offset; }; static int pm8xxx_rtc_read_nvmem_offset(struct pm8xxx_rtc *rtc_dd) { size_t len; void *buf; int rc; buf = nvmem_cell_read(rtc_dd->nvmem_cell, &len); if (IS_ERR(buf)) { rc = PTR_ERR(buf); dev_dbg(rtc_dd->dev, "failed to read nvmem offset: %d\n", rc); return rc; } if (len != sizeof(u32)) { dev_dbg(rtc_dd->dev, "unexpected nvmem cell size %zu\n", len); kfree(buf); return -EINVAL; } rtc_dd->offset = get_unaligned_le32(buf); kfree(buf); return 0; } static int pm8xxx_rtc_write_nvmem_offset(struct pm8xxx_rtc *rtc_dd, u32 offset) { u8 buf[sizeof(u32)]; int rc; put_unaligned_le32(offset, buf); rc = nvmem_cell_write(rtc_dd->nvmem_cell, buf, sizeof(buf)); if (rc < 0) { dev_dbg(rtc_dd->dev, "failed to write nvmem offset: %d\n", rc); return rc; } return 0; } static int pm8xxx_rtc_read_offset(struct pm8xxx_rtc *rtc_dd) { if (!rtc_dd->nvmem_cell) return 0; return pm8xxx_rtc_read_nvmem_offset(rtc_dd); } static int pm8xxx_rtc_read_raw(struct pm8xxx_rtc *rtc_dd, u32 *secs) { const struct pm8xxx_rtc_regs *regs = rtc_dd->regs; u8 value[NUM_8_BIT_RTC_REGS]; unsigned int reg; int rc; rc = regmap_bulk_read(rtc_dd->regmap, regs->read, value, sizeof(value)); if (rc) return rc; /* * Read the LSB again and check if there has been a carry over. * If there has, redo the read operation. */ rc = regmap_read(rtc_dd->regmap, regs->read, ®); if (rc < 0) return rc; if (reg < value[0]) { rc = regmap_bulk_read(rtc_dd->regmap, regs->read, value, sizeof(value)); if (rc) return rc; } *secs = get_unaligned_le32(value); return 0; } static int pm8xxx_rtc_update_offset(struct pm8xxx_rtc *rtc_dd, u32 secs) { u32 raw_secs; u32 offset; int rc; if (!rtc_dd->nvmem_cell) return -ENODEV; rc = pm8xxx_rtc_read_raw(rtc_dd, &raw_secs); if (rc) return rc; offset = secs - raw_secs; if (offset == rtc_dd->offset) return 0; rc = pm8xxx_rtc_write_nvmem_offset(rtc_dd, offset); if (rc) return rc; rtc_dd->offset = offset; return 0; } /* * Steps to write the RTC registers. * 1. Disable alarm if enabled. * 2. Disable rtc if enabled. * 3. Write 0x00 to LSB. * 4. Write Byte[1], Byte[2], Byte[3] then Byte[0]. * 5. Enable rtc if disabled in step 2. * 6. Enable alarm if disabled in step 1. */ static int __pm8xxx_rtc_set_time(struct pm8xxx_rtc *rtc_dd, u32 secs) { const struct pm8xxx_rtc_regs *regs = rtc_dd->regs; u8 value[NUM_8_BIT_RTC_REGS]; bool alarm_enabled; int rc; put_unaligned_le32(secs, value); rc = regmap_update_bits_check(rtc_dd->regmap, regs->alarm_ctrl, regs->alarm_en, 0, &alarm_enabled); if (rc) return rc; /* Disable RTC */ rc = regmap_update_bits(rtc_dd->regmap, regs->ctrl, PM8xxx_RTC_ENABLE, 0); if (rc) return rc; /* Write 0 to Byte[0] */ rc = regmap_write(rtc_dd->regmap, regs->write, 0); if (rc) return rc; /* Write Byte[1], Byte[2], Byte[3] */ rc = regmap_bulk_write(rtc_dd->regmap, regs->write + 1, &value[1], sizeof(value) - 1); if (rc) return rc; /* Write Byte[0] */ rc = regmap_write(rtc_dd->regmap, regs->write, value[0]); if (rc) return rc; /* Enable RTC */ rc = regmap_update_bits(rtc_dd->regmap, regs->ctrl, PM8xxx_RTC_ENABLE, PM8xxx_RTC_ENABLE); if (rc) return rc; if (alarm_enabled) { rc = regmap_update_bits(rtc_dd->regmap, regs->alarm_ctrl, regs->alarm_en, regs->alarm_en); if (rc) return rc; } return 0; } static int pm8xxx_rtc_set_time(struct device *dev, struct rtc_time *tm) { struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev); u32 secs; int rc; secs = rtc_tm_to_time64(tm); if (rtc_dd->allow_set_time) rc = __pm8xxx_rtc_set_time(rtc_dd, secs); else rc = pm8xxx_rtc_update_offset(rtc_dd, secs); if (rc) return rc; dev_dbg(dev, "set time: %ptRd %ptRt (%u + %u)\n", tm, tm, secs - rtc_dd->offset, rtc_dd->offset); return 0; } static int pm8xxx_rtc_read_time(struct device *dev, struct rtc_time *tm) { struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev); u32 secs; int rc; rc = pm8xxx_rtc_read_raw(rtc_dd, &secs); if (rc) return rc; secs += rtc_dd->offset; rtc_time64_to_tm(secs, tm); dev_dbg(dev, "read time: %ptRd %ptRt (%u + %u)\n", tm, tm, secs - rtc_dd->offset, rtc_dd->offset); return 0; } static int pm8xxx_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) { struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev); const struct pm8xxx_rtc_regs *regs = rtc_dd->regs; u8 value[NUM_8_BIT_RTC_REGS]; u32 secs; int rc; secs = rtc_tm_to_time64(&alarm->time); secs -= rtc_dd->offset; put_unaligned_le32(secs, value); rc = regmap_update_bits(rtc_dd->regmap, regs->alarm_ctrl, regs->alarm_en, 0); if (rc) return rc; rc = regmap_bulk_write(rtc_dd->regmap, regs->alarm_rw, value, sizeof(value)); if (rc) return rc; if (alarm->enabled) { rc = regmap_update_bits(rtc_dd->regmap, regs->alarm_ctrl, regs->alarm_en, regs->alarm_en); if (rc) return rc; } dev_dbg(dev, "set alarm: %ptRd %ptRt\n", &alarm->time, &alarm->time); return 0; } static int pm8xxx_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) { struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev); const struct pm8xxx_rtc_regs *regs = rtc_dd->regs; u8 value[NUM_8_BIT_RTC_REGS]; unsigned int ctrl_reg; u32 secs; int rc; rc = regmap_bulk_read(rtc_dd->regmap, regs->alarm_rw, value, sizeof(value)); if (rc) return rc; secs = get_unaligned_le32(value); secs += rtc_dd->offset; rtc_time64_to_tm(secs, &alarm->time); rc = regmap_read(rtc_dd->regmap, regs->alarm_ctrl, &ctrl_reg); if (rc) return rc; alarm->enabled = !!(ctrl_reg & PM8xxx_RTC_ALARM_ENABLE); dev_dbg(dev, "read alarm: %ptRd %ptRt\n", &alarm->time, &alarm->time); return 0; } static int pm8xxx_rtc_alarm_irq_enable(struct device *dev, unsigned int enable) { struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev); const struct pm8xxx_rtc_regs *regs = rtc_dd->regs; u8 value[NUM_8_BIT_RTC_REGS] = {0}; unsigned int val; int rc; if (enable) val = regs->alarm_en; else val = 0; rc = regmap_update_bits(rtc_dd->regmap, regs->alarm_ctrl, regs->alarm_en, val); if (rc) return rc; /* Clear alarm register */ if (!enable) { rc = regmap_bulk_write(rtc_dd->regmap, regs->alarm_rw, value, sizeof(value)); if (rc) return rc; } return 0; } static const struct rtc_class_ops pm8xxx_rtc_ops = { .read_time = pm8xxx_rtc_read_time, .set_time = pm8xxx_rtc_set_time, .set_alarm = pm8xxx_rtc_set_alarm, .read_alarm = pm8xxx_rtc_read_alarm, .alarm_irq_enable = pm8xxx_rtc_alarm_irq_enable, }; static irqreturn_t pm8xxx_alarm_trigger(int irq, void *dev_id) { struct pm8xxx_rtc *rtc_dd = dev_id; const struct pm8xxx_rtc_regs *regs = rtc_dd->regs; int rc; rtc_update_irq(rtc_dd->rtc, 1, RTC_IRQF | RTC_AF); /* Disable alarm */ rc = regmap_update_bits(rtc_dd->regmap, regs->alarm_ctrl, regs->alarm_en, 0); if (rc) return IRQ_NONE; /* Clear alarm status */ rc = regmap_update_bits(rtc_dd->regmap, regs->alarm_ctrl2, PM8xxx_RTC_ALARM_CLEAR, 0); if (rc) return IRQ_NONE; return IRQ_HANDLED; } static int pm8xxx_rtc_enable(struct pm8xxx_rtc *rtc_dd) { const struct pm8xxx_rtc_regs *regs = rtc_dd->regs; return regmap_update_bits(rtc_dd->regmap, regs->ctrl, PM8xxx_RTC_ENABLE, PM8xxx_RTC_ENABLE); } static const struct pm8xxx_rtc_regs pm8921_regs = { .ctrl = 0x11d, .write = 0x11f, .read = 0x123, .alarm_rw = 0x127, .alarm_ctrl = 0x11d, .alarm_ctrl2 = 0x11e, .alarm_en = BIT(1), }; static const struct pm8xxx_rtc_regs pm8058_regs = { .ctrl = 0x1e8, .write = 0x1ea, .read = 0x1ee, .alarm_rw = 0x1f2, .alarm_ctrl = 0x1e8, .alarm_ctrl2 = 0x1e9, .alarm_en = BIT(1), }; static const struct pm8xxx_rtc_regs pm8941_regs = { .ctrl = 0x6046, .write = 0x6040, .read = 0x6048, .alarm_rw = 0x6140, .alarm_ctrl = 0x6146, .alarm_ctrl2 = 0x6148, .alarm_en = BIT(7), }; static const struct pm8xxx_rtc_regs pmk8350_regs = { .ctrl = 0x6146, .write = 0x6140, .read = 0x6148, .alarm_rw = 0x6240, .alarm_ctrl = 0x6246, .alarm_ctrl2 = 0x6248, .alarm_en = BIT(7), }; static const struct of_device_id pm8xxx_id_table[] = { { .compatible = "qcom,pm8921-rtc", .data = &pm8921_regs }, { .compatible = "qcom,pm8058-rtc", .data = &pm8058_regs }, { .compatible = "qcom,pm8941-rtc", .data = &pm8941_regs }, { .compatible = "qcom,pmk8350-rtc", .data = &pmk8350_regs }, { }, }; MODULE_DEVICE_TABLE(of, pm8xxx_id_table); static int pm8xxx_rtc_probe(struct platform_device *pdev) { const struct of_device_id *match; struct pm8xxx_rtc *rtc_dd; int rc; match = of_match_node(pm8xxx_id_table, pdev->dev.of_node); if (!match) return -ENXIO; rtc_dd = devm_kzalloc(&pdev->dev, sizeof(*rtc_dd), GFP_KERNEL); if (rtc_dd == NULL) return -ENOMEM; rtc_dd->regmap = dev_get_regmap(pdev->dev.parent, NULL); if (!rtc_dd->regmap) return -ENXIO; rtc_dd->alarm_irq = platform_get_irq(pdev, 0); if (rtc_dd->alarm_irq < 0) return -ENXIO; rtc_dd->allow_set_time = of_property_read_bool(pdev->dev.of_node, "allow-set-time"); rtc_dd->nvmem_cell = devm_nvmem_cell_get(&pdev->dev, "offset"); if (IS_ERR(rtc_dd->nvmem_cell)) { rc = PTR_ERR(rtc_dd->nvmem_cell); if (rc != -ENOENT) return rc; rtc_dd->nvmem_cell = NULL; } rtc_dd->regs = match->data; rtc_dd->dev = &pdev->dev; if (!rtc_dd->allow_set_time) { rc = pm8xxx_rtc_read_offset(rtc_dd); if (rc) return rc; } rc = pm8xxx_rtc_enable(rtc_dd); if (rc) return rc; platform_set_drvdata(pdev, rtc_dd); device_init_wakeup(&pdev->dev, 1); rtc_dd->rtc = devm_rtc_allocate_device(&pdev->dev); if (IS_ERR(rtc_dd->rtc)) return PTR_ERR(rtc_dd->rtc); rtc_dd->rtc->ops = &pm8xxx_rtc_ops; rtc_dd->rtc->range_max = U32_MAX; rc = devm_request_any_context_irq(&pdev->dev, rtc_dd->alarm_irq, pm8xxx_alarm_trigger, IRQF_TRIGGER_RISING, "pm8xxx_rtc_alarm", rtc_dd); if (rc < 0) return rc; rc = devm_rtc_register_device(rtc_dd->rtc); if (rc) return rc; rc = dev_pm_set_wake_irq(&pdev->dev, rtc_dd->alarm_irq); if (rc) return rc; return 0; } static int pm8xxx_remove(struct platform_device *pdev) { dev_pm_clear_wake_irq(&pdev->dev); return 0; } static struct platform_driver pm8xxx_rtc_driver = { .probe = pm8xxx_rtc_probe, .remove = pm8xxx_remove, .driver = { .name = "rtc-pm8xxx", .of_match_table = pm8xxx_id_table, }, }; module_platform_driver(pm8xxx_rtc_driver); MODULE_ALIAS("platform:rtc-pm8xxx"); MODULE_DESCRIPTION("PMIC8xxx RTC driver"); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Anirudh Ghayal <aghayal@codeaurora.org>"); MODULE_AUTHOR("Johan Hovold <johan@kernel.org>");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1