diff options
| -rw-r--r-- | Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml | 29 | ||||
| -rw-r--r-- | Documentation/devicetree/bindings/mfd/apple,smc.yaml | 79 | ||||
| -rw-r--r-- | Documentation/devicetree/bindings/power/reset/apple,smc-reboot.yaml | 40 | ||||
| -rw-r--r-- | MAINTAINERS | 7 | ||||
| -rw-r--r-- | drivers/gpio/Kconfig | 10 | ||||
| -rw-r--r-- | drivers/gpio/Makefile | 1 | ||||
| -rw-r--r-- | drivers/gpio/gpio-macsmc.c | 292 | ||||
| -rw-r--r-- | drivers/mfd/Kconfig | 18 | ||||
| -rw-r--r-- | drivers/mfd/Makefile | 1 | ||||
| -rw-r--r-- | drivers/mfd/macsmc.c | 498 | ||||
| -rw-r--r-- | drivers/power/reset/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/power/reset/Makefile | 1 | ||||
| -rw-r--r-- | drivers/power/reset/macsmc-reboot.c | 290 | ||||
| -rw-r--r-- | drivers/soc/apple/rtkit.c | 3 | ||||
| -rw-r--r-- | include/linux/mfd/macsmc.h | 279 |
15 files changed, 1555 insertions, 2 deletions
diff --git a/Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml b/Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml new file mode 100644 index 00000000000000..42b1bc0a10c97a --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/apple,smc-gpio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple Mac System Management Controller GPIO + +maintainers: + - Sven Peter <sven@kernel.org> + +description: + Apple Mac System Management Controller GPIO block. + +properties: + compatible: + const: apple,smc-gpio + + gpio-controller: true + + '#gpio-cells': + const: 2 + +required: + - compatible + - gpio-controller + - '#gpio-cells' + +additionalProperties: false diff --git a/Documentation/devicetree/bindings/mfd/apple,smc.yaml b/Documentation/devicetree/bindings/mfd/apple,smc.yaml new file mode 100644 index 00000000000000..8a10e270d421ec --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/apple,smc.yaml @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/apple,smc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple Mac System Management Controller + +maintainers: + - Sven Peter <sven@kernel.org> + +description: + Apple Mac System Management Controller implements various functions + such as GPIO, RTC, power, reboot. + +properties: + compatible: + items: + - enum: + - apple,t6000-smc + - apple,t8103-smc + - apple,t8112-smc + - const: apple,smc + + reg: + items: + - description: SMC area + - description: SRAM area + + reg-names: + items: + - const: smc + - const: sram + + mboxes: + maxItems: 1 + + gpio: + $ref: /schemas/gpio/apple,smc-gpio.yaml + + reboot: + $ref: /schemas/power/reset/apple,smc-reboot.yaml + +additionalProperties: false + +required: + - compatible + - reg + - reg-names + - mboxes + +examples: + - | + soc { + #address-cells = <2>; + #size-cells = <2>; + + smc@23e400000 { + compatible = "apple,t8103-smc", "apple,smc"; + reg = <0x2 0x3e400000 0x0 0x4000>, + <0x2 0x3fe00000 0x0 0x100000>; + reg-names = "smc", "sram"; + mboxes = <&smc_mbox>; + + smc_gpio: gpio { + compatible = "apple,smc-gpio"; + gpio-controller; + #gpio-cells = <2>; + }; + + reboot { + compatible = "apple,smc-reboot"; + nvmem-cells = <&shutdown_flag>, <&boot_stage>, + <&boot_error_count>, <&panic_count>; + nvmem-cell-names = "shutdown_flag", "boot_stage", + "boot_error_count", "panic_count"; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/power/reset/apple,smc-reboot.yaml b/Documentation/devicetree/bindings/power/reset/apple,smc-reboot.yaml new file mode 100644 index 00000000000000..ce5ed88493cdb6 --- /dev/null +++ b/Documentation/devicetree/bindings/power/reset/apple,smc-reboot.yaml @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/reset/apple,smc-reboot.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple SMC Reboot Controller + +description: + The Apple System Management Controller (SMC) provides reboot functionality + on Apple Silicon SoCs. It uses NVMEM cells to store and track various + system state information related to boot, shutdown, and panic events. + +maintainers: + - Sven Peter <sven@kernel.org> + +properties: + compatible: + const: apple,smc-reboot + + nvmem-cells: + items: + - description: Flag indicating shutdown (as opposed to reboot) + - description: Stage at which the boot process stopped (0x30 for normal boot) + - description: Counter for boot errors + - description: Counter for system panics + + nvmem-cell-names: + items: + - const: shutdown_flag + - const: boot_stage + - const: boot_error_count + - const: panic_count + +required: + - compatible + - nvmem-cells + - nvmem-cell-names + +additionalProperties: false diff --git a/MAINTAINERS b/MAINTAINERS index 971ee194ee83fb..698bafdc99684a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2332,6 +2332,7 @@ F: Documentation/devicetree/bindings/arm/apple/* F: Documentation/devicetree/bindings/clock/apple,nco.yaml F: Documentation/devicetree/bindings/cpufreq/apple,cluster-cpufreq.yaml F: Documentation/devicetree/bindings/dma/apple,admac.yaml +F: Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml F: Documentation/devicetree/bindings/i2c/apple,i2c.yaml F: Documentation/devicetree/bindings/input/touchscreen/apple,z2-multitouch.yaml F: Documentation/devicetree/bindings/interrupt-controller/apple,* @@ -2339,6 +2340,7 @@ F: Documentation/devicetree/bindings/iommu/apple,dart.yaml F: Documentation/devicetree/bindings/iommu/apple,sart.yaml F: Documentation/devicetree/bindings/leds/backlight/apple,dwi-bl.yaml F: Documentation/devicetree/bindings/mailbox/apple,mailbox.yaml +F: Documentation/devicetree/bindings/mfd/apple,smc.yaml F: Documentation/devicetree/bindings/net/bluetooth/brcm,bcm4377-bluetooth.yaml F: Documentation/devicetree/bindings/nvme/apple,nvme-ans.yaml F: Documentation/devicetree/bindings/nvmem/apple,efuses.yaml @@ -2346,6 +2348,7 @@ F: Documentation/devicetree/bindings/nvmem/apple,spmi-nvmem.yaml F: Documentation/devicetree/bindings/pci/apple,pcie.yaml F: Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml F: Documentation/devicetree/bindings/power/apple* +F: Documentation/devicetree/bindings/power/reset/apple,smc-reboot.yaml F: Documentation/devicetree/bindings/pwm/apple,s5l-fpwm.yaml F: Documentation/devicetree/bindings/spi/apple,spi.yaml F: Documentation/devicetree/bindings/spmi/apple,spmi.yaml @@ -2355,6 +2358,7 @@ F: drivers/bluetooth/hci_bcm4377.c F: drivers/clk/clk-apple-nco.c F: drivers/cpufreq/apple-soc-cpufreq.c F: drivers/dma/apple-admac.c +F: drivers/gpio/gpio-macsmc.c F: drivers/pmdomain/apple/ F: drivers/i2c/busses/i2c-pasemi-core.c F: drivers/i2c/busses/i2c-pasemi-platform.c @@ -2362,10 +2366,12 @@ F: drivers/input/touchscreen/apple_z2.c F: drivers/iommu/apple-dart.c F: drivers/iommu/io-pgtable-dart.c F: drivers/irqchip/irq-apple-aic.c +F: drivers/mfd/macsmc.c F: drivers/nvme/host/apple.c F: drivers/nvmem/apple-efuses.c F: drivers/nvmem/apple-spmi-nvmem.c F: drivers/pinctrl/pinctrl-apple-gpio.c +F: drivers/power/reset/macsmc-reboot.c F: drivers/pwm/pwm-apple.c F: drivers/soc/apple/* F: drivers/spi/spi-apple.c @@ -2374,6 +2380,7 @@ F: drivers/video/backlight/apple_dwi_bl.c F: drivers/watchdog/apple_wdt.c F: include/dt-bindings/interrupt-controller/apple-aic.h F: include/dt-bindings/pinctrl/apple.h +F: include/linux/mfd/macsmc.h F: include/linux/soc/apple/* F: include/uapi/drm/asahi_drm.h diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 12bdf6e965f19c..6e3c3f0e3dcfeb 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1473,6 +1473,16 @@ config GPIO_LP87565 This driver can also be built as a module. If so, the module will be called gpio-lp87565. +config GPIO_MACSMC + tristate "Apple Mac SMC GPIO" + depends on MFD_MACSMC + help + Support for GPIOs controlled by the SMC microcontroller on Apple Mac + systems. + + This driver can also be built as a module. If so, the module will be + called gpio-macsmc. + config GPIO_MADERA tristate "Cirrus Logic Madera class codecs" depends on PINCTRL_MADERA diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 88dedd298256c1..3e5bc90ba59e56 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -99,6 +99,7 @@ obj-$(CONFIG_GPIO_LP873X) += gpio-lp873x.o obj-$(CONFIG_GPIO_LP87565) += gpio-lp87565.o obj-$(CONFIG_GPIO_LPC18XX) += gpio-lpc18xx.o obj-$(CONFIG_GPIO_LPC32XX) += gpio-lpc32xx.o +obj-$(CONFIG_GPIO_MACSMC) += gpio-macsmc.o obj-$(CONFIG_GPIO_MADERA) += gpio-madera.o obj-$(CONFIG_GPIO_MAX3191X) += gpio-max3191x.o obj-$(CONFIG_GPIO_MAX7300) += gpio-max7300.o diff --git a/drivers/gpio/gpio-macsmc.c b/drivers/gpio/gpio-macsmc.c new file mode 100644 index 00000000000000..7570d9e89adfff --- /dev/null +++ b/drivers/gpio/gpio-macsmc.c @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SMC GPIO driver + * Copyright The Asahi Linux Contributors + * + * This driver implements basic SMC PMU GPIO support that can read inputs + * and write outputs. Mode changes and IRQ config are not yet implemented. + */ + +#include <linux/bitmap.h> +#include <linux/device.h> +#include <linux/gpio/driver.h> +#include <linux/mfd/core.h> +#include <linux/mfd/macsmc.h> + +#define MAX_GPIO 64 + +/* + * Commands 0-6 are, presumably, the intended API. + * Command 0xff lets you get/set the pin configuration in detail directly, + * but the bit meanings seem not to be stable between devices/PMU hardware + * versions. + * + * We're going to try to make do with the low commands for now. + * We don't implement pin mode changes at this time. + */ + +#define CMD_ACTION (0 << 24) +#define CMD_OUTPUT (1 << 24) +#define CMD_INPUT (2 << 24) +#define CMD_PINMODE (3 << 24) +#define CMD_IRQ_ENABLE (4 << 24) +#define CMD_IRQ_ACK (5 << 24) +#define CMD_IRQ_MODE (6 << 24) +#define CMD_CONFIG (0xff << 24) + +#define MODE_INPUT 0 +#define MODE_OUTPUT 1 +#define MODE_VALUE_0 0 +#define MODE_VALUE_1 2 + +#define IRQ_MODE_HIGH 0 +#define IRQ_MODE_LOW 1 +#define IRQ_MODE_RISING 2 +#define IRQ_MODE_FALLING 3 +#define IRQ_MODE_BOTH 4 + +#define CONFIG_MASK GENMASK(23, 16) +#define CONFIG_VAL GENMASK(7, 0) + +#define CONFIG_OUTMODE GENMASK(7, 6) +#define CONFIG_IRQMODE GENMASK(5, 3) +#define CONFIG_PULLDOWN BIT(2) +#define CONFIG_PULLUP BIT(1) +#define CONFIG_OUTVAL BIT(0) + +/* + * Output modes seem to differ depending on the PMU in use... ? + * j274 / M1 (Sera PMU): + * 0 = input + * 1 = output + * 2 = open drain + * 3 = disable + * j314 / M1Pro (Maverick PMU): + * 0 = input + * 1 = open drain + * 2 = output + * 3 = ? + */ + +struct macsmc_gpio { + struct device *dev; + struct apple_smc *smc; + struct gpio_chip gc; + + int first_index; +}; + +static int macsmc_gpio_nr(smc_key key) +{ + int low = hex_to_bin(key & 0xff); + int high = hex_to_bin((key >> 8) & 0xff); + + if (low < 0 || high < 0) + return -1; + + return low | (high << 4); +} + +static int macsmc_gpio_key(unsigned int offset) +{ + return _SMC_KEY("gP\0\0") | hex_asc_hi(offset) << 8 | hex_asc_lo(offset); +} + +static int macsmc_gpio_find_first_gpio_index(struct macsmc_gpio *smcgp) +{ + struct apple_smc *smc = smcgp->smc; + smc_key key = macsmc_gpio_key(0); + smc_key first_key, last_key; + int start, count, ret; + + /* Return early if the key is out of bounds */ + ret = apple_smc_get_key_by_index(smc, 0, &first_key); + if (ret) + return ret; + if (key <= first_key) + return -ENODEV; + + ret = apple_smc_get_key_by_index(smc, smc->key_count - 1, &last_key); + if (ret) + return ret; + if (key > last_key) + return -ENODEV; + + /* Binary search to find index of first SMC key bigger or equal to key */ + start = 0; + count = smc->key_count; + while (count > 1) { + smc_key pkey; + int pivot = start + ((count - 1) >> 1); + + ret = apple_smc_get_key_by_index(smc, pivot, &pkey); + if (ret < 0) + return ret; + + if (pkey == key) + return pivot; + + pivot++; + + if (pkey < key) { + count -= pivot - start; + start = pivot; + } else { + count = pivot - start; + } + } + + return start; +} + +static int macsmc_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct macsmc_gpio *smcgp = gpiochip_get_data(gc); + smc_key key = macsmc_gpio_key(offset); + u32 val; + int ret; + + /* First try reading the explicit pin mode register */ + ret = apple_smc_rw_u32(smcgp->smc, key, CMD_PINMODE, &val); + if (!ret) + return (val & MODE_OUTPUT) ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; + + /* + * Less common IRQ configs cause CMD_PINMODE to fail, and so does open drain mode. + * Fall back to reading IRQ mode, which will only succeed for inputs. + */ + ret = apple_smc_rw_u32(smcgp->smc, key, CMD_IRQ_MODE, &val); + return ret ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; +} + +static int macsmc_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct macsmc_gpio *smcgp = gpiochip_get_data(gc); + smc_key key = macsmc_gpio_key(offset); + u32 cmd, val; + int ret; + + ret = macsmc_gpio_get_direction(gc, offset); + if (ret < 0) + return ret; + + if (ret == GPIO_LINE_DIRECTION_OUT) + cmd = CMD_OUTPUT; + else + cmd = CMD_INPUT; + + ret = apple_smc_rw_u32(smcgp->smc, key, cmd, &val); + if (ret < 0) + return ret; + + return val ? 1 : 0; +} + +static int macsmc_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct macsmc_gpio *smcgp = gpiochip_get_data(gc); + smc_key key = macsmc_gpio_key(offset); + int ret; + + value |= CMD_OUTPUT; + ret = apple_smc_write_u32(smcgp->smc, key, CMD_OUTPUT | value); + if (ret < 0) + dev_err(smcgp->dev, "GPIO set failed %p4ch = 0x%x\n", + &key, value); + + return ret; +} + +static int macsmc_gpio_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, unsigned int ngpios) +{ + struct macsmc_gpio *smcgp = gpiochip_get_data(gc); + int count; + int i; + + count = min(smcgp->smc->key_count, MAX_GPIO); + + bitmap_zero(valid_mask, ngpios); + + for (i = 0; i < count; i++) { + int ret, gpio_nr; + smc_key key; + + ret = apple_smc_get_key_by_index(smcgp->smc, smcgp->first_index + i, &key); + if (ret < 0) + return ret; + + if (key > SMC_KEY(gPff)) + break; + + gpio_nr = macsmc_gpio_nr(key); + if (gpio_nr < 0 || gpio_nr > MAX_GPIO) { + dev_err(smcgp->dev, "Bad GPIO key %p4ch\n", &key); + continue; + } + + set_bit(gpio_nr, valid_mask); + } + + return 0; +} + +static int macsmc_gpio_probe(struct platform_device *pdev) +{ + struct macsmc_gpio *smcgp; + struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); + smc_key key; + int ret; + + smcgp = devm_kzalloc(&pdev->dev, sizeof(*smcgp), GFP_KERNEL); + if (!smcgp) + return -ENOMEM; + + smcgp->dev = &pdev->dev; + smcgp->smc = smc; + + smcgp->first_index = macsmc_gpio_find_first_gpio_index(smcgp); + if (smcgp->first_index < 0) + return smcgp->first_index; + + ret = apple_smc_get_key_by_index(smc, smcgp->first_index, &key); + if (ret < 0) + return ret; + + if (key > macsmc_gpio_key(MAX_GPIO - 1)) + return -ENODEV; + + dev_info(smcgp->dev, "First GPIO key: %p4ch\n", &key); + + smcgp->gc.label = "macsmc-pmu-gpio"; + smcgp->gc.owner = THIS_MODULE; + smcgp->gc.get = macsmc_gpio_get; + smcgp->gc.set_rv = macsmc_gpio_set; + smcgp->gc.get_direction = macsmc_gpio_get_direction; + smcgp->gc.init_valid_mask = macsmc_gpio_init_valid_mask; + smcgp->gc.can_sleep = true; + smcgp->gc.ngpio = MAX_GPIO; + smcgp->gc.base = -1; + smcgp->gc.parent = &pdev->dev; + + return devm_gpiochip_add_data(&pdev->dev, &smcgp->gc, smcgp); +} + +static const struct of_device_id macsmc_gpio_of_table[] = { + { .compatible = "apple,smc-gpio", }, + {} +}; +MODULE_DEVICE_TABLE(of, macsmc_gpio_of_table); + +static struct platform_driver macsmc_gpio_driver = { + .driver = { + .name = "macsmc-gpio", + .of_match_table = macsmc_gpio_of_table, + }, + .probe = macsmc_gpio_probe, +}; +module_platform_driver(macsmc_gpio_driver); + +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple SMC GPIO driver"); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 6fb3768e3d71cb..c6cc423608877b 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -285,6 +285,24 @@ config MFD_CS42L43_SDW Select this to support the Cirrus Logic CS42L43 PC CODEC with headphone and class D speaker drivers over SoundWire. +config MFD_MACSMC + tristate "Apple Silicon System Management Controller (SMC)" + depends on ARCH_APPLE || COMPILE_TEST + depends on OF + depends on APPLE_RTKIT + select MFD_CORE + help + The System Management Controller (SMC) on Apple Silicon machines is a + piece of hardware that exposes various functionalities such as + temperature sensors, voltage/power meters, shutdown/reboot handling, + GPIOs and more. + + Communication happens via a shared mailbox using the RTKit protocol + which is also used for other co-processors. The SMC protocol then + allows reading and writing many different keys which implement the + various features. The MFD core device handles this protocol and + exposes it to the sub-devices. + config MFD_MADERA tristate "Cirrus Logic Madera codecs" select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 79495f9f3457b8..f7bdedd5a66d16 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_MFD_CS42L43_SDW) += cs42l43-sdw.o obj-$(CONFIG_MFD_ENE_KB3930) += ene-kb3930.o obj-$(CONFIG_MFD_EXYNOS_LPASS) += exynos-lpass.o obj-$(CONFIG_MFD_GATEWORKS_GSC) += gateworks-gsc.o +obj-$(CONFIG_MFD_MACSMC) += macsmc.o obj-$(CONFIG_MFD_TI_LP873X) += lp873x.o obj-$(CONFIG_MFD_TI_LP87565) += lp87565.o diff --git a/drivers/mfd/macsmc.c b/drivers/mfd/macsmc.c new file mode 100644 index 00000000000000..870c8b2028a8fc --- /dev/null +++ b/drivers/mfd/macsmc.c @@ -0,0 +1,498 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SMC (System Management Controller) MFD driver + * + * Copyright The Asahi Linux Contributors + */ + +#include <linux/bitfield.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/math.h> +#include <linux/mfd/core.h> +#include <linux/mfd/macsmc.h> +#include <linux/notifier.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/overflow.h> +#include <linux/platform_device.h> +#include <linux/soc/apple/rtkit.h> +#include <linux/unaligned.h> + +#define SMC_ENDPOINT 0x20 + +/* We don't actually know the true size here but this seem reasonable */ +#define SMC_SHMEM_SIZE 0x1000 +#define SMC_MAX_SIZE 255 + +#define SMC_MSG_READ_KEY 0x10 +#define SMC_MSG_WRITE_KEY 0x11 +#define SMC_MSG_GET_KEY_BY_INDEX 0x12 +#define SMC_MSG_GET_KEY_INFO 0x13 +#define SMC_MSG_INITIALIZE 0x17 +#define SMC_MSG_NOTIFICATION 0x18 +#define SMC_MSG_RW_KEY 0x20 + +#define SMC_DATA GENMASK_ULL(63, 32) +#define SMC_WSIZE GENMASK_ULL(31, 24) +#define SMC_SIZE GENMASK_ULL(23, 16) +#define SMC_ID GENMASK_ULL(15, 12) +#define SMC_MSG GENMASK_ULL(7, 0) +#define SMC_RESULT SMC_MSG + +#define SMC_TIMEOUT_MS 500 + +static const struct mfd_cell apple_smc_devs[] = { + MFD_CELL_OF("macsmc-gpio", NULL, NULL, 0, 0, "apple,smc-gpio"), + MFD_CELL_OF("macsmc-reboot", NULL, NULL, 0, 0, "apple,smc-reboot"), +}; + +static int apple_smc_cmd_locked(struct apple_smc *smc, u64 cmd, u64 arg, + u64 size, u64 wsize, u32 *ret_data) +{ + u8 result; + int ret; + u64 msg; + + lockdep_assert_held(&smc->mutex); + + if (smc->boot_stage != APPLE_SMC_INITIALIZED) + return -EIO; + if (smc->atomic_mode) + return -EIO; + + reinit_completion(&smc->cmd_done); + + smc->msg_id = (smc->msg_id + 1) & 0xf; + msg = (FIELD_PREP(SMC_MSG, cmd) | + FIELD_PREP(SMC_SIZE, size) | + FIELD_PREP(SMC_WSIZE, wsize) | + FIELD_PREP(SMC_ID, smc->msg_id) | + FIELD_PREP(SMC_DATA, arg)); + + ret = apple_rtkit_send_message(smc->rtk, SMC_ENDPOINT, msg, NULL, false); + if (ret) { + dev_err(smc->dev, "Failed to send command\n"); + return ret; + } + + if (wait_for_completion_timeout(&smc->cmd_done, msecs_to_jiffies(SMC_TIMEOUT_MS)) <= 0) { + dev_err(smc->dev, "Command timed out (%llx)", msg); + return -ETIMEDOUT; + } + + if (FIELD_GET(SMC_ID, smc->cmd_ret) != smc->msg_id) { + dev_err(smc->dev, "Command sequence mismatch (expected %d, got %d)\n", + smc->msg_id, (unsigned int)FIELD_GET(SMC_ID, smc->cmd_ret)); + return -EIO; + } + + result = FIELD_GET(SMC_RESULT, smc->cmd_ret); + if (result) + return -EIO; + + if (ret_data) + *ret_data = FIELD_GET(SMC_DATA, smc->cmd_ret); + + return FIELD_GET(SMC_SIZE, smc->cmd_ret); +} + +static int apple_smc_cmd(struct apple_smc *smc, u64 cmd, u64 arg, + u64 size, u64 wsize, u32 *ret_data) +{ + guard(mutex)(&smc->mutex); + + return apple_smc_cmd_locked(smc, cmd, arg, size, wsize, ret_data); +} + +static int apple_smc_rw_locked(struct apple_smc *smc, smc_key key, + const void *wbuf, size_t wsize, + void *rbuf, size_t rsize) +{ + u64 smc_size, smc_wsize; + u32 rdata; + int ret; + u64 cmd; + + lockdep_assert_held(&smc->mutex); + + if (rsize > SMC_MAX_SIZE) + return -EINVAL; + if (wsize > SMC_MAX_SIZE) + return -EINVAL; + + if (rsize && wsize) { + cmd = SMC_MSG_RW_KEY; + memcpy_toio(smc->shmem.iomem, wbuf, wsize); + smc_size = rsize; + smc_wsize = wsize; + } else if (wsize && !rsize) { + cmd = SMC_MSG_WRITE_KEY; + memcpy_toio(smc->shmem.iomem, wbuf, wsize); + /* + * Setting size to the length we want to write and wsize to 0 + * looks silly but that's how the SMC protocol works ¯\_(ツ)_/¯ + */ + smc_size = wsize; + smc_wsize = 0; + } else if (!wsize && rsize) { + cmd = SMC_MSG_READ_KEY; + smc_size = rsize; + smc_wsize = 0; + } else { + return -EINVAL; + } + + ret = apple_smc_cmd_locked(smc, cmd, key, smc_size, smc_wsize, &rdata); + if (ret < 0) + return ret; + + if (rsize) { + /* + * Small data <= 4 bytes is returned as part of the reply + * message which is sent over the mailbox FIFO. Everything + * bigger has to be copied from SRAM which is mapped as + * Device memory. + */ + if (rsize <= 4) + memcpy(rbuf, &rdata, rsize); + else + memcpy_fromio(rbuf, smc->shmem.iomem, rsize); + } + + return ret; +} + +int apple_smc_read(struct apple_smc *smc, smc_key key, void *buf, size_t size) +{ + guard(mutex)(&smc->mutex); + + return apple_smc_rw_locked(smc, key, NULL, 0, buf, size); +} +EXPORT_SYMBOL(apple_smc_read); + +int apple_smc_write(struct apple_smc *smc, smc_key key, void *buf, size_t size) +{ + guard(mutex)(&smc->mutex); + + return apple_smc_rw_locked(smc, key, buf, size, NULL, 0); +} +EXPORT_SYMBOL(apple_smc_write); + +int apple_smc_rw(struct apple_smc *smc, smc_key key, void *wbuf, size_t wsize, + void *rbuf, size_t rsize) +{ + guard(mutex)(&smc->mutex); + + return apple_smc_rw_locked(smc, key, wbuf, wsize, rbuf, rsize); +} +EXPORT_SYMBOL(apple_smc_rw); + +int apple_smc_get_key_by_index(struct apple_smc *smc, int index, smc_key *key) +{ + int ret; + + ret = apple_smc_cmd(smc, SMC_MSG_GET_KEY_BY_INDEX, index, 0, 0, key); + + *key = swab32(*key); + return ret; +} +EXPORT_SYMBOL(apple_smc_get_key_by_index); + +int apple_smc_get_key_info(struct apple_smc *smc, smc_key key, struct apple_smc_key_info *info) +{ + u8 key_info[6]; + int ret; + + ret = apple_smc_cmd(smc, SMC_MSG_GET_KEY_INFO, key, 0, 0, NULL); + if (ret >= 0 && info) { + memcpy_fromio(key_info, smc->shmem.iomem, sizeof(key_info)); + info->size = key_info[0]; + info->type_code = get_unaligned_be32(&key_info[1]); + info->flags = key_info[5]; + } + return ret; +} +EXPORT_SYMBOL(apple_smc_get_key_info); + +int apple_smc_enter_atomic(struct apple_smc *smc) +{ + guard(mutex)(&smc->mutex); + + /* + * Disable notifications since this is called before shutdown and no + * notification handler will be able to handle the notification + * using atomic operations only. Also ignore any failure here + * because we're about to shut down or reboot anyway. + * We can't use apple_smc_write_flag here since that would try to lock + * smc->mutex again. + */ + const u8 flag = 0; + + apple_smc_rw_locked(smc, SMC_KEY(NTAP), &flag, sizeof(flag), NULL, 0); + + smc->atomic_mode = true; + + return 0; +} +EXPORT_SYMBOL(apple_smc_enter_atomic); + +int apple_smc_write_atomic(struct apple_smc *smc, smc_key key, void *buf, size_t size) +{ + guard(spinlock_irqsave)(&smc->lock); + u8 result; + int ret; + u64 msg; + + if (size > SMC_MAX_SIZE || size == 0) + return -EINVAL; + + if (smc->boot_stage != APPLE_SMC_INITIALIZED) + return -EIO; + if (!smc->atomic_mode) + return -EIO; + + memcpy_toio(smc->shmem.iomem, buf, size); + smc->msg_id = (smc->msg_id + 1) & 0xf; + msg = (FIELD_PREP(SMC_MSG, SMC_MSG_WRITE_KEY) | + FIELD_PREP(SMC_SIZE, size) | + FIELD_PREP(SMC_ID, smc->msg_id) | + FIELD_PREP(SMC_DATA, key)); + smc->atomic_pending = true; + + ret = apple_rtkit_send_message(smc->rtk, SMC_ENDPOINT, msg, NULL, true); + if (ret < 0) { + dev_err(smc->dev, "Failed to send command (%d)\n", ret); + return ret; + } + + while (smc->atomic_pending) { + ret = apple_rtkit_poll(smc->rtk); + if (ret < 0) { + dev_err(smc->dev, "RTKit poll failed (%llx)", msg); + return ret; + } + udelay(100); + } + + if (FIELD_GET(SMC_ID, smc->cmd_ret) != smc->msg_id) { + dev_err(smc->dev, "Command sequence mismatch (expected %d, got %d)\n", + smc->msg_id, (unsigned int)FIELD_GET(SMC_ID, smc->cmd_ret)); + return -EIO; + } + + result = FIELD_GET(SMC_RESULT, smc->cmd_ret); + if (result) + return -EIO; + + return FIELD_GET(SMC_SIZE, smc->cmd_ret); +} +EXPORT_SYMBOL(apple_smc_write_atomic); + +static void apple_smc_rtkit_crashed(void *cookie, const void *bfr, size_t bfr_len) +{ + struct apple_smc *smc = cookie; + + smc->boot_stage = APPLE_SMC_ERROR_CRASHED; + dev_err(smc->dev, "SMC crashed! Your system will reboot in a few seconds...\n"); +} + +static int apple_smc_rtkit_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) +{ + struct apple_smc *smc = cookie; + size_t bfr_end; + + if (!bfr->iova) { + dev_err(smc->dev, "RTKit wants a RAM buffer\n"); + return -EIO; + } + + if (check_add_overflow(bfr->iova, bfr->size - 1, &bfr_end)) + return -EFAULT; + + if (bfr->iova < smc->sram->start || bfr->iova > smc->sram->end || + bfr_end > smc->sram->end) { + dev_err(smc->dev, "RTKit buffer request outside SRAM region: [0x%llx, 0x%llx]\n", + (unsigned long long)bfr->iova, + (unsigned long long)bfr_end); + return -EFAULT; + } + + bfr->iomem = smc->sram_base + (bfr->iova - smc->sram->start); + bfr->is_mapped = true; + + return 0; +} + +static bool apple_smc_rtkit_recv_early(void *cookie, u8 endpoint, u64 message) +{ + struct apple_smc *smc = cookie; + + if (endpoint != SMC_ENDPOINT) { + dev_warn(smc->dev, "Received message for unknown endpoint 0x%x\n", endpoint); + return false; + } + + if (smc->boot_stage == APPLE_SMC_BOOTING) { + int ret; + + smc->shmem.iova = message; + smc->shmem.size = SMC_SHMEM_SIZE; + ret = apple_smc_rtkit_shmem_setup(smc, &smc->shmem); + if (ret < 0) { + smc->boot_stage = APPLE_SMC_ERROR_NO_SHMEM; + dev_err(smc->dev, "Failed to initialize shared memory (%d)\n", ret); + } else { + smc->boot_stage = APPLE_SMC_INITIALIZED; + } + complete(&smc->init_done); + } else if (FIELD_GET(SMC_MSG, message) == SMC_MSG_NOTIFICATION) { + /* Handle these in the RTKit worker thread */ + return false; + } else { + smc->cmd_ret = message; + if (smc->atomic_pending) + smc->atomic_pending = false; + else + complete(&smc->cmd_done); + } + + return true; +} + +static void apple_smc_rtkit_recv(void *cookie, u8 endpoint, u64 message) +{ + struct apple_smc *smc = cookie; + + if (endpoint != SMC_ENDPOINT) { + dev_warn(smc->dev, "Received message for unknown endpoint 0x%x\n", endpoint); + return; + } + + if (FIELD_GET(SMC_MSG, message) != SMC_MSG_NOTIFICATION) { + dev_warn(smc->dev, "Received unknown message from worker: 0x%llx\n", message); + return; + } + + blocking_notifier_call_chain(&smc->event_handlers, FIELD_GET(SMC_DATA, message), NULL); +} + +static const struct apple_rtkit_ops apple_smc_rtkit_ops = { + .crashed = apple_smc_rtkit_crashed, + .recv_message = apple_smc_rtkit_recv, + .recv_message_early = apple_smc_rtkit_recv_early, + .shmem_setup = apple_smc_rtkit_shmem_setup, +}; + +static void apple_smc_rtkit_shutdown(void *data) +{ + struct apple_smc *smc = data; + + /* Shut down SMC firmware, if it's not completely wedged */ + if (apple_rtkit_is_running(smc->rtk)) + apple_rtkit_quiesce(smc->rtk); +} + +static void apple_smc_disable_notifications(void *data) +{ + struct apple_smc *smc = data; + + apple_smc_write_flag(smc, SMC_KEY(NTAP), false); +} + +static int apple_smc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct apple_smc *smc; + u32 count; + int ret; + + smc = devm_kzalloc(dev, sizeof(*smc), GFP_KERNEL); + if (!smc) + return -ENOMEM; + + smc->dev = &pdev->dev; + smc->sram_base = devm_platform_get_and_ioremap_resource(pdev, 1, &smc->sram); + if (IS_ERR(smc->sram_base)) + return dev_err_probe(dev, PTR_ERR(smc->sram_base), "Failed to map SRAM region"); + + smc->rtk = devm_apple_rtkit_init(dev, smc, NULL, 0, &apple_smc_rtkit_ops); + if (IS_ERR(smc->rtk)) + return dev_err_probe(dev, PTR_ERR(smc->rtk), "Failed to initialize RTKit"); + + smc->boot_stage = APPLE_SMC_BOOTING; + ret = apple_rtkit_wake(smc->rtk); + if (ret) + return dev_err_probe(dev, ret, "Failed to wake up SMC"); + + ret = devm_add_action_or_reset(dev, apple_smc_rtkit_shutdown, smc); + if (ret) + return dev_err_probe(dev, ret, "Failed to register rtkit shutdown action"); + + ret = apple_rtkit_start_ep(smc->rtk, SMC_ENDPOINT); + if (ret) + return dev_err_probe(dev, ret, "Failed to start SMC endpoint"); + + init_completion(&smc->init_done); + init_completion(&smc->cmd_done); + + ret = apple_rtkit_send_message(smc->rtk, SMC_ENDPOINT, + FIELD_PREP(SMC_MSG, SMC_MSG_INITIALIZE), NULL, false); + if (ret) + return dev_err_probe(dev, ret, "Failed to send init message"); + + if (wait_for_completion_timeout(&smc->init_done, msecs_to_jiffies(SMC_TIMEOUT_MS)) == 0) { + dev_err(dev, "Timed out initializing SMC"); + return -ETIMEDOUT; + } + + if (smc->boot_stage != APPLE_SMC_INITIALIZED) { + dev_err(dev, "SMC failed to boot successfully, boot stage=%d\n", smc->boot_stage); + return -EIO; + } + + dev_set_drvdata(&pdev->dev, smc); + BLOCKING_INIT_NOTIFIER_HEAD(&smc->event_handlers); + + ret = apple_smc_read_u32(smc, SMC_KEY(#KEY), &count); + if (ret) + return dev_err_probe(smc->dev, ret, "Failed to get key count"); + smc->key_count = be32_to_cpu(count); + + /* Enable notifications */ + apple_smc_write_flag(smc, SMC_KEY(NTAP), true); + ret = devm_add_action_or_reset(dev, apple_smc_disable_notifications, smc); + if (ret) + return dev_err_probe(dev, ret, "Failed to register notification disable action"); + + 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"); + + + return 0; +} + +static const struct of_device_id apple_smc_of_match[] = { + { .compatible = "apple,smc" }, + {}, +}; +MODULE_DEVICE_TABLE(of, apple_smc_of_match); + +static struct platform_driver apple_smc_driver = { + .driver = { + .name = "macsmc", + .of_match_table = apple_smc_of_match, + }, + .probe = apple_smc_probe, +}; +module_platform_driver(apple_smc_driver); + +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); +MODULE_AUTHOR("Sven Peter <sven@kernel.org>"); +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple SMC driver"); diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index e71f0af4e378c1..733d812621599d 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -128,6 +128,15 @@ config POWER_RESET_LINKSTATION Say Y here if you have a Buffalo LinkStation LS421D/E. +config POWER_RESET_MACSMC + tristate "Apple SMC reset/power-off driver" + depends on MFD_MACSMC + help + This driver supports reset and power-off on Apple Mac machines + that implement this functionality via the SMC. + + Say Y here if you have an Apple Silicon Mac. + config POWER_RESET_MSM bool "Qualcomm MSM power-off driver" depends on ARCH_QCOM diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile index 1b9b63a1a8731b..b7c2b5940be997 100644 --- a/drivers/power/reset/Makefile +++ b/drivers/power/reset/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o obj-$(CONFIG_POWER_RESET_LINKSTATION) += linkstation-poweroff.o +obj-$(CONFIG_POWER_RESET_MACSMC) += macsmc-reboot.o obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o obj-$(CONFIG_POWER_RESET_MT6323) += mt6323-poweroff.o obj-$(CONFIG_POWER_RESET_QCOM_PON) += qcom-pon.o diff --git a/drivers/power/reset/macsmc-reboot.c b/drivers/power/reset/macsmc-reboot.c new file mode 100644 index 00000000000000..e9702acdd366b0 --- /dev/null +++ b/drivers/power/reset/macsmc-reboot.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SMC Reboot/Poweroff Handler + * Copyright The Asahi Linux Contributors + */ + +#include <linux/delay.h> +#include <linux/mfd/core.h> +#include <linux/mfd/macsmc.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/nvmem-consumer.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <linux/slab.h> + +struct macsmc_reboot_nvmem { + struct nvmem_cell *shutdown_flag; + struct nvmem_cell *boot_stage; + struct nvmem_cell *boot_error_count; + struct nvmem_cell *panic_count; +}; + +static const char * const nvmem_names[] = { + "shutdown_flag", + "boot_stage", + "boot_error_count", + "panic_count", +}; + +enum boot_stage { + BOOT_STAGE_SHUTDOWN = 0x00, /* Clean shutdown */ + BOOT_STAGE_IBOOT_DONE = 0x2f, /* Last stage of bootloader */ + BOOT_STAGE_KERNEL_STARTED = 0x30, /* Normal OS booting */ +}; + +struct macsmc_reboot { + struct device *dev; + struct apple_smc *smc; + struct notifier_block reboot_notify; + + union { + struct macsmc_reboot_nvmem nvm; + struct nvmem_cell *nvm_cells[ARRAY_SIZE(nvmem_names)]; + }; +}; + +/* Helpers to read/write a u8 given a struct nvmem_cell */ +static int nvmem_cell_get_u8(struct nvmem_cell *cell) +{ + size_t len; + void *bfr; + u8 val; + + bfr = nvmem_cell_read(cell, &len); + if (IS_ERR(bfr)) + return PTR_ERR(bfr); + + if (len < 1) { + kfree(bfr); + return -EINVAL; + } + + val = *(u8 *)bfr; + kfree(bfr); + return val; +} + +static int nvmem_cell_set_u8(struct nvmem_cell *cell, u8 val) +{ + return nvmem_cell_write(cell, &val, sizeof(val)); +} + +/* + * SMC 'MBSE' key actions: + * + * 'offw' - shutdown warning + * 'slpw' - sleep warning + * 'rest' - restart warning + * 'off1' - shutdown (needs PMU bit set to stay on) + * 'susp' - suspend + * 'phra' - restart ("PE Halt Restart Action"?) + * 'panb' - panic beginning + * 'pane' - panic end + */ + +static int macsmc_prepare_atomic(struct sys_off_data *data) +{ + struct macsmc_reboot *reboot = data->cb_data; + + dev_info(reboot->dev, "Preparing SMC for atomic mode\n"); + + apple_smc_enter_atomic(reboot->smc); + return NOTIFY_OK; +} + +static int macsmc_power_off(struct sys_off_data *data) +{ + struct macsmc_reboot *reboot = data->cb_data; + + dev_info(reboot->dev, "Issuing power off (off1)\n"); + + if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(off1)) < 0) { + dev_err(reboot->dev, "Failed to issue MBSE = off1 (power_off)\n"); + } else { + mdelay(100); + WARN_ONCE(1, "Unable to power off system\n"); + } + + return NOTIFY_OK; +} + +static int macsmc_restart(struct sys_off_data *data) +{ + struct macsmc_reboot *reboot = data->cb_data; + + dev_info(reboot->dev, "Issuing restart (phra)\n"); + + if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(phra)) < 0) { + dev_err(reboot->dev, "Failed to issue MBSE = phra (restart)\n"); + } else { + mdelay(100); + WARN_ONCE(1, "Unable to restart system\n"); + } + + return NOTIFY_OK; +} + +static int macsmc_reboot_notify(struct notifier_block *this, unsigned long action, void *data) +{ + struct macsmc_reboot *reboot = container_of(this, struct macsmc_reboot, reboot_notify); + u8 shutdown_flag; + u32 val; + + switch (action) { + case SYS_RESTART: + val = SMC_KEY(rest); + shutdown_flag = 0; + break; + case SYS_POWER_OFF: + val = SMC_KEY(offw); + shutdown_flag = 1; + break; + default: + return NOTIFY_DONE; + } + + dev_info(reboot->dev, "Preparing for reboot (%p4ch)\n", &val); + + /* On the Mac Mini, this will turn off the LED for power off */ + if (apple_smc_write_u32(reboot->smc, SMC_KEY(MBSE), val) < 0) + dev_err(reboot->dev, "Failed to issue MBSE = %p4ch (reboot_prepare)\n", &val); + + /* Set the boot_stage to 0, which means we're doing a clean shutdown/reboot. */ + if (reboot->nvm.boot_stage && + nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_SHUTDOWN) < 0) + dev_err(reboot->dev, "Failed to write boot_stage\n"); + + /* + * Set the PMU flag to actually reboot into the off state. + * Without this, the device will just reboot. We make it optional in case it is no longer + * necessary on newer hardware. + */ + if (reboot->nvm.shutdown_flag && + nvmem_cell_set_u8(reboot->nvm.shutdown_flag, shutdown_flag) < 0) + dev_err(reboot->dev, "Failed to write shutdown_flag\n"); + + return NOTIFY_OK; +} + +static void macsmc_power_init_error_counts(struct macsmc_reboot *reboot) +{ + int boot_error_count, panic_count; + + if (!reboot->nvm.boot_error_count || !reboot->nvm.panic_count) + return; + + boot_error_count = nvmem_cell_get_u8(reboot->nvm.boot_error_count); + if (boot_error_count < 0) { + dev_err(reboot->dev, "Failed to read boot_error_count (%d)\n", boot_error_count); + return; + } + + panic_count = nvmem_cell_get_u8(reboot->nvm.panic_count); + if (panic_count < 0) { + dev_err(reboot->dev, "Failed to read panic_count (%d)\n", panic_count); + return; + } + + if (!boot_error_count && !panic_count) + return; + + dev_warn(reboot->dev, "PMU logged %d boot error(s) and %d panic(s)\n", + boot_error_count, panic_count); + + if (nvmem_cell_set_u8(reboot->nvm.panic_count, 0) < 0) + dev_err(reboot->dev, "Failed to reset panic_count\n"); + if (nvmem_cell_set_u8(reboot->nvm.boot_error_count, 0) < 0) + dev_err(reboot->dev, "Failed to reset boot_error_count\n"); +} + +static int macsmc_reboot_probe(struct platform_device *pdev) +{ + struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); + struct macsmc_reboot *reboot; + int ret, i; + + reboot = devm_kzalloc(&pdev->dev, sizeof(*reboot), GFP_KERNEL); + if (!reboot) + return -ENOMEM; + + reboot->dev = &pdev->dev; + reboot->smc = smc; + + platform_set_drvdata(pdev, reboot); + + for (i = 0; i < ARRAY_SIZE(nvmem_names); i++) { + struct nvmem_cell *cell; + + cell = devm_nvmem_cell_get(&pdev->dev, + nvmem_names[i]); + if (IS_ERR(cell)) { + if (PTR_ERR(cell) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_warn(&pdev->dev, "Missing NVMEM cell %s (%ld)\n", + nvmem_names[i], PTR_ERR(cell)); + /* Non fatal, we'll deal with it */ + cell = NULL; + } + reboot->nvm_cells[i] = cell; + } + + /* 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) + dev_err(reboot->dev, "Failed to write boot_stage\n"); + + /* Display and clear the error counts */ + macsmc_power_init_error_counts(reboot); + + reboot->reboot_notify.notifier_call = macsmc_reboot_notify; + + ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF_PREPARE, + SYS_OFF_PRIO_HIGH, macsmc_prepare_atomic, reboot); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to register power-off prepare handler\n"); + ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_HIGH, + macsmc_power_off, reboot); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to register power-off handler\n"); + + ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART_PREPARE, + SYS_OFF_PRIO_HIGH, macsmc_prepare_atomic, reboot); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to register restart prepare handler\n"); + ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART, SYS_OFF_PRIO_HIGH, + macsmc_restart, reboot); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to register restart handler\n"); + + ret = devm_register_reboot_notifier(&pdev->dev, &reboot->reboot_notify); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to register reboot notifier\n"); + + dev_info(&pdev->dev, "Handling reboot and poweroff requests via SMC\n"); + + return 0; +} + +static const struct of_device_id macsmc_reboot_of_table[] = { + { .compatible = "apple,smc-reboot", }, + {} +}; +MODULE_DEVICE_TABLE(of, macsmc_reboot_of_table); + +static struct platform_driver macsmc_reboot_driver = { + .driver = { + .name = "macsmc-reboot", + .of_match_table = macsmc_reboot_of_table, + }, + .probe = macsmc_reboot_probe, +}; +module_platform_driver(macsmc_reboot_driver); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple SMC reboot/poweroff driver"); +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c index 5fffd0f003dc2f..b8d4da147d23f7 100644 --- a/drivers/soc/apple/rtkit.c +++ b/drivers/soc/apple/rtkit.c @@ -279,8 +279,7 @@ static int apple_rtkit_common_rx_get_buffer(struct apple_rtkit *rtk, dev_dbg(rtk->dev, "RTKit: buffer request for 0x%zx bytes at %pad\n", buffer->size, &buffer->iova); - if (buffer->iova && - (!rtk->ops->shmem_setup || !rtk->ops->shmem_destroy)) { + if (buffer->iova && !rtk->ops->shmem_setup) { err = -EINVAL; goto error; } diff --git a/include/linux/mfd/macsmc.h b/include/linux/mfd/macsmc.h new file mode 100644 index 00000000000000..6b13f01a85924f --- /dev/null +++ b/include/linux/mfd/macsmc.h @@ -0,0 +1,279 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* + * Apple SMC (System Management Controller) core definitions + * + * Copyright (C) The Asahi Linux Contributors + */ + +#ifndef _LINUX_MFD_MACSMC_H +#define _LINUX_MFD_MACSMC_H + +#include <linux/soc/apple/rtkit.h> + +/** + * typedef smc_key - Alias for u32 to be used for SMC keys + * + * SMC keys are 32bit integers containing packed ASCII characters in natural + * integer order, i.e. 0xAABBCCDD, which represent the FourCC ABCD. + * The SMC driver is designed with this assumption and ensures the right + * endianness is used when these are stored to memory and sent to or received + * from the actual SMC firmware (which can be done in either shared memory or + * as 64bit mailbox message on Apple Silicon). + * Internally, SMC stores these keys in a table sorted lexicographically and + * allows resolving an index into this table to the corresponding SMC key. + * Thus, storing keys as u32 is very convenient as it allows to e.g. use + * normal comparison operators which directly map to the natural order used + * by SMC firmware. + * + * This simple type alias is introduced to allow easy recognition of SMC key + * variables and arguments. + */ +typedef u32 smc_key; + +/** + * SMC_KEY - Convert FourCC SMC keys in source code to smc_key + * + * This macro can be used to easily define FourCC SMC keys in source code + * and convert these to u32 / smc_key, e.g. SMC_KEY(NTAP) will expand to + * 0x4e544150. + * + * @s: FourCC SMC key to be converted + */ +#define SMC_KEY(s) (smc_key)(_SMC_KEY(#s)) +#define _SMC_KEY(s) (((s)[0] << 24) | ((s)[1] << 16) | ((s)[2] << 8) | (s)[3]) + +#define APPLE_SMC_READABLE BIT(7) +#define APPLE_SMC_WRITABLE BIT(6) +#define APPLE_SMC_FUNCTION BIT(4) + +/** + * struct apple_smc_key_info - Information for a SMC key as returned by SMC + * @type_code: FourCC code indicating the type for this key. + * Known types: + * ch8*: ASCII string + * flag: Boolean, 1 or 0 + * flt: 32-bit single-precision IEEE 754 float + * hex: Binary data + * ioft: 64bit Unsigned fixed-point intger (48.16) + * {si,ui}{8,16,32,64}: Signed/Unsigned 8-/16-/32-/64-bit integer + * @size: Size of the buffer associated with this key + * @flags: Bitfield encoding flags (APPLE_SMC_{READABLE,WRITABLE,FUNCTION}) + */ +struct apple_smc_key_info { + u32 type_code; + u8 size; + u8 flags; +}; + +/** + * enum apple_smc_boot_stage - SMC boot stage + * @APPLE_SMC_BOOTING: SMC is booting + * @APPLE_SMC_INITIALIZED: SMC is initialized and ready to use + * @APPLE_SMC_ERROR_NO_SHMEM: Shared memory could not be initialized during boot + * @APPLE_SMC_ERROR_CRASHED: SMC has crashed + */ +enum apple_smc_boot_stage { + APPLE_SMC_BOOTING, + APPLE_SMC_INITIALIZED, + APPLE_SMC_ERROR_NO_SHMEM, + APPLE_SMC_ERROR_CRASHED +}; + +/** + * struct apple_smc + * @dev: Underlying device struct for the physical backend device + * @key_count: Number of available SMC keys + * @first_key: First valid SMC key + * @last_key: Last valid SMC key + * @event_handlers: Notifier call chain for events received from SMC + * @rtk: Pointer to Apple RTKit instance + * @init_done: Completion for initialization + * @boot_stage: Current boot stage of SMC + * @sram: Pointer to SRAM resource + * @sram_base: SRAM base address + * @shmem: RTKit shared memory structure for SRAM + * @msg_id: Current message id for commands, will be incremented for each command + * @atomic_mode: Flag set when atomic mode is entered + * @atomic_pending: Flag indicating pending atomic command + * @cmd_done: Completion for command execution in non-atomic mode + * @cmd_ret: Return value from SMC for last command + * @mutex: Mutex for non-atomic mode + * @lock: Spinlock for atomic mode + */ +struct apple_smc { + struct device *dev; + + u32 key_count; + smc_key first_key; + smc_key last_key; + + struct blocking_notifier_head event_handlers; + + struct apple_rtkit *rtk; + + struct completion init_done; + enum apple_smc_boot_stage boot_stage; + + struct resource *sram; + void __iomem *sram_base; + struct apple_rtkit_shmem shmem; + + unsigned int msg_id; + + bool atomic_mode; + bool atomic_pending; + struct completion cmd_done; + u64 cmd_ret; + + struct mutex mutex; + spinlock_t lock; +}; + +/** + * apple_smc_read - Read size bytes from given SMC key into buf + * @smc: Pointer to apple_smc struct + * @key: smc_key to be read + * @buf: Buffer into which size bytes of data will be read from SMC + * @size: Number of bytes to be read into buf + * + * Return: Zero on success, negative errno on error + */ +int apple_smc_read(struct apple_smc *smc, smc_key key, void *buf, size_t size); + +/** + * apple_smc_write - Write size bytes into given SMC key from buf + * @smc: Pointer to apple_smc struct + * @key: smc_key data will be written to + * @buf: Buffer from which size bytes of data will be written to SMC + * @size: Number of bytes to be written + * + * Return: Zero on success, negative errno on error + */ +int apple_smc_write(struct apple_smc *smc, smc_key key, void *buf, size_t size); + +/** + * apple_smc_enter_atomic - Enter atomic mode to be able to use apple_smc_write_atomic + * @smc: Pointer to apple_smc struct + * + * This function switches the SMC backend to atomic mode which allows the + * use of apple_smc_write_atomic while disabling *all* other functions. + * This is only used for shutdown/reboot which requires writing to a SMC + * key from atomic context. + * + * Return: Zero on success, negative errno on error + */ +int apple_smc_enter_atomic(struct apple_smc *smc); + +/** + * apple_smc_write_atomic - Write size bytes into given SMC key from buf without sleeping + * @smc: Pointer to apple_smc struct + * @key: smc_key data will be written to + * @buf: Buffer from which size bytes of data will be written to SMC + * @size: Number of bytes to be written + * + * Note that this function will fail if apple_smc_enter_atomic hasn't been + * called before. + * + * Return: Zero on success, negative errno on error + */ +int apple_smc_write_atomic(struct apple_smc *smc, smc_key key, void *buf, size_t size); + +/** + * apple_smc_rw - Write and then read using the given SMC key + * @smc: Pointer to apple_smc struct + * @key: smc_key data will be written to + * @wbuf: Buffer from which size bytes of data will be written to SMC + * @wsize: Number of bytes to be written + * @rbuf: Buffer to which size bytes of data will be read from SMC + * @rsize: Number of bytes to be read + * + * Return: Zero on success, negative errno on error + */ +int apple_smc_rw(struct apple_smc *smc, smc_key key, void *wbuf, size_t wsize, + void *rbuf, size_t rsize); + +/** + * apple_smc_get_key_by_index - Given an index return the corresponding SMC key + * @smc: Pointer to apple_smc struct + * @index: Index to be resolved + * @key: Buffer for SMC key to be returned + * + * Return: Zero on success, negative errno on error + */ +int apple_smc_get_key_by_index(struct apple_smc *smc, int index, smc_key *key); + +/** + * apple_smc_get_key_info - Get key information from SMC + * @smc: Pointer to apple_smc struct + * @key: Key to acquire information for + * @info: Pointer to struct apple_smc_key_info which will be filled + * + * Return: Zero on success, negative errno on error + */ +int apple_smc_get_key_info(struct apple_smc *smc, smc_key key, struct apple_smc_key_info *info); + +/** + * apple_smc_key_exists - Check if the given SMC key exists + * @smc: Pointer to apple_smc struct + * @key: smc_key to be checked + * + * Return: True if the key exists, false otherwise + */ +static inline bool apple_smc_key_exists(struct apple_smc *smc, smc_key key) +{ + return apple_smc_get_key_info(smc, key, NULL) >= 0; +} + +#define APPLE_SMC_TYPE_OPS(type) \ + static inline int apple_smc_read_##type(struct apple_smc *smc, smc_key key, type *p) \ + { \ + int ret = apple_smc_read(smc, key, p, sizeof(*p)); \ + return (ret < 0) ? ret : ((ret != sizeof(*p)) ? -EINVAL : 0); \ + } \ + static inline int apple_smc_write_##type(struct apple_smc *smc, smc_key key, type p) \ + { \ + return apple_smc_write(smc, key, &p, sizeof(p)); \ + } \ + static inline int apple_smc_write_##type##_atomic(struct apple_smc *smc, smc_key key, type p) \ + { \ + return apple_smc_write_atomic(smc, key, &p, sizeof(p)); \ + } \ + static inline int apple_smc_rw_##type(struct apple_smc *smc, smc_key key, \ + type w, type *r) \ + { \ + int ret = apple_smc_rw(smc, key, &w, sizeof(w), r, sizeof(*r)); \ + return (ret < 0) ? ret : ((ret != sizeof(*r)) ? -EINVAL : 0); \ + } + +APPLE_SMC_TYPE_OPS(u64) +APPLE_SMC_TYPE_OPS(u32) +APPLE_SMC_TYPE_OPS(u16) +APPLE_SMC_TYPE_OPS(u8) +APPLE_SMC_TYPE_OPS(s64) +APPLE_SMC_TYPE_OPS(s32) +APPLE_SMC_TYPE_OPS(s16) +APPLE_SMC_TYPE_OPS(s8) + +static inline int apple_smc_read_flag(struct apple_smc *smc, smc_key key, bool *flag) +{ + u8 val; + int ret = apple_smc_read_u8(smc, key, &val); + + if (ret < 0) + return ret; + + *flag = val ? true : false; + return ret; +} + +static inline int apple_smc_write_flag(struct apple_smc *smc, smc_key key, bool state) +{ + return apple_smc_write_u8(smc, key, state ? 1 : 0); +} + +static inline int apple_smc_write_flag_atomic(struct apple_smc *smc, smc_key key, bool state) +{ + return apple_smc_write_u8_atomic(smc, key, state ? 1 : 0); +} + +#endif |
