Add samsung specific changes
This commit is contained in:
@@ -1985,4 +1985,9 @@ config RTC_DRV_POLARFIRE_SOC
|
||||
This driver can also be built as a module, if so, the module
|
||||
will be called "rtc-mpfs".
|
||||
|
||||
config RTC_AUTO_PWRON
|
||||
tristate "RTC Auto Power on PMICs"
|
||||
help
|
||||
Support for the auto power on alarm on the PMIC.
|
||||
|
||||
endif # RTC_CLASS
|
||||
|
@@ -184,3 +184,4 @@ obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o
|
||||
obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o
|
||||
obj-$(CONFIG_RTC_DRV_XGENE) += rtc-xgene.o
|
||||
obj-$(CONFIG_RTC_DRV_ZYNQMP) += rtc-zynqmp.o
|
||||
obj-$(CONFIG_RTC_AUTO_PWRON) += sec_pon_alarm.o
|
||||
|
@@ -4,6 +4,7 @@
|
||||
*
|
||||
* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
|
||||
* Copyright (c) 2023, Linaro Limited
|
||||
* Copyright (c) 2021-2023 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
#include <linux/of.h>
|
||||
#include <linux/module.h>
|
||||
@@ -72,13 +73,13 @@ static int pm8xxx_rtc_read_nvmem_offset(struct pm8xxx_rtc *rtc_dd)
|
||||
{
|
||||
size_t len;
|
||||
void *buf;
|
||||
int rc;
|
||||
int rtc_alarm_status;
|
||||
|
||||
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;
|
||||
rtc_alarm_status = PTR_ERR(buf);
|
||||
dev_dbg(rtc_dd->dev, "failed to read nvmem offset: %d\n", rtc_alarm_status);
|
||||
return rtc_alarm_status;
|
||||
}
|
||||
|
||||
if (len != sizeof(u32)) {
|
||||
@@ -97,14 +98,14 @@ static int pm8xxx_rtc_read_nvmem_offset(struct pm8xxx_rtc *rtc_dd)
|
||||
static int pm8xxx_rtc_write_nvmem_offset(struct pm8xxx_rtc *rtc_dd, u32 offset)
|
||||
{
|
||||
u8 buf[sizeof(u32)];
|
||||
int rc;
|
||||
int rtc_alarm_status;
|
||||
|
||||
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;
|
||||
rtc_alarm_status = nvmem_cell_write(rtc_dd->nvmem_cell, buf, sizeof(buf));
|
||||
if (rtc_alarm_status < 0) {
|
||||
dev_dbg(rtc_dd->dev, "failed to write nvmem offset: %d\n", rtc_alarm_status);
|
||||
return rtc_alarm_status;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -123,25 +124,25 @@ 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;
|
||||
int rtc_alarm_status;
|
||||
|
||||
rc = regmap_bulk_read(rtc_dd->regmap, regs->read, value, sizeof(value));
|
||||
if (rc)
|
||||
return rc;
|
||||
rtc_alarm_status = regmap_bulk_read(rtc_dd->regmap, regs->read, value, sizeof(value));
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
rtc_alarm_status = regmap_read(rtc_dd->regmap, regs->read, ®);
|
||||
if (rtc_alarm_status < 0)
|
||||
return rtc_alarm_status;
|
||||
|
||||
if (reg < value[0]) {
|
||||
rc = regmap_bulk_read(rtc_dd->regmap, regs->read, value,
|
||||
rtc_alarm_status = regmap_bulk_read(rtc_dd->regmap, regs->read, value,
|
||||
sizeof(value));
|
||||
if (rc)
|
||||
return rc;
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
}
|
||||
|
||||
*secs = get_unaligned_le32(value);
|
||||
@@ -153,23 +154,23 @@ static int pm8xxx_rtc_update_offset(struct pm8xxx_rtc *rtc_dd, u32 secs)
|
||||
{
|
||||
u32 raw_secs;
|
||||
u32 offset;
|
||||
int rc;
|
||||
int rtc_alarm_status;
|
||||
|
||||
if (!rtc_dd->nvmem_cell)
|
||||
return -ENODEV;
|
||||
|
||||
rc = pm8xxx_rtc_read_raw(rtc_dd, &raw_secs);
|
||||
if (rc)
|
||||
return rc;
|
||||
rtc_alarm_status = pm8xxx_rtc_read_raw(rtc_dd, &raw_secs);
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
|
||||
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_alarm_status = pm8xxx_rtc_write_nvmem_offset(rtc_dd, offset);
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
|
||||
rtc_dd->offset = offset;
|
||||
|
||||
@@ -190,47 +191,47 @@ 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;
|
||||
int rtc_alarm_status;
|
||||
|
||||
put_unaligned_le32(secs, value);
|
||||
|
||||
rc = regmap_update_bits_check(rtc_dd->regmap, regs->alarm_ctrl,
|
||||
rtc_alarm_status = regmap_update_bits_check(rtc_dd->regmap, regs->alarm_ctrl,
|
||||
regs->alarm_en, 0, &alarm_enabled);
|
||||
if (rc)
|
||||
return rc;
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
|
||||
/* Disable RTC */
|
||||
rc = regmap_update_bits(rtc_dd->regmap, regs->ctrl, PM8xxx_RTC_ENABLE, 0);
|
||||
if (rc)
|
||||
return rc;
|
||||
rtc_alarm_status = regmap_update_bits(rtc_dd->regmap, regs->ctrl, PM8xxx_RTC_ENABLE, 0);
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
|
||||
/* Write 0 to Byte[0] */
|
||||
rc = regmap_write(rtc_dd->regmap, regs->write, 0);
|
||||
if (rc)
|
||||
return rc;
|
||||
rtc_alarm_status = regmap_write(rtc_dd->regmap, regs->write, 0);
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
|
||||
/* Write Byte[1], Byte[2], Byte[3] */
|
||||
rc = regmap_bulk_write(rtc_dd->regmap, regs->write + 1,
|
||||
rtc_alarm_status = regmap_bulk_write(rtc_dd->regmap, regs->write + 1,
|
||||
&value[1], sizeof(value) - 1);
|
||||
if (rc)
|
||||
return rc;
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
|
||||
/* Write Byte[0] */
|
||||
rc = regmap_write(rtc_dd->regmap, regs->write, value[0]);
|
||||
if (rc)
|
||||
return rc;
|
||||
rtc_alarm_status = regmap_write(rtc_dd->regmap, regs->write, value[0]);
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
|
||||
/* Enable RTC */
|
||||
rc = regmap_update_bits(rtc_dd->regmap, regs->ctrl, PM8xxx_RTC_ENABLE,
|
||||
rtc_alarm_status = regmap_update_bits(rtc_dd->regmap, regs->ctrl, PM8xxx_RTC_ENABLE,
|
||||
PM8xxx_RTC_ENABLE);
|
||||
if (rc)
|
||||
return rc;
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
|
||||
if (alarm_enabled) {
|
||||
rc = regmap_update_bits(rtc_dd->regmap, regs->alarm_ctrl,
|
||||
rtc_alarm_status = regmap_update_bits(rtc_dd->regmap, regs->alarm_ctrl,
|
||||
regs->alarm_en, regs->alarm_en);
|
||||
if (rc)
|
||||
return rc;
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -240,17 +241,17 @@ 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;
|
||||
int rtc_alarm_status;
|
||||
|
||||
secs = rtc_tm_to_time64(tm);
|
||||
|
||||
if (rtc_dd->allow_set_time)
|
||||
rc = __pm8xxx_rtc_set_time(rtc_dd, secs);
|
||||
rtc_alarm_status = __pm8xxx_rtc_set_time(rtc_dd, secs);
|
||||
else
|
||||
rc = pm8xxx_rtc_update_offset(rtc_dd, secs);
|
||||
rtc_alarm_status = pm8xxx_rtc_update_offset(rtc_dd, secs);
|
||||
|
||||
if (rc)
|
||||
return rc;
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
|
||||
dev_dbg(dev, "set time: %ptRd %ptRt (%u + %u)\n", tm, tm,
|
||||
secs - rtc_dd->offset, rtc_dd->offset);
|
||||
@@ -261,11 +262,11 @@ 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;
|
||||
int rtc_alarm_status;
|
||||
|
||||
rc = pm8xxx_rtc_read_raw(rtc_dd, &secs);
|
||||
if (rc)
|
||||
return rc;
|
||||
rtc_alarm_status = pm8xxx_rtc_read_raw(rtc_dd, &secs);
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
|
||||
secs += rtc_dd->offset;
|
||||
rtc_time64_to_tm(secs, tm);
|
||||
@@ -281,27 +282,27 @@ static int pm8xxx_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
|
||||
const struct pm8xxx_rtc_regs *regs = rtc_dd->regs;
|
||||
u8 value[NUM_8_BIT_RTC_REGS];
|
||||
u32 secs;
|
||||
int rc;
|
||||
int rtc_alarm_status;
|
||||
|
||||
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,
|
||||
rtc_alarm_status = regmap_update_bits(rtc_dd->regmap, regs->alarm_ctrl,
|
||||
regs->alarm_en, 0);
|
||||
if (rc)
|
||||
return rc;
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
|
||||
rc = regmap_bulk_write(rtc_dd->regmap, regs->alarm_rw, value,
|
||||
rtc_alarm_status = regmap_bulk_write(rtc_dd->regmap, regs->alarm_rw, value,
|
||||
sizeof(value));
|
||||
if (rc)
|
||||
return rc;
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
|
||||
if (alarm->enabled) {
|
||||
rc = regmap_update_bits(rtc_dd->regmap, regs->alarm_ctrl,
|
||||
rtc_alarm_status = regmap_update_bits(rtc_dd->regmap, regs->alarm_ctrl,
|
||||
regs->alarm_en, regs->alarm_en);
|
||||
if (rc)
|
||||
return rc;
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "set alarm: %ptRd %ptRt\n", &alarm->time, &alarm->time);
|
||||
@@ -316,20 +317,20 @@ static int pm8xxx_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
|
||||
u8 value[NUM_8_BIT_RTC_REGS];
|
||||
unsigned int ctrl_reg;
|
||||
u32 secs;
|
||||
int rc;
|
||||
int rtc_alarm_status;
|
||||
|
||||
rc = regmap_bulk_read(rtc_dd->regmap, regs->alarm_rw, value,
|
||||
rtc_alarm_status = regmap_bulk_read(rtc_dd->regmap, regs->alarm_rw, value,
|
||||
sizeof(value));
|
||||
if (rc)
|
||||
return rc;
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
|
||||
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;
|
||||
rtc_alarm_status = regmap_read(rtc_dd->regmap, regs->alarm_ctrl, &ctrl_reg);
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
|
||||
alarm->enabled = !!(ctrl_reg & PM8xxx_RTC_ALARM_ENABLE);
|
||||
|
||||
@@ -344,24 +345,24 @@ static int pm8xxx_rtc_alarm_irq_enable(struct device *dev, unsigned int enable)
|
||||
const struct pm8xxx_rtc_regs *regs = rtc_dd->regs;
|
||||
u8 value[NUM_8_BIT_RTC_REGS] = {0};
|
||||
unsigned int val;
|
||||
int rc;
|
||||
int rtc_alarm_status;
|
||||
|
||||
if (enable)
|
||||
val = regs->alarm_en;
|
||||
else
|
||||
val = 0;
|
||||
|
||||
rc = regmap_update_bits(rtc_dd->regmap, regs->alarm_ctrl,
|
||||
rtc_alarm_status = regmap_update_bits(rtc_dd->regmap, regs->alarm_ctrl,
|
||||
regs->alarm_en, val);
|
||||
if (rc)
|
||||
return rc;
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
|
||||
/* Clear alarm register */
|
||||
if (!enable) {
|
||||
rc = regmap_bulk_write(rtc_dd->regmap, regs->alarm_rw, value,
|
||||
rtc_alarm_status = regmap_bulk_write(rtc_dd->regmap, regs->alarm_rw, value,
|
||||
sizeof(value));
|
||||
if (rc)
|
||||
return rc;
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -379,20 +380,20 @@ 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;
|
||||
int rtc_alarm_status;
|
||||
|
||||
rtc_update_irq(rtc_dd->rtc, 1, RTC_IRQF | RTC_AF);
|
||||
|
||||
/* Disable alarm */
|
||||
rc = regmap_update_bits(rtc_dd->regmap, regs->alarm_ctrl,
|
||||
rtc_alarm_status = regmap_update_bits(rtc_dd->regmap, regs->alarm_ctrl,
|
||||
regs->alarm_en, 0);
|
||||
if (rc)
|
||||
if (rtc_alarm_status)
|
||||
return IRQ_NONE;
|
||||
|
||||
/* Clear alarm status */
|
||||
rc = regmap_update_bits(rtc_dd->regmap, regs->alarm_ctrl2,
|
||||
rtc_alarm_status = regmap_update_bits(rtc_dd->regmap, regs->alarm_ctrl2,
|
||||
PM8xxx_RTC_ALARM_CLEAR, 0);
|
||||
if (rc)
|
||||
if (rtc_alarm_status)
|
||||
return IRQ_NONE;
|
||||
|
||||
return IRQ_HANDLED;
|
||||
@@ -446,11 +447,26 @@ static const struct pm8xxx_rtc_regs pmk8350_regs = {
|
||||
.alarm_en = BIT(7),
|
||||
};
|
||||
|
||||
static const struct pm8xxx_rtc_regs pm5100_regs = {
|
||||
.ctrl = 0x6446,
|
||||
.write = 0x6440,
|
||||
.read = 0x6448,
|
||||
.alarm_rw = 0x6540,
|
||||
.alarm_ctrl = 0x6546,
|
||||
.alarm_ctrl2 = 0x6548,
|
||||
.alarm_en = BIT(7),
|
||||
};
|
||||
|
||||
/*
|
||||
* Hardcoded RTC bases until IORESOURCE_REG mapping is figured out
|
||||
*/
|
||||
|
||||
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 },
|
||||
{ .compatible = "qcom,pm5100-rtc", .data = &pm5100_regs },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, pm8xxx_id_table);
|
||||
@@ -459,7 +475,7 @@ static int pm8xxx_rtc_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct of_device_id *match;
|
||||
struct pm8xxx_rtc *rtc_dd;
|
||||
int rc;
|
||||
int rtc_alarm_status;
|
||||
|
||||
match = of_match_node(pm8xxx_id_table, pdev->dev.of_node);
|
||||
if (!match)
|
||||
@@ -482,9 +498,9 @@ static int pm8xxx_rtc_probe(struct platform_device *pdev)
|
||||
|
||||
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_alarm_status = PTR_ERR(rtc_dd->nvmem_cell);
|
||||
if (rtc_alarm_status != -ENOENT)
|
||||
return rtc_alarm_status;
|
||||
rtc_dd->nvmem_cell = NULL;
|
||||
}
|
||||
|
||||
@@ -492,14 +508,14 @@ static int pm8xxx_rtc_probe(struct platform_device *pdev)
|
||||
rtc_dd->dev = &pdev->dev;
|
||||
|
||||
if (!rtc_dd->allow_set_time) {
|
||||
rc = pm8xxx_rtc_read_offset(rtc_dd);
|
||||
if (rc)
|
||||
return rc;
|
||||
rtc_alarm_status = pm8xxx_rtc_read_offset(rtc_dd);
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
}
|
||||
|
||||
rc = pm8xxx_rtc_enable(rtc_dd);
|
||||
if (rc)
|
||||
return rc;
|
||||
rtc_alarm_status = pm8xxx_rtc_enable(rtc_dd);
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
|
||||
platform_set_drvdata(pdev, rtc_dd);
|
||||
|
||||
@@ -512,29 +528,62 @@ static int pm8xxx_rtc_probe(struct platform_device *pdev)
|
||||
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,
|
||||
rtc_alarm_status = 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;
|
||||
if (rtc_alarm_status < 0)
|
||||
return rtc_alarm_status;
|
||||
|
||||
rc = devm_rtc_register_device(rtc_dd->rtc);
|
||||
if (rc)
|
||||
return rc;
|
||||
rtc_alarm_status = devm_rtc_register_device(rtc_dd->rtc);
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
|
||||
rc = dev_pm_set_wake_irq(&pdev->dev, rtc_dd->alarm_irq);
|
||||
if (rc)
|
||||
return rc;
|
||||
rtc_alarm_status = dev_pm_set_wake_irq(&pdev->dev, rtc_dd->alarm_irq);
|
||||
if (rtc_alarm_status)
|
||||
return rtc_alarm_status;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_RTC_AUTO_PWRON)
|
||||
static struct rtc_wkalrm pwron_alarm;
|
||||
|
||||
void pmic_rtc_setalarm(struct rtc_wkalrm *alm)
|
||||
{
|
||||
memcpy(&pwron_alarm, alm, sizeof(struct rtc_wkalrm));
|
||||
}
|
||||
EXPORT_SYMBOL(pmic_rtc_setalarm);
|
||||
#endif
|
||||
|
||||
static void pm8xxx_remove(struct platform_device *pdev)
|
||||
{
|
||||
dev_pm_clear_wake_irq(&pdev->dev);
|
||||
}
|
||||
|
||||
static void pm8xxx_rtc_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
struct pm8xxx_rtc *rtc_dd = platform_get_drvdata(pdev);
|
||||
|
||||
#if IS_ENABLED(CONFIG_RTC_AUTO_PWRON)
|
||||
struct rtc_wkalrm alarm;
|
||||
int rtc_alarm_status = 0;
|
||||
|
||||
if (pdev) {
|
||||
pm8xxx_rtc_set_alarm(&pdev->dev, &pwron_alarm);
|
||||
rtc_alarm_status = pm8xxx_rtc_read_alarm(&pdev->dev, &alarm);
|
||||
if (!rtc_alarm_status) {
|
||||
pr_info("%s: %d-%02d-%02d %02d:%02d:%02d\n", __func__,
|
||||
alarm.time.tm_year + 1900, alarm.time.tm_mon + 1, alarm.time.tm_mday,
|
||||
alarm.time.tm_hour, alarm.time.tm_min, alarm.time.tm_sec);
|
||||
}
|
||||
} else
|
||||
pr_err("%s: spmi device not found\n", __func__);
|
||||
#endif
|
||||
|
||||
devm_free_irq(rtc_dd->dev, rtc_dd->alarm_irq, rtc_dd);
|
||||
}
|
||||
|
||||
static struct platform_driver pm8xxx_rtc_driver = {
|
||||
.probe = pm8xxx_rtc_probe,
|
||||
.remove_new = pm8xxx_remove,
|
||||
@@ -542,6 +591,7 @@ static struct platform_driver pm8xxx_rtc_driver = {
|
||||
.name = "rtc-pm8xxx",
|
||||
.of_match_table = pm8xxx_id_table,
|
||||
},
|
||||
.shutdown = pm8xxx_rtc_shutdown,
|
||||
};
|
||||
|
||||
module_platform_driver(pm8xxx_rtc_driver);
|
||||
|
437
drivers/rtc/sec_pon_alarm.c
Normal file
437
drivers/rtc/sec_pon_alarm.c
Normal file
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
* /drivers/rtc/power-on-alarm.c
|
||||
*
|
||||
* Copyright (C) 2021 Samsung Electronics
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/time.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/alarmtimer.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/sec_param.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/of_device.h>
|
||||
|
||||
#define PON_ALARM_KPARAM_MAGIC 0x41504153
|
||||
#define PON_ALARM_START_POLL_TIME (10LL * NSEC_PER_SEC) /* 10 sec */
|
||||
#define PON_ALARM_BOOTING_TIME (5 * 60)
|
||||
#define PON_ALARM_POLL_TIME (15 * 60)
|
||||
|
||||
#define BOOTALM_BIT_EN 0
|
||||
#define BOOTALM_BIT_YEAR 1
|
||||
#define BOOTALM_BIT_MONTH 5
|
||||
#define BOOTALM_BIT_DAY 7
|
||||
#define BOOTALM_BIT_HOUR 9
|
||||
#define BOOTALM_BIT_MIN 11
|
||||
#define BOOTALM_BIT_TOTAL 13
|
||||
|
||||
#define RTC_ALM_SEC_MASK 0x00000003
|
||||
#define RTC_RESET_NUM 70
|
||||
#define RTC_RESET_YEAR 50
|
||||
|
||||
#define ALARM_ENABLE_MULTIPLIER 1
|
||||
|
||||
#define PRIME_EPOCH_YEAR 1900
|
||||
|
||||
enum pon_alarm_state {
|
||||
PON_ALARM_DISTANT = 0,
|
||||
PON_ALARM_NEAR,
|
||||
PON_ALARM_EXPIRED,
|
||||
PON_ALARM_OVER,
|
||||
PON_ALARM_ERROR
|
||||
};
|
||||
|
||||
struct pon_alarm_info {
|
||||
struct device *dev;
|
||||
struct rtc_device *rtcdev;
|
||||
struct rtc_wkalrm val;
|
||||
struct alarm check_poll;
|
||||
struct work_struct check_func;
|
||||
struct wakeup_source *ws;
|
||||
uint lpm_mode;
|
||||
unsigned char triggered;
|
||||
};
|
||||
|
||||
struct alarm_timespec {
|
||||
char alarm[14];
|
||||
};
|
||||
|
||||
#define ANDROID_ALARM_BASE_CMD(cmd) (cmd & ~(_IOC(0, 0, 0xf0, 0)))
|
||||
#define ANDROID_ALARM_SET_ALARM_BOOT _IOW('a', 7, struct alarm_timespec)
|
||||
|
||||
|
||||
extern void pmic_rtc_setalarm(struct rtc_wkalrm *alm);
|
||||
|
||||
static unsigned int __read_mostly rtcalarm;
|
||||
static unsigned int __read_mostly lpcharge;
|
||||
module_param(rtcalarm, uint, 0444);
|
||||
module_param(lpcharge, uint, 0444);
|
||||
|
||||
struct pon_alarm_info pon_alarm;
|
||||
|
||||
static void pon_alarm_normalize(struct rtc_wkalrm *alarm)
|
||||
{
|
||||
if (!alarm->enabled) {
|
||||
/* RTC reset + 50 years = 1580518864 = 0x5e34cdd0 */
|
||||
alarm->time.tm_year = RTC_RESET_NUM + RTC_RESET_YEAR;
|
||||
alarm->time.tm_mon = 1;
|
||||
alarm->time.tm_mday = 1;
|
||||
alarm->time.tm_hour = 1;
|
||||
alarm->time.tm_min = 1;
|
||||
alarm->time.tm_sec = 4;
|
||||
}
|
||||
}
|
||||
|
||||
static void pon_alarm_set(struct rtc_wkalrm *alarm)
|
||||
{
|
||||
time64_t secs_pwron;
|
||||
unsigned int rtc_alarm_params[3];
|
||||
int ret;
|
||||
|
||||
memcpy(&pon_alarm.val, alarm, sizeof(struct rtc_wkalrm));
|
||||
pon_alarm_normalize(&pon_alarm.val);
|
||||
|
||||
pr_info("%s: reserve pmic alarm\n", __func__);
|
||||
pmic_rtc_setalarm(&pon_alarm.val);
|
||||
|
||||
secs_pwron = rtc_tm_to_time64(&pon_alarm.val.time);
|
||||
rtc_alarm_params[0] = PON_ALARM_KPARAM_MAGIC;
|
||||
rtc_alarm_params[1] = (unsigned int)pon_alarm.val.enabled;
|
||||
rtc_alarm_params[2] = (unsigned int)secs_pwron;
|
||||
ret = sec_set_param(param_index_sapa, rtc_alarm_params);
|
||||
pr_info("%s: ret=%d, enabled=%d, alarm=%u\n",
|
||||
__func__, ret, rtc_alarm_params[1], rtc_alarm_params[2]);
|
||||
}
|
||||
|
||||
static char *pon_alarm_get_timeformat(struct rtc_time tm)
|
||||
{
|
||||
static char time_str[20];
|
||||
|
||||
snprintf(time_str, sizeof(time_str), "%4d-%02d-%02d %02d:%02d:%02d",
|
||||
tm.tm_year, tm.tm_mon + 1, tm.tm_mday,
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec);
|
||||
return time_str;
|
||||
}
|
||||
|
||||
static void pon_get_alarm_time(struct rtc_wkalrm *alm, u8 *buf_ptr)
|
||||
{
|
||||
/*
|
||||
* 0|1234|56|78|90|12
|
||||
* 1|2010|01|01|00|00
|
||||
* en|yyyy|mm|dd|hh|mm
|
||||
*/
|
||||
alm->time.tm_sec = 0;
|
||||
alm->time.tm_min = (buf_ptr[BOOTALM_BIT_MIN] - '0') * 10
|
||||
+ (buf_ptr[BOOTALM_BIT_MIN+1] - '0');
|
||||
alm->time.tm_hour = (buf_ptr[BOOTALM_BIT_HOUR] - '0') * 10
|
||||
+ (buf_ptr[BOOTALM_BIT_HOUR+1] - '0');
|
||||
alm->time.tm_mday = (buf_ptr[BOOTALM_BIT_DAY] - '0') * 10
|
||||
+ (buf_ptr[BOOTALM_BIT_DAY+1] - '0');
|
||||
alm->time.tm_mon = (buf_ptr[BOOTALM_BIT_MONTH] - '0') * 10
|
||||
+ (buf_ptr[BOOTALM_BIT_MONTH+1] - '0');
|
||||
alm->time.tm_year = (buf_ptr[BOOTALM_BIT_YEAR] - '0') * 1000
|
||||
+ (buf_ptr[BOOTALM_BIT_YEAR+1] - '0') * 100
|
||||
+ (buf_ptr[BOOTALM_BIT_YEAR+2] - '0') * 10
|
||||
+ (buf_ptr[BOOTALM_BIT_YEAR+3] - '0');
|
||||
|
||||
switch (*buf_ptr) {
|
||||
case '1':
|
||||
alm->enabled = 1;
|
||||
break;
|
||||
case '2':
|
||||
alm->enabled = 2;
|
||||
break;
|
||||
default:
|
||||
pr_warn("Invalid value for alarm enable flag: %c, setting to 0\n", *buf_ptr);
|
||||
alm->enabled = 0;
|
||||
}
|
||||
|
||||
pr_info("%s: %s => tm(%d %s)\n", __func__, buf_ptr, alm->enabled, pon_alarm_get_timeformat(alm->time));
|
||||
}
|
||||
|
||||
static void pon_read_kernel_time(struct timespec64 *ktm_ts, struct rtc_wkalrm *alm)
|
||||
{
|
||||
struct rtc_time ktm_tm;
|
||||
|
||||
/* read kernel time */
|
||||
ktime_get_real_ts64(ktm_ts);
|
||||
ktm_tm = rtc_ktime_to_tm(timespec64_to_ktime(*ktm_ts));
|
||||
pr_info("%s: <KTM > %s\n", __func__, pon_alarm_get_timeformat(ktm_tm));
|
||||
|
||||
alm->time.tm_mon -= 1;
|
||||
alm->time.tm_year -= PRIME_EPOCH_YEAR;
|
||||
pr_info("%s: <ALRM> %s\n", __func__, pon_alarm_get_timeformat(alm->time));
|
||||
}
|
||||
|
||||
static int pon_read_current_time(time64_t *rtc_sec, struct rtc_time *rtc_tm)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = rtc_read_time(pon_alarm.rtcdev, rtc_tm);
|
||||
if (ret) {
|
||||
pr_err("%s: rtc read failed, ret=%d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*rtc_sec = rtc_tm_to_time64(rtc_tm);
|
||||
pr_info("%s: <rtc > %s -> %lld\n", __func__, pon_alarm_get_timeformat(*rtc_tm), *rtc_sec);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void pon_calculate_offset(struct timespec64 *delta, struct timespec64 ktm_ts, time64_t rtc_sec)
|
||||
{
|
||||
set_normalized_timespec64(delta,
|
||||
(time64_t)ktm_ts.tv_sec - rtc_sec,
|
||||
(s64)ktm_ts.tv_nsec);
|
||||
}
|
||||
|
||||
static time64_t set_rtc_alm_sec(struct timespec64 delta, struct rtc_wkalrm *alm)
|
||||
{
|
||||
time64_t rtc_alm_sec = rtc_tm_to_time64(&alm->time) - delta.tv_sec;
|
||||
|
||||
rtc_alm_sec = (rtc_alm_sec & ~RTC_ALM_SEC_MASK) | ((alm->enabled - 1) << (ALARM_ENABLE_MULTIPLIER));
|
||||
return rtc_alm_sec;
|
||||
}
|
||||
|
||||
static void pon_convert_utc_to_rtc(struct timespec64 delta, struct rtc_wkalrm *alm)
|
||||
{
|
||||
time64_t rtc_alm_sec = set_rtc_alm_sec(delta, alm);
|
||||
|
||||
alm->enabled = 1;
|
||||
rtc_time64_to_tm(rtc_alm_sec, &alm->time);
|
||||
|
||||
pr_info("%s: <alrm> %s -> %lld\n", __func__, pon_alarm_get_timeformat(alm->time), rtc_alm_sec);
|
||||
}
|
||||
|
||||
static void pon_alarm_parse_data(char *alarm_data, struct rtc_wkalrm *alm)
|
||||
{
|
||||
char buf_ptr[BOOTALM_BIT_TOTAL + 1] = {0,};
|
||||
struct rtc_time rtc_tm;
|
||||
struct timespec64 delta, ktm_ts;
|
||||
time64_t rtc_sec;
|
||||
int ret;
|
||||
|
||||
alarm_data[BOOTALM_BIT_TOTAL] = '\0';
|
||||
strlcpy(buf_ptr, alarm_data, BOOTALM_BIT_TOTAL+1);
|
||||
|
||||
pon_get_alarm_time(alm, buf_ptr);
|
||||
if (!alm->enabled) {
|
||||
pr_info("%s: pon alarm is not enabled!\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
pon_read_kernel_time(&ktm_ts, alm);
|
||||
ret = pon_read_current_time(&rtc_sec, &rtc_tm);
|
||||
if (ret < 0) {
|
||||
pr_info("%s: pon read current time fail!\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
pon_calculate_offset(&delta, ktm_ts, rtc_sec);
|
||||
|
||||
pon_convert_utc_to_rtc(delta, alm);
|
||||
}
|
||||
|
||||
static enum pon_alarm_state check_pon_alarm_state(time64_t rtc_secs, time64_t secs_pwron, time64_t *data)
|
||||
{
|
||||
if (rtc_secs < secs_pwron) {
|
||||
if (secs_pwron - rtc_secs > PON_ALARM_POLL_TIME)
|
||||
return PON_ALARM_DISTANT;
|
||||
if (data)
|
||||
*data = secs_pwron - rtc_secs;
|
||||
return PON_ALARM_NEAR;
|
||||
} else if (rtc_secs <= secs_pwron + PON_ALARM_BOOTING_TIME) {
|
||||
if (data)
|
||||
*data = rtc_secs + 10;
|
||||
return PON_ALARM_EXPIRED;
|
||||
} else {
|
||||
return PON_ALARM_OVER;
|
||||
}
|
||||
}
|
||||
|
||||
static int pon_alarm_check_state(time64_t *data)
|
||||
{
|
||||
struct rtc_time tm;
|
||||
time64_t rtc_secs;
|
||||
time64_t secs_pwron;
|
||||
enum pon_alarm_state state = PON_ALARM_NEAR;
|
||||
int ret;
|
||||
|
||||
ret = rtc_read_time(pon_alarm.rtcdev, &tm);
|
||||
if (ret) {
|
||||
pr_err("%s: rtc read failed.\n", __func__);
|
||||
return PON_ALARM_ERROR;
|
||||
}
|
||||
rtc_secs = rtc_tm_to_time64(&tm);
|
||||
secs_pwron = rtc_tm_to_time64(&pon_alarm.val.time);
|
||||
|
||||
state = check_pon_alarm_state(rtc_secs, secs_pwron, data);
|
||||
|
||||
pr_info("%s: rtc:%lld, alrm:%lld[%d]\n", __func__, rtc_secs, secs_pwron, state);
|
||||
return state;
|
||||
}
|
||||
|
||||
static void pon_alarm_check_func(struct work_struct *work)
|
||||
{
|
||||
struct pon_alarm_info *pon_info = container_of(work, struct pon_alarm_info, check_func);
|
||||
int res;
|
||||
time64_t remain;
|
||||
|
||||
res = pon_alarm_check_state(&remain);
|
||||
if (res <= PON_ALARM_NEAR) {
|
||||
ktime_t ktime;
|
||||
|
||||
if (res == PON_ALARM_DISTANT)
|
||||
remain = PON_ALARM_POLL_TIME;
|
||||
ktime = ns_to_ktime((u64)remain * NSEC_PER_SEC);
|
||||
alarm_start_relative(&pon_info->check_poll, ktime);
|
||||
pr_info("%s: next %lld s\n", __func__, remain);
|
||||
} else if (res == PON_ALARM_EXPIRED) {
|
||||
__pm_stay_awake(pon_info->ws);
|
||||
pon_info->triggered = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static enum alarmtimer_restart pon_alarm_check_callback(struct alarm *alarm, ktime_t now)
|
||||
{
|
||||
struct pon_alarm_info *pon_info = container_of(alarm, struct pon_alarm_info, check_poll);
|
||||
|
||||
schedule_work(&pon_info->check_func);
|
||||
return ALARMTIMER_NORESTART;
|
||||
}
|
||||
|
||||
|
||||
static int pon_alarm_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
file->private_data = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t pon_alarm_read(struct file *file,
|
||||
char __user *buff, size_t count, loff_t *ppos)
|
||||
{
|
||||
char trigger = (pon_alarm.triggered) ? '1' : '0';
|
||||
|
||||
if (copy_to_user((void __user *)buff, &trigger, 1)) {
|
||||
pr_err("%s: read failed.\n", __func__);
|
||||
return -EFAULT;
|
||||
}
|
||||
pr_info("%s: trigger=%c\n", __func__, trigger);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static long pon_alarm_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
int ret = 0;
|
||||
struct alarm_timespec data;
|
||||
struct rtc_wkalrm alm;
|
||||
|
||||
pr_info("%s: cmd=%08x\n", __func__, cmd);
|
||||
|
||||
switch (ANDROID_ALARM_BASE_CMD(cmd)) {
|
||||
case ANDROID_ALARM_SET_ALARM_BOOT:
|
||||
if (copy_from_user(data.alarm, (void __user *)arg, 14)) {
|
||||
ret = -EFAULT;
|
||||
pr_err("%s: SET ret=%d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
pon_alarm_parse_data(data.alarm, &alm);
|
||||
pon_alarm_set(&alm);
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pon_alarm_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations pon_alarm_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = pon_alarm_open,
|
||||
.read = pon_alarm_read,
|
||||
.unlocked_ioctl = pon_alarm_ioctl,
|
||||
.release = pon_alarm_release,
|
||||
};
|
||||
|
||||
static struct miscdevice pon_alarm_device = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "power_on_alarm",
|
||||
.fops = &pon_alarm_fops,
|
||||
};
|
||||
|
||||
|
||||
static int __init pon_alarm_init(void)
|
||||
{
|
||||
struct rtc_device *rtcdev;
|
||||
int ret;
|
||||
|
||||
rtcdev = alarmtimer_get_rtcdev();
|
||||
if (!rtcdev) {
|
||||
pr_err("%s: no rtcdev (defer)\n", __func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = misc_register(&pon_alarm_device);
|
||||
if (ret) {
|
||||
pr_err("%s: ret=%d, fail to register misc device\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pr_info("%s: rtcalarm=%u, lpcharge=%u\n", __func__, rtcalarm, lpcharge);
|
||||
pon_alarm.rtcdev = rtcdev;
|
||||
pon_alarm.lpm_mode = lpcharge;
|
||||
pon_alarm.triggered = 0;
|
||||
pon_alarm.val.enabled = (rtcalarm) ? 1 : 0;
|
||||
rtc_time64_to_tm((time64_t)rtcalarm, &pon_alarm.val.time);
|
||||
|
||||
if (pon_alarm.lpm_mode && pon_alarm.val.enabled) {
|
||||
pr_info("%s: reserve pmic alarm\n", __func__);
|
||||
pmic_rtc_setalarm(&pon_alarm.val);
|
||||
pon_alarm.ws = wakeup_source_register(pon_alarm.dev, "PON_ALARM");
|
||||
|
||||
alarm_init(&pon_alarm.check_poll, ALARM_REALTIME, pon_alarm_check_callback);
|
||||
INIT_WORK(&pon_alarm.check_func, pon_alarm_check_func);
|
||||
alarm_start_relative(&pon_alarm.check_poll, ns_to_ktime(PON_ALARM_START_POLL_TIME));
|
||||
}
|
||||
|
||||
pr_info("%s: done\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit pon_alarm_exit(void)
|
||||
{
|
||||
misc_deregister(&pon_alarm_device);
|
||||
if (pon_alarm.lpm_mode && pon_alarm.val.enabled) {
|
||||
pr_info("%s: clear for lpm_mode\n", __func__);
|
||||
cancel_work_sync(&pon_alarm.check_func);
|
||||
alarm_cancel(&pon_alarm.check_poll);
|
||||
wakeup_source_unregister(pon_alarm.ws);
|
||||
}
|
||||
}
|
||||
|
||||
module_init(pon_alarm_init);
|
||||
module_exit(pon_alarm_exit);
|
||||
|
||||
MODULE_ALIAS("platform:sec_pon_alarm");
|
||||
MODULE_DESCRIPTION("SEC_PON_ALARM driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
Reference in New Issue
Block a user