From 4954d7062a3e96b36013c04bae6501a91d28340b Mon Sep 17 00:00:00 2001 From: Dj Date: Mon, 26 Oct 2026 02:34:18 -0400 Subject: [PATCH 1/4] mfd: macsmc: Support T6050 power and pcIO consumers Add the T6050 SMC consumer profile, described pcIO GPIO outputs, power-supply identity sizing, and charging policy. Preserve the newer shared SRAM accessor and metadata serialization. Source snapshot: t6050-gpu ee68abb105200e99636a5edfc647a5cfc1d89954 Integration base: 9cbdd0734d9e186c5f5f5949a21b909d10c2571b Signed-off-by: Dj Signed-off-by: Erik Bourget --- .../devicetree/bindings/mfd/apple,smc.yaml | 2 +- drivers/gpio/gpio-macsmc.c | 77 ++++++++++++++++++- drivers/mfd/macsmc.c | 35 +++++++-- drivers/power/supply/macsmc-power.c | 41 ++++++++-- 4 files changed, 141 insertions(+), 14 deletions(-) diff --git a/Documentation/devicetree/bindings/mfd/apple,smc.yaml b/Documentation/devicetree/bindings/mfd/apple,smc.yaml index 050ac9c6388ca..c35e7d0e10faa 100644 --- a/Documentation/devicetree/bindings/mfd/apple,smc.yaml +++ b/Documentation/devicetree/bindings/mfd/apple,smc.yaml @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) AND GPL-2.0-only %YAML 1.2 --- $id: http://devicetree.org/schemas/mfd/apple,smc.yaml# diff --git a/drivers/gpio/gpio-macsmc.c b/drivers/gpio/gpio-macsmc.c index 5f43b79b6013c..6a26f900bba28 100644 --- a/drivers/gpio/gpio-macsmc.c +++ b/drivers/gpio/gpio-macsmc.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-only OR MIT +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) AND GPL-2.0-only /* * Apple SMC GPIO driver * Copyright The Asahi Linux Contributors @@ -16,6 +16,7 @@ #include #include #include +#include #define MAX_GPIO 64 @@ -103,6 +104,7 @@ struct macsmc_gpio { struct apple_smc *smc; struct gpio_chip gc; + u64 *output_commands; int first_index; smc_key base_key; @@ -383,6 +385,76 @@ static int macsmc_gpio_init_valid_mask(struct gpio_chip *gc, return 0; } +/* Each DT template identifies one output; its low 16 bits carry the value. */ +static int macsmc_pcio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct macsmc_gpio *smcgp = gpiochip_get_data(gc); + u8 command[8]; + int ret; + + put_unaligned_le64(smcgp->output_commands[offset] | !!value, command); + ret = apple_smc_write(smcgp->smc, SMC_KEY(pcIO), command, sizeof(command)); + /* SMC writes return status, not a byte count. */ + return ret < 0 ? ret : 0; +} + +static int macsmc_pcio_get(struct gpio_chip *gc, unsigned int offset) +{ + /* The native output command has no established input/readback operation. */ + return -EOPNOTSUPP; +} + +static int macsmc_pcio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + return GPIO_LINE_DIRECTION_OUT; +} + +static int macsmc_pcio_probe(struct macsmc_gpio *smcgp) +{ + struct device *dev = smcgp->dev; + struct apple_smc_key_info info; + int count, ret; + + /* A board that uses none of the outputs leaves the list out. */ + if (!of_property_present(dev->of_node, "apple,output-commands")) + return -ENODEV; + + ret = apple_smc_get_key_info(smcgp->smc, SMC_KEY(pcIO), &info); + if (ret < 0) + return ret; + if (info.size != 8 || !(info.flags & APPLE_SMC_WRITABLE)) + return dev_err_probe(dev, -EINVAL, "pcIO is not an eight-byte output key\n"); + count = of_property_count_u64_elems(dev->of_node, "apple,output-commands"); + if (count <= 0 || count > MAX_GPIO) + return -EINVAL; + smcgp->output_commands = devm_kcalloc(dev, count, sizeof(u64), GFP_KERNEL); + if (!smcgp->output_commands) + return -ENOMEM; + ret = of_property_read_u64_array(dev->of_node, "apple,output-commands", + smcgp->output_commands, count); + if (ret) + return ret; + for (int i = 0; i < count; i++) { + if (smcgp->output_commands[i] & 0xffff) + return -EINVAL; + for (int j = 0; j < i; j++) + if (smcgp->output_commands[i] == smcgp->output_commands[j]) + return -EINVAL; + } + + smcgp->gc.label = "macsmc-pcio-output"; + smcgp->gc.owner = THIS_MODULE; + smcgp->gc.get = macsmc_pcio_get; + smcgp->gc.set = macsmc_pcio_set; + smcgp->gc.direction_output = macsmc_pcio_set; + smcgp->gc.get_direction = macsmc_pcio_get_direction; + smcgp->gc.can_sleep = true; + smcgp->gc.ngpio = count; + smcgp->gc.base = -1; + smcgp->gc.parent = dev; + return devm_gpiochip_add_data(dev, &smcgp->gc, smcgp); +} + struct macsmc_gpio_of_match_data { smc_key base_key; }; @@ -449,6 +521,8 @@ static int macsmc_gpio_probe(struct platform_device *pdev) smcgp->dev = &pdev->dev; smcgp->smc = smc; + if (device_is_compatible(&pdev->dev, "apple,smc-pcio-gpio")) + return macsmc_pcio_probe(smcgp); smcgp->base_key = data ? data->base_key : _SMC_KEY("gP\0\0"); smcgp->j700_pcio = of_machine_is_compatible("apple,j700") && device_is_compatible(&pdev->dev, "apple,smc-gpio"); @@ -498,6 +572,7 @@ static const struct macsmc_gpio_of_match_data macsmc_gpio_low_data = { }; static const struct of_device_id macsmc_gpio_of_table[] = { + { .compatible = "apple,smc-pcio-gpio" }, { .compatible = "apple,smc-gpio", .data = &macsmc_gpio_up_data, diff --git a/drivers/mfd/macsmc.c b/drivers/mfd/macsmc.c index 546e5cdbb72ad..80f965194bc3a 100644 --- a/drivers/mfd/macsmc.c +++ b/drivers/mfd/macsmc.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-only OR MIT +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) AND GPL-2.0-only /* * Apple SMC (System Management Controller) MFD driver * @@ -59,6 +59,16 @@ static const struct mfd_cell apple_smc_devs[] = { MFD_CELL_OF("macsmc-rtc", NULL, NULL, 0, 0, "apple,smc-rtc"), }; +/* This profile exposes notifications and the explicitly described outputs. */ +static const struct mfd_cell apple_smc_t6050_devs[] = { + MFD_CELL_NAME("macsmc-input"), + MFD_CELL_NAME("macsmc-power"), + MFD_CELL_OF("macsmc-hwmon", NULL, NULL, 0, 0, "apple,smc-hwmon"), +}; + +static const struct mfd_cell apple_smc_pcio_dev = + MFD_CELL_OF("macsmc-gpio", NULL, NULL, 0, 0, "apple,smc-pcio-gpio"); + /* The second GPIO bank ('gp' keys) only exists on some SMCs. */ static const struct mfd_cell apple_smc_low_gpio_dev = MFD_CELL_OF("macsmc-low-gpio", NULL, NULL, 0, 0, "apple,smc-low-gpio"); @@ -250,8 +260,8 @@ int apple_smc_get_key_info(struct apple_smc *smc, smc_key key, struct apple_smc_ u8 key_info[6]; int ret; + /* Keep the shared reply buffer stable until its metadata is copied. */ guard(mutex)(&smc->mutex); - ret = apple_smc_cmd_locked(smc, SMC_MSG_GET_KEY_INFO, key, 0, 0, NULL); if (ret >= 0 && info) { apple_smc_read_sram(smc, key_info, sizeof(key_info)); @@ -559,12 +569,26 @@ static int apple_smc_probe(struct platform_device *pdev) if (ret) return ret; - ret = devm_mfd_add_devices(smc->dev, PLATFORM_DEVID_NONE, - apple_smc_devs, ARRAY_SIZE(apple_smc_devs), - NULL, 0, NULL); + if (of_device_is_compatible(dev->of_node, "apple,t6050-smc")) + ret = devm_mfd_add_devices(smc->dev, PLATFORM_DEVID_NONE, + apple_smc_t6050_devs, + ARRAY_SIZE(apple_smc_t6050_devs), NULL, 0, NULL); + else + ret = devm_mfd_add_devices(smc->dev, PLATFORM_DEVID_NONE, + apple_smc_devs, ARRAY_SIZE(apple_smc_devs), + NULL, 0, NULL); if (ret) return dev_err_probe(smc->dev, ret, "Failed to register sub-devices"); + np = of_get_compatible_child(dev->of_node, "apple,smc-pcio-gpio"); + if (np) { + of_node_put(np); + ret = devm_mfd_add_devices(smc->dev, PLATFORM_DEVID_NONE, + &apple_smc_pcio_dev, 1, NULL, 0, NULL); + if (ret) + return dev_err_probe(dev, ret, "Failed to register pcIO outputs\n"); + } + /* * Register the second GPIO bank only where the device tree describes * it: the MFD core would otherwise create an unbound device and warn @@ -585,6 +609,7 @@ static int apple_smc_probe(struct platform_device *pdev) static const struct of_device_id apple_smc_of_match[] = { { .compatible = "apple,t8152-smc" }, + { .compatible = "apple,t6050-smc" }, { .compatible = "apple,t8103-smc" }, { .compatible = "apple,smc" }, {}, diff --git a/drivers/power/supply/macsmc-power.c b/drivers/power/supply/macsmc-power.c index 1bc23703654a4..1e306c7bb3f6c 100644 --- a/drivers/power/supply/macsmc-power.c +++ b/drivers/power/supply/macsmc-power.c @@ -960,10 +960,34 @@ static int macsmc_power_event(struct notifier_block *nb, unsigned long event, vo return NOTIFY_DONE; } +static int macsmc_read_identity(struct apple_smc *smc, smc_key key, + char *out, size_t capacity) +{ + struct apple_smc_key_info info; + int ret; + + if (!capacity) + return -EINVAL; + out[0] = '\0'; + ret = apple_smc_get_key_info(smc, key, &info); + if (ret < 0) + return ret; + if (!info.size || info.size >= capacity || !(info.flags & APPLE_SMC_READABLE)) + return -EINVAL; + /* SMC read requests must describe this key, not the destination buffer. */ + ret = apple_smc_read(smc, key, out, info.size); + if (ret != info.size) { + out[0] = '\0'; + return ret < 0 ? ret : -EIO; + } + out[info.size] = '\0'; + return 0; +} + static int macsmc_power_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); + struct apple_smc *smc; struct power_supply_config psy_cfg = {}; struct apple_smc_key_info info; struct macsmc_power *power; @@ -976,6 +1000,9 @@ static int macsmc_power_probe(struct platform_device *pdev) enum power_supply_property *props; size_t nprops; + if (!dev->parent) + return dev_err_probe(dev, -ENODEV, "Missing SMC parent\n"); + smc = dev_get_drvdata(dev->parent); if (!smc) return -ENODEV; @@ -1121,12 +1148,12 @@ static int macsmc_power_probe(struct platform_device *pdev) power->batt_desc.num_properties = nprops; /* Fetch identity strings */ - apple_smc_read(smc, SMC_KEY(BMDN), power->model_name, - sizeof(power->model_name) - 1); - apple_smc_read(smc, SMC_KEY(BMSN), power->serial_number, - sizeof(power->serial_number) - 1); - apple_smc_read(smc, SMC_KEY(BMDT), power->mfg_date, - sizeof(power->mfg_date) - 1); + macsmc_read_identity(smc, SMC_KEY(BMDN), power->model_name, + sizeof(power->model_name)); + macsmc_read_identity(smc, SMC_KEY(BMSN), power->serial_number, + sizeof(power->serial_number)); + macsmc_read_identity(smc, SMC_KEY(BMDT), power->mfg_date, + sizeof(power->mfg_date)); apple_smc_read_u8(power->smc, SMC_KEY(BNCB), &power->num_cells); power->nominal_voltage_mv = MACSMC_NOMINAL_CELL_VOLTAGE_MV * power->num_cells; From 853ccc6231b615d39ea101dd436f65c148bdf195 Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Wed, 4 Nov 2026 11:59:40 -0800 Subject: [PATCH 2/4] mfd: macsmc: Register T6050's reboot and RTC cells where the DT has them The T6050 profile (Aurora's t6050/smc) registers input, power and hwmon only, so a T6050 kernel that owns the machine (J775D on bare metal) had no SMC restart or power-off and no RTC: poweroff halted Linux and left the machine on. Add macsmc-reboot and macsmc-rtc to the T6050 profile as cells that are registered only where the device tree has their child node, the rule the pcIO outputs and the second GPIO bank already follow; those two blocks become one table and one helper. A T6050 device tree without reboot or rtc children registers exactly what it did, and other SMCs are unchanged (same cells in the same order; the registration failure messages now name the cell). Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 Signed-off-by: Erik Bourget --- drivers/mfd/macsmc.c | 80 +++++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/drivers/mfd/macsmc.c b/drivers/mfd/macsmc.c index 80f965194bc3a..927ddaa00e0df 100644 --- a/drivers/mfd/macsmc.c +++ b/drivers/mfd/macsmc.c @@ -66,12 +66,47 @@ static const struct mfd_cell apple_smc_t6050_devs[] = { MFD_CELL_OF("macsmc-hwmon", NULL, NULL, 0, 0, "apple,smc-hwmon"), }; -static const struct mfd_cell apple_smc_pcio_dev = - MFD_CELL_OF("macsmc-gpio", NULL, NULL, 0, 0, "apple,smc-pcio-gpio"); +/* + * Cells registered only where the device tree describes them: the MFD core would otherwise + * create an unbound device and warn that it found no node on every SMC without one. The pcIO + * outputs and the second GPIO bank ('gp' keys) only exist on some SMCs. + */ +static const struct mfd_cell apple_smc_described_devs[] = { + MFD_CELL_OF("macsmc-gpio", NULL, NULL, 0, 0, "apple,smc-pcio-gpio"), + MFD_CELL_OF("macsmc-low-gpio", NULL, NULL, 0, 0, "apple,smc-low-gpio"), +}; -/* The second GPIO bank ('gp' keys) only exists on some SMCs. */ -static const struct mfd_cell apple_smc_low_gpio_dev = - MFD_CELL_OF("macsmc-low-gpio", NULL, NULL, 0, 0, "apple,smc-low-gpio"); +/* + * T6050's reboot/power-off and RTC, for a kernel that owns the machine (J775D, booted bare + * metal). Registered only where the device tree describes them, so a T6050 device tree without + * them registers what it did before. + */ +static const struct mfd_cell apple_smc_t6050_described_devs[] = { + MFD_CELL_OF("macsmc-reboot", NULL, NULL, 0, 0, "apple,smc-reboot"), + MFD_CELL_OF("macsmc-rtc", NULL, NULL, 0, 0, "apple,smc-rtc"), +}; + +static int apple_smc_add_described_devices(struct apple_smc *smc, const struct mfd_cell *cells, + size_t count) +{ + struct device_node *np; + size_t i; + int ret; + + for (i = 0; i < count; i++) { + np = of_get_compatible_child(smc->dev->of_node, cells[i].of_compatible); + if (!np) + continue; + of_node_put(np); + ret = devm_mfd_add_devices(smc->dev, PLATFORM_DEVID_NONE, &cells[i], 1, + NULL, 0, NULL); + if (ret) + return dev_err_probe(smc->dev, ret, "Failed to register %s\n", + cells[i].name); + } + + return 0; +} /* T8152 currently exposes read-only telemetry; firmware owns fan control. */ static const struct mfd_cell apple_smc_telemetry_devs[] = { @@ -493,9 +528,9 @@ static void apple_smc_disable_notifications(void *data) static int apple_smc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct device_node *np; struct apple_smc *smc; __be32 count = 0; + bool t6050; int ret; smc = devm_kzalloc(dev, sizeof(*smc), GFP_KERNEL); @@ -569,7 +604,8 @@ static int apple_smc_probe(struct platform_device *pdev) if (ret) return ret; - if (of_device_is_compatible(dev->of_node, "apple,t6050-smc")) + t6050 = of_device_is_compatible(dev->of_node, "apple,t6050-smc"); + if (t6050) ret = devm_mfd_add_devices(smc->dev, PLATFORM_DEVID_NONE, apple_smc_t6050_devs, ARRAY_SIZE(apple_smc_t6050_devs), NULL, 0, NULL); @@ -580,31 +616,13 @@ static int apple_smc_probe(struct platform_device *pdev) if (ret) return dev_err_probe(smc->dev, ret, "Failed to register sub-devices"); - np = of_get_compatible_child(dev->of_node, "apple,smc-pcio-gpio"); - if (np) { - of_node_put(np); - ret = devm_mfd_add_devices(smc->dev, PLATFORM_DEVID_NONE, - &apple_smc_pcio_dev, 1, NULL, 0, NULL); - if (ret) - return dev_err_probe(dev, ret, "Failed to register pcIO outputs\n"); - } - - /* - * Register the second GPIO bank only where the device tree describes - * it: the MFD core would otherwise create an unbound device and warn - * that it found no node on every SMC without one. - */ - np = of_get_compatible_child(dev->of_node, "apple,smc-low-gpio"); - if (np) { - of_node_put(np); - ret = devm_mfd_add_devices(smc->dev, PLATFORM_DEVID_NONE, - &apple_smc_low_gpio_dev, 1, NULL, 0, NULL); - if (ret) - return dev_err_probe(smc->dev, ret, - "Failed to register the second GPIO bank"); - } + ret = apple_smc_add_described_devices(smc, apple_smc_described_devs, + ARRAY_SIZE(apple_smc_described_devs)); + if (ret || !t6050) + return ret; - return 0; + return apple_smc_add_described_devices(smc, apple_smc_t6050_described_devs, + ARRAY_SIZE(apple_smc_t6050_described_devs)); } static const struct of_device_id apple_smc_of_match[] = { From ad0f93637543787d6156515baf9a38422add5c29 Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Wed, 4 Nov 2026 11:59:40 -0800 Subject: [PATCH 3/4] hwmon: macsmc: Let discovered temperatures join the SoC die zone apple,soc-die-keys adds a "SoC die " channel per listed key for the macsmc_soc_die zone, and apple,discover-temperatures refuses to run once any temperature channel exists, so a machine that discovers its temperatures (T8142, J775D) could not have the zone: the probe failed with -EINVAL. With discovery, a listed key now adds no channel; the channel discovery creates for it (labelled with the key) is marked for the zone instead. Machines that set only one of the two properties are unchanged. Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 Signed-off-by: Erik Bourget --- .../bindings/hwmon/apple,smc-hwmon.yaml | 4 +++- Documentation/hwmon/macsmc-hwmon.rst | 2 ++ drivers/hwmon/macsmc-hwmon.c | 15 ++++++++++++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/hwmon/apple,smc-hwmon.yaml b/Documentation/devicetree/bindings/hwmon/apple,smc-hwmon.yaml index a4e4dfd3c0886..5ba2a01b323e5 100644 --- a/Documentation/devicetree/bindings/hwmon/apple,smc-hwmon.yaml +++ b/Documentation/devicetree/bindings/hwmon/apple,smc-hwmon.yaml @@ -62,7 +62,9 @@ properties: follow the temperature-* children in the listed order and are labelled "SoC die ". Keys absent from the running firmware or with an unsupported format are skipped. These channels do not change the indices - used by thermal-sensors references to temperature-* children. + used by thermal-sensors references to temperature-* children. With + apple,discover-temperatures, a listed key adds no channel; its + discovered channel (labelled with the key) supplies the zone. apple,thermal-pressure-key: $ref: /schemas/types.yaml#/definitions/string diff --git a/Documentation/hwmon/macsmc-hwmon.rst b/Documentation/hwmon/macsmc-hwmon.rst index b0c78c3412088..23baece30154a 100644 --- a/Documentation/hwmon/macsmc-hwmon.rst +++ b/Documentation/hwmon/macsmc-hwmon.rst @@ -43,6 +43,8 @@ Only readable four-byte floating-point keys are used. When thermal support is enabled, their hottest reading supplies the ``macsmc_soc_die`` thermal zone. A failed or implausible reading fails the zone read rather than reporting a partial result. Keys absent from the firmware are skipped. +With ``apple,discover-temperatures`` the listed keys add no channels: the +discovered channel of each listed key, labelled with the key, joins the zone. The optional ``apple,thermal-pressure-key`` on the same node describes a readable signed 32-bit firmware thermal pressure level. Neither feature needs diff --git a/drivers/hwmon/macsmc-hwmon.c b/drivers/hwmon/macsmc-hwmon.c index d0206489fa372..ddf287a190efa 100644 --- a/drivers/hwmon/macsmc-hwmon.c +++ b/drivers/hwmon/macsmc-hwmon.c @@ -901,6 +901,9 @@ static int macsmc_hwmon_discover_temperatures(struct macsmc_hwmon *hwmon) if (!smc->key_count || smc->key_count > U16_MAX) return -EINVAL; + ret = of_property_count_strings(hwmon->dev->of_node, "apple,soc-die-keys"); + hwmon->die_listed = ret > 0 ? ret : 0; + hwmon->temp.sensors = devm_kcalloc(hwmon->dev, smc->key_count, sizeof(*sensor), GFP_KERNEL); if (!hwmon->temp.sensors) @@ -934,6 +937,12 @@ static int macsmc_hwmon_discover_temperatures(struct macsmc_hwmon *hwmon) /* Key identity only: do not invent locations or calibration. */ snprintf(sensor->label, sizeof(sensor->label), "%c%c%c%c", key >> 24, (key >> 16) & 0xff, (key >> 8) & 0xff, key & 0xff); + /* A listed SoC die key keeps its discovered channel and joins the die zone. */ + if (of_property_match_string(hwmon->dev->of_node, "apple,soc-die-keys", + sensor->label) >= 0) { + sensor->die = true; + hwmon->die_count++; + } } return 0; @@ -1117,7 +1126,11 @@ static int macsmc_hwmon_populate_sensors(struct macsmc_hwmon *hwmon, for_each_child_of_node_with_prefix(hwmon_node, key_node, "temperature-") { n_temperature++; } - n_temperature += n_die_keys; + /* With discovery, listed die keys mark discovered channels instead (see there). */ + if (!of_property_read_bool(hwmon_node, "apple,discover-temperatures")) + n_temperature += n_die_keys; + else + n_die_keys = 0; if (n_temperature) { hwmon->temp.sensors = devm_kcalloc(hwmon->dev, n_temperature, From 4d11bfdc0ef5e960fd4712854bdc34fbdb2ee955 Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Tue, 3 Nov 2026 01:35:36 -0800 Subject: [PATCH 4/4] power: reset: macsmc-reboot: Add ac_power_mode, with power-on at connect Expose the PMU's power-on policy byte as the ac_power_mode attribute Asahi's downstream driver had, with a third mode. The byte has two policy bits: bit 0 keeps the machine off when power returns after a loss, bit 5 keeps it off when power is connected. Asahi's driver wrote the whole byte (0x02 restore, 0x03 off), which clears bit 5: on machines whose PMU powers on at connect that write means "always", the opposite of "off". Change only the two bits and leave the rest to the PMU: off bit 0 and bit 5 set restore bit 5 set, bit 0 clear always bit 5 clear (bit 0 set) The pm_setting cell is optional; the attribute appears only with it. The binding takes it as a fifth cell, as the t600x/t602x device trees already list it. Based on Asahi's ac_power_mode (marcan). Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 Signed-off-by: Erik Bourget --- .../ABI/testing/sysfs-driver-macsmc-reboot | 16 +++ drivers/power/reset/macsmc-reboot.c | 118 ++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-macsmc-reboot diff --git a/Documentation/ABI/testing/sysfs-driver-macsmc-reboot b/Documentation/ABI/testing/sysfs-driver-macsmc-reboot new file mode 100644 index 0000000000000..8a485592fedba --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-macsmc-reboot @@ -0,0 +1,16 @@ +What: /sys/bus/platform/drivers/macsmc-reboot//ac_power_mode +Date: November 2026 +KernelVersion: 7.4 +Description: + What the machine does when external power arrives, as kept in + the PMU (present only when the device tree gives the reboot + node a pm_setting cell). Reading lists the modes with the + current one in brackets; writing a mode name sets it: + + off: + stay off + restore: + power on after a power loss if the machine was on + always: + power on whenever power is connected (the 2024 and later + desktops; other machines may ignore it) diff --git a/drivers/power/reset/macsmc-reboot.c b/drivers/power/reset/macsmc-reboot.c index 7688cfd4ed60d..a2e1422e05bd8 100644 --- a/drivers/power/reset/macsmc-reboot.c +++ b/drivers/power/reset/macsmc-reboot.c @@ -8,10 +8,12 @@ #include #include #include +#include #include #include #include #include +#include struct macsmc_reboot_nvmem { struct nvmem_cell *shutdown_flag; @@ -33,10 +35,39 @@ enum boot_stage { BOOT_STAGE_KERNEL_STARTED = 0x30, /* Normal OS booting */ }; +/* + * The PMU's power-on policy byte: bit 0 set keeps the machine off when power returns after a + * loss, bit 5 set keeps it off when power is connected (bit 5 clear powers it on at connect + * whatever bit 0 says). Changes are read-modify-write: the other bits are the PMU's. + */ +#define PM_SETTING_NO_RESTORE BIT(0) +#define PM_SETTING_NO_POWER_ON_CONNECT BIT(5) +#define PM_SETTING_AC_POWER_MASK (PM_SETTING_NO_RESTORE | PM_SETTING_NO_POWER_ON_CONNECT) + +enum ac_power_mode { + AC_POWER_OFF, /* stay off */ + AC_POWER_RESTORE, /* power on after a power loss if it was on */ + AC_POWER_ALWAYS, /* power on whenever power is connected */ +}; + +static const char * const ac_power_modes[] = { + [AC_POWER_OFF] = "off", + [AC_POWER_RESTORE] = "restore", + [AC_POWER_ALWAYS] = "always", +}; + +static const u8 ac_power_mode_bits[] = { + [AC_POWER_OFF] = PM_SETTING_NO_RESTORE | PM_SETTING_NO_POWER_ON_CONNECT, + [AC_POWER_RESTORE] = PM_SETTING_NO_POWER_ON_CONNECT, + [AC_POWER_ALWAYS] = PM_SETTING_NO_RESTORE, +}; + struct macsmc_reboot { struct device *dev; struct apple_smc *smc; struct notifier_block reboot_notify; + struct nvmem_cell *pm_setting; + struct mutex pm_setting_lock; union { struct macsmc_reboot_nvmem nvm; @@ -198,6 +229,80 @@ static void macsmc_power_init_error_counts(struct macsmc_reboot *reboot) dev_err(reboot->dev, "Failed to reset boot_error_count\n"); } +static enum ac_power_mode macsmc_ac_power_mode(u8 val) +{ + if (!(val & PM_SETTING_NO_POWER_ON_CONNECT)) + return AC_POWER_ALWAYS; + if (val & PM_SETTING_NO_RESTORE) + return AC_POWER_OFF; + return AC_POWER_RESTORE; +} + +static ssize_t ac_power_mode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct macsmc_reboot *reboot = dev_get_drvdata(dev); + enum ac_power_mode mode; + int val, i, len = 0; + + val = nvmem_cell_get_u8(reboot->pm_setting); + if (val < 0) + return val; + mode = macsmc_ac_power_mode(val); + + for (i = 0; i < ARRAY_SIZE(ac_power_modes); i++) + len += sysfs_emit_at(buf, len, i == mode ? "[%s] " : "%s ", ac_power_modes[i]); + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t ac_power_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct macsmc_reboot *reboot = dev_get_drvdata(dev); + int mode, val, ret; + u8 new; + + mode = sysfs_match_string(ac_power_modes, buf); + if (mode < 0) + return mode; + + mutex_lock(&reboot->pm_setting_lock); + val = nvmem_cell_get_u8(reboot->pm_setting); + if (val < 0) { + ret = val; + goto out; + } + new = (val & ~PM_SETTING_AC_POWER_MASK) | ac_power_mode_bits[mode]; + ret = new == val ? 0 : nvmem_cell_set_u8(reboot->pm_setting, new); + if (!ret && new != val) + dev_info(dev, "PM setting %02x => %02x (ac_power_mode %s)\n", val, new, + ac_power_modes[mode]); +out: + mutex_unlock(&reboot->pm_setting_lock); + + return ret < 0 ? ret : n; +} +static DEVICE_ATTR_RW(ac_power_mode); + +static struct attribute *macsmc_reboot_attrs[] = { + &dev_attr_ac_power_mode.attr, + NULL +}; + +static umode_t macsmc_reboot_attr_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct macsmc_reboot *reboot = dev_get_drvdata(kobj_to_dev(kobj)); + + return reboot && reboot->pm_setting ? attr->mode : 0; +} + +static const struct attribute_group macsmc_reboot_group = { + .attrs = macsmc_reboot_attrs, + .is_visible = macsmc_reboot_attr_visible, +}; +__ATTRIBUTE_GROUPS(macsmc_reboot); + static int macsmc_reboot_probe(struct platform_device *pdev) { struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); @@ -237,6 +342,18 @@ static int macsmc_reboot_probe(struct platform_device *pdev) reboot->nvm_cells[i] = cell; } + /* Optional: the power-on policy byte, for ac_power_mode */ + mutex_init(&reboot->pm_setting_lock); + reboot->pm_setting = devm_nvmem_cell_get(&pdev->dev, "pm_setting"); + if (IS_ERR(reboot->pm_setting)) { + ret = PTR_ERR(reboot->pm_setting); + if (ret == -EPROBE_DEFER) + return ret; + if (ret != -ENOENT) + dev_warn(&pdev->dev, "Missing NVMEM cell pm_setting (%d)\n", ret); + reboot->pm_setting = NULL; + } + /* Set the boot_stage to indicate we're running the OS kernel */ if (reboot->nvm.boot_stage && nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_KERNEL_STARTED) < 0) @@ -287,6 +404,7 @@ static struct platform_driver macsmc_reboot_driver = { .driver = { .name = "macsmc-reboot", .of_match_table = macsmc_reboot_of_table, + .dev_groups = macsmc_reboot_groups, }, .probe = macsmc_reboot_probe, };