Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion Documentation/devicetree/bindings/rtc/apple,smc-rtc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ properties:
PMU alarm compare value, a 32-bit count of whole seconds of the RTC
counter without the offset.
- description: PMU alarm mask byte.
- description:
PMU alarm event byte, write 1 to clear. Maverick PMUs, such as the
M1 Pro's and the M2 Pro's, latch a fired alarm here as well as in the
control byte, and keep the alarm interrupt silent until it is
cleared.

nvmem-cell-names:
oneOf:
Expand All @@ -43,11 +48,17 @@ properties:
- const: rtc_ctrl
- const: rtc_alarm
- const: rtc_alarm_mask
- items:
- const: rtc_offset
- const: rtc_ctrl
- const: rtc_alarm
- const: rtc_alarm_mask
- const: rtc_alarm_event

interrupts:
maxItems: 1
description:
The PMU's RTC alarm interrupt. Only used with the three alarm cells.
The PMU's RTC alarm interrupt. Only used with the alarm cells.

wakeup-source: true

Expand Down
29 changes: 27 additions & 2 deletions arch/arm64/boot/dts/apple/t600x-die0.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,13 @@

rtc {
compatible = "apple,smc-rtc";
nvmem-cells = <&rtc_offset>;
nvmem-cell-names = "rtc_offset";
nvmem-cells = <&rtc_offset>, <&rtc_ctrl>, <&rtc_alarm>,
<&rtc_alarm_mask>, <&rtc_alarm_event>;
nvmem-cell-names = "rtc_offset", "rtc_ctrl", "rtc_alarm",
"rtc_alarm_mask", "rtc_alarm_event";
/* The PMU's RTC alarm, peripheral interrupt 2 of nub-spmi0 */
interrupts-extended = <&nub_spmi0 2 IRQ_TYPE_EDGE_RISING>;
wakeup-source;
};
};

Expand Down Expand Up @@ -345,6 +350,10 @@
reg = <0x2 0x920a1300 0x0 0x100>;
#address-cells = <2>;
#size-cells = <0>;
#interrupt-cells = <2>;
interrupt-controller;
interrupt-parent = <&aic>;
interrupts = <AIC_IRQ 0 636 IRQ_TYPE_LEVEL_HIGH>;

pmic1: pmic@f {
compatible = "apple,maverick-pmic", "apple,spmi-nvmem";
Expand All @@ -355,14 +364,30 @@
#address-cells = <1>;
#size-cells = <1>;

rtc_ctrl: rtc-ctrl@1401 {
reg = <0x1401 0x1>;
};

pm_setting: pm-setting@1405 {
reg = <0x1405 0x1>;
};

rtc_alarm: rtc-alarm@140c {
reg = <0x140c 0x4>;
};

rtc_offset: rtc-offset@1411 {
reg = <0x1411 0x6>;
};

rtc_alarm_event: rtc-alarm-event@14c0 {
reg = <0x14c0 0x1>;
};

rtc_alarm_mask: rtc-alarm-mask@14c2 {
reg = <0x14c2 0x1>;
};

boot_stage: boot-stage@6001 {
reg = <0x6001 0x1>;
};
Expand Down
58 changes: 48 additions & 10 deletions drivers/rtc/rtc-macsmc.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@
#define RTC_CTRL_ALARM_EVENT BIT(1)
#define RTC_CTRL_ALARM_EN BIT(6)

/*
* The time comes from the SMC's CLKM, which lags the PMU's counter, the
* alarm's comparator, by a few ms (5.8 ms on J314s). With a tick offset whose
* fraction is smaller than that, the alarm would fire while the time still
* reads the second before. The RTC core then sets the same second again, the
* alarm fires again at once, and so on until the time catches up: five or six
* interrupts instead of one. So below this margin the alarm is armed one counter
* second later, and fires within the set second.
*/
#define RTC_ALARM_MARGIN_TICKS (1 << (RTC_SEC_SHIFT - 5)) /* 1/32 s */

struct macsmc_rtc {
struct device *dev;
struct apple_smc *smc;
Expand All @@ -45,6 +56,12 @@ struct macsmc_rtc {
struct nvmem_cell *alarm_ctrl;
struct nvmem_cell *alarm_value;
struct nvmem_cell *alarm_mask;
/*
* Maverick PMUs (the M1 Pro's and the M2 Pro's) also latch the event in
* a separate, write-1-to-clear interrupt status register, and their
* control byte's event bit clears when it is written as 0, not as 1.
*/
struct nvmem_cell *alarm_event;
struct mutex alarm_lock;
u32 alarm_secs;
};
Expand Down Expand Up @@ -174,8 +191,12 @@ static int macsmc_rtc_write_u8(struct nvmem_cell *cell, u8 val)
return ret < 0 ? ret : 0;
}

/* The wall-clock offset in whole seconds: wall time = counter seconds + offset */
static int macsmc_rtc_offset_secs(struct macsmc_rtc *rtc, s64 *secs)
/*
* The wall-clock offset in whole seconds (wall time = counter seconds + offset),
* and the counter seconds to add to an alarm for RTC_ALARM_MARGIN_TICKS: 1 with
* a tick offset whose fraction is under the margin, else 0.
*/
static int macsmc_rtc_offset_secs(struct macsmc_rtc *rtc, s64 *secs, int *late)
{
u64 off = 0;
size_t len;
Expand All @@ -191,13 +212,15 @@ static int macsmc_rtc_offset_secs(struct macsmc_rtc *rtc, s64 *secs)
return -EIO;
}
*secs = (s64)get_unaligned_le64(p);
*late = 0;
} else {
if (len < RTC_BYTES) {
kfree(p);
return -EIO;
}
memcpy(&off, p, RTC_BYTES);
*secs = sign_extend64(off, RTC_BITS - 1) >> RTC_SEC_SHIFT;
*late = (off & ((1 << RTC_SEC_SHIFT) - 1)) < RTC_ALARM_MARGIN_TICKS;
}
kfree(p);

Expand All @@ -206,8 +229,11 @@ static int macsmc_rtc_offset_secs(struct macsmc_rtc *rtc, s64 *secs)

/*
* Arm the alarm at counter second @secs, or disarm it if @secs is 0. The
* other bits of the control byte are kept. A latched event is cleared by
* writing it back, so the event bit is written as 1 only to clear it.
* other bits of the control byte are kept. A latched event is cleared first:
* on T6030 by writing the control byte back with the event bit set; on a
* Maverick PMU by writing 1 to its interrupt status, which must be clear for
* the next alarm to raise an interrupt, while the control byte's event bit
* clears below, where it is written as 0.
*/
static int macsmc_rtc_program_alarm(struct macsmc_rtc *rtc, u32 secs)
{
Expand All @@ -218,7 +244,11 @@ static int macsmc_rtc_program_alarm(struct macsmc_rtc *rtc, u32 secs)
ret = macsmc_rtc_read_u8(rtc->alarm_ctrl, &ctrl);
if (ret)
return ret;
if (ctrl & RTC_CTRL_ALARM_EVENT) {
if (rtc->alarm_event) {
ret = macsmc_rtc_write_u8(rtc->alarm_event, 1);
if (ret)
return ret;
} else if (ctrl & RTC_CTRL_ALARM_EVENT) {
ret = macsmc_rtc_write_u8(rtc->alarm_ctrl, ctrl);
if (ret)
return ret;
Expand Down Expand Up @@ -250,11 +280,12 @@ static int macsmc_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
size_t len;
s64 off;
u32 secs;
int late;
void *p;
u8 ctrl;
int ret;

ret = macsmc_rtc_offset_secs(rtc, &off);
ret = macsmc_rtc_offset_secs(rtc, &off, &late);
if (ret)
return ret;

Expand All @@ -270,7 +301,8 @@ static int macsmc_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
secs = len >= sizeof(u32) ? get_unaligned_le32(p) : 0;
kfree(p);

rtc_time64_to_tm((time64_t)secs + off, &alrm->time);
/* The time that was set, not the counter second it was armed for */
rtc_time64_to_tm((time64_t)secs - late + off, &alrm->time);
alrm->enabled = !!(ctrl & RTC_CTRL_ALARM_EN);
alrm->pending = !!(ctrl & RTC_CTRL_ALARM_EVENT);
out:
Expand All @@ -282,18 +314,21 @@ static int macsmc_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
{
struct macsmc_rtc *rtc = dev_get_drvdata(dev);
s64 off, secs;
int late;
int ret;

ret = macsmc_rtc_offset_secs(rtc, &off);
ret = macsmc_rtc_offset_secs(rtc, &off, &late);
if (ret)
return ret;

/*
* With a tick offset, off is rounded down to whole seconds but the
* time is floor((ctr + off) / 2^15), so the alarm fires at the set
* second or up to one second after it.
* second or up to one second after it. With the offset's fraction
* under RTC_ALARM_MARGIN_TICKS, the alarm is armed a second later, so
* it can't fire while the time still reads the second before.
*/
secs = rtc_tm_to_time64(&alrm->time) - off;
secs = rtc_tm_to_time64(&alrm->time) - off + late;
if (secs <= 0 || secs > U32_MAX)
return -ERANGE;

Expand Down Expand Up @@ -366,12 +401,15 @@ static int macsmc_rtc_init_alarm(struct platform_device *pdev,
rtc->alarm_ctrl = macsmc_rtc_cell_optional(dev, "rtc_ctrl");
rtc->alarm_value = macsmc_rtc_cell_optional(dev, "rtc_alarm");
rtc->alarm_mask = macsmc_rtc_cell_optional(dev, "rtc_alarm_mask");
rtc->alarm_event = macsmc_rtc_cell_optional(dev, "rtc_alarm_event");
if (IS_ERR(rtc->alarm_ctrl))
return PTR_ERR(rtc->alarm_ctrl);
if (IS_ERR(rtc->alarm_value))
return PTR_ERR(rtc->alarm_value);
if (IS_ERR(rtc->alarm_mask))
return PTR_ERR(rtc->alarm_mask);
if (IS_ERR(rtc->alarm_event))
return PTR_ERR(rtc->alarm_event);
if (!rtc->alarm_ctrl || !rtc->alarm_value || !rtc->alarm_mask)
return -ENODEV;

Expand Down
Loading