Skip to content
Merged
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
16 changes: 16 additions & 0 deletions Documentation/ABI/testing/sysfs-driver-macsmc-reboot
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
What: /sys/bus/platform/drivers/macsmc-reboot/<dev>/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)
4 changes: 3 additions & 1 deletion Documentation/devicetree/bindings/hwmon/apple,smc-hwmon.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ properties:
follow the temperature-* children in the listed order and are labelled
"SoC die <key>". 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
Expand Down
2 changes: 1 addition & 1 deletion Documentation/devicetree/bindings/mfd/apple,smc.yaml
Original file line number Diff line number Diff line change
@@ -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#
Expand Down
2 changes: 2 additions & 0 deletions Documentation/hwmon/macsmc-hwmon.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
77 changes: 76 additions & 1 deletion drivers/gpio/gpio-macsmc.c
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,6 +16,7 @@
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/soc/apple/t8132.h>
#include <linux/unaligned.h>

#define MAX_GPIO 64

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
};
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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,
Expand Down
15 changes: 14 additions & 1 deletion drivers/hwmon/macsmc-hwmon.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
91 changes: 67 additions & 24 deletions drivers/mfd/macsmc.c
Original file line number Diff line number Diff line change
@@ -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
*
Expand Down Expand Up @@ -59,9 +59,54 @@ static const struct mfd_cell apple_smc_devs[] = {
MFD_CELL_OF("macsmc-rtc", NULL, NULL, 0, 0, "apple,smc-rtc"),
};

/* 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");
/* 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"),
};

/*
* 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"),
};

/*
* 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[] = {
Expand Down Expand Up @@ -250,8 +295,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));
Expand Down Expand Up @@ -483,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);
Expand Down Expand Up @@ -559,32 +604,30 @@ 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);
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);
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");

/*
* 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[] = {
{ .compatible = "apple,t8152-smc" },
{ .compatible = "apple,t6050-smc" },
{ .compatible = "apple,t8103-smc" },
{ .compatible = "apple,smc" },
{},
Expand Down
Loading
Loading