mirror of https://github.com/torvalds/linux.git
Immutable branch between MFD, GPIO, Power and SoC due for the v6.17 merge window
-----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEdrbJNaO+IJqU8IdIUa+KL4f8d2EFAmiCCbUACgkQUa+KL4f8 d2H+ZA//Zy+DBrMj9q+5EWoNCfwEtPY2/FFCDxNPIQyV24xF2DywthvmP3/Hbqlu JE1I5DURclOz5RRNaoyYLSmBlHU+jlYpjvhGDSAx4tA1aynh+DYy7eW62FdWnqhy yxD/wRSiIegrqt26HrPfjqlToevbyrHp4Tk8f90cr/g2YJnS84L/myHTmm0dZw3G I4WdFYmkxm4KZQXa2l7d1PzFCIRq6P2nWH8rRUbGCuGKAaDyykdgrAzZdbcdWKoY TinYa41Aag6w19rIpqgoIo9ycGRyBCj1Bg4kPtr9C1OelAMqAIdyB3WDwNgl14vX RGAp+J3XYDFIMY/WA1r7t+CEOBkYhvYmbrsGcNBMpe4jdUr9QsqADIHd+nwXydow SsG6K57L3qAH/CRbiqji3tV2NLxuJwE2HE/1WEuUCtPwjAGdzmd3tFdzrdxewwWb 8fSHncstlTXwzQoveUbuCPjQi7iAlatzWqAhcc18G7XlTq4f2tFwRGrc6tK/iOug 6RqlqTy4utetYiS8mScz3VLezHrKNWJoy0c/oKkBP8ZUQRGv9mmYoulkesZ4SJ/v uXtiqNYrioWbULTOHRbrXqL9ukD6GdnesWdaRLCTGcyRs8UwA9Dj1NqpNV5VEbAE V04jH0Uj/KI5X1l3c+QQBdooIRs9Tt7gRr0Re8mBaW6CGVAedOU= =bazF -----END PGP SIGNATURE----- Merge tag 'ib-mfd-gpio-power-soc-v6.17' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd into gpio/for-next Immutable branch between MFD, GPIO, Power and SoC due for the v6.17 merge window
This commit is contained in:
commit
8c7a86088a
|
|
@ -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
|
||||||
|
|
@ -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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -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
|
||||||
|
|
@ -2332,6 +2332,7 @@ F: Documentation/devicetree/bindings/arm/apple/*
|
||||||
F: Documentation/devicetree/bindings/clock/apple,nco.yaml
|
F: Documentation/devicetree/bindings/clock/apple,nco.yaml
|
||||||
F: Documentation/devicetree/bindings/cpufreq/apple,cluster-cpufreq.yaml
|
F: Documentation/devicetree/bindings/cpufreq/apple,cluster-cpufreq.yaml
|
||||||
F: Documentation/devicetree/bindings/dma/apple,admac.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/i2c/apple,i2c.yaml
|
||||||
F: Documentation/devicetree/bindings/input/touchscreen/apple,z2-multitouch.yaml
|
F: Documentation/devicetree/bindings/input/touchscreen/apple,z2-multitouch.yaml
|
||||||
F: Documentation/devicetree/bindings/interrupt-controller/apple,*
|
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/iommu/apple,sart.yaml
|
||||||
F: Documentation/devicetree/bindings/leds/backlight/apple,dwi-bl.yaml
|
F: Documentation/devicetree/bindings/leds/backlight/apple,dwi-bl.yaml
|
||||||
F: Documentation/devicetree/bindings/mailbox/apple,mailbox.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/net/bluetooth/brcm,bcm4377-bluetooth.yaml
|
||||||
F: Documentation/devicetree/bindings/nvme/apple,nvme-ans.yaml
|
F: Documentation/devicetree/bindings/nvme/apple,nvme-ans.yaml
|
||||||
F: Documentation/devicetree/bindings/nvmem/apple,efuses.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/pci/apple,pcie.yaml
|
||||||
F: Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml
|
F: Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml
|
||||||
F: Documentation/devicetree/bindings/power/apple*
|
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/pwm/apple,s5l-fpwm.yaml
|
||||||
F: Documentation/devicetree/bindings/spi/apple,spi.yaml
|
F: Documentation/devicetree/bindings/spi/apple,spi.yaml
|
||||||
F: Documentation/devicetree/bindings/spmi/apple,spmi.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/clk/clk-apple-nco.c
|
||||||
F: drivers/cpufreq/apple-soc-cpufreq.c
|
F: drivers/cpufreq/apple-soc-cpufreq.c
|
||||||
F: drivers/dma/apple-admac.c
|
F: drivers/dma/apple-admac.c
|
||||||
|
F: drivers/gpio/gpio-macsmc.c
|
||||||
F: drivers/pmdomain/apple/
|
F: drivers/pmdomain/apple/
|
||||||
F: drivers/i2c/busses/i2c-pasemi-core.c
|
F: drivers/i2c/busses/i2c-pasemi-core.c
|
||||||
F: drivers/i2c/busses/i2c-pasemi-platform.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/apple-dart.c
|
||||||
F: drivers/iommu/io-pgtable-dart.c
|
F: drivers/iommu/io-pgtable-dart.c
|
||||||
F: drivers/irqchip/irq-apple-aic.c
|
F: drivers/irqchip/irq-apple-aic.c
|
||||||
|
F: drivers/mfd/macsmc.c
|
||||||
F: drivers/nvme/host/apple.c
|
F: drivers/nvme/host/apple.c
|
||||||
F: drivers/nvmem/apple-efuses.c
|
F: drivers/nvmem/apple-efuses.c
|
||||||
F: drivers/nvmem/apple-spmi-nvmem.c
|
F: drivers/nvmem/apple-spmi-nvmem.c
|
||||||
F: drivers/pinctrl/pinctrl-apple-gpio.c
|
F: drivers/pinctrl/pinctrl-apple-gpio.c
|
||||||
|
F: drivers/power/reset/macsmc-reboot.c
|
||||||
F: drivers/pwm/pwm-apple.c
|
F: drivers/pwm/pwm-apple.c
|
||||||
F: drivers/soc/apple/*
|
F: drivers/soc/apple/*
|
||||||
F: drivers/spi/spi-apple.c
|
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: drivers/watchdog/apple_wdt.c
|
||||||
F: include/dt-bindings/interrupt-controller/apple-aic.h
|
F: include/dt-bindings/interrupt-controller/apple-aic.h
|
||||||
F: include/dt-bindings/pinctrl/apple.h
|
F: include/dt-bindings/pinctrl/apple.h
|
||||||
|
F: include/linux/mfd/macsmc.h
|
||||||
F: include/linux/soc/apple/*
|
F: include/linux/soc/apple/*
|
||||||
F: include/uapi/drm/asahi_drm.h
|
F: include/uapi/drm/asahi_drm.h
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1473,6 +1473,16 @@ config GPIO_LP87565
|
||||||
This driver can also be built as a module. If so, the module will be
|
This driver can also be built as a module. If so, the module will be
|
||||||
called gpio-lp87565.
|
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
|
config GPIO_MADERA
|
||||||
tristate "Cirrus Logic Madera class codecs"
|
tristate "Cirrus Logic Madera class codecs"
|
||||||
depends on PINCTRL_MADERA
|
depends on PINCTRL_MADERA
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,7 @@ obj-$(CONFIG_GPIO_LP873X) += gpio-lp873x.o
|
||||||
obj-$(CONFIG_GPIO_LP87565) += gpio-lp87565.o
|
obj-$(CONFIG_GPIO_LP87565) += gpio-lp87565.o
|
||||||
obj-$(CONFIG_GPIO_LPC18XX) += gpio-lpc18xx.o
|
obj-$(CONFIG_GPIO_LPC18XX) += gpio-lpc18xx.o
|
||||||
obj-$(CONFIG_GPIO_LPC32XX) += gpio-lpc32xx.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_MADERA) += gpio-madera.o
|
||||||
obj-$(CONFIG_GPIO_MAX3191X) += gpio-max3191x.o
|
obj-$(CONFIG_GPIO_MAX3191X) += gpio-max3191x.o
|
||||||
obj-$(CONFIG_GPIO_MAX7300) += gpio-max7300.o
|
obj-$(CONFIG_GPIO_MAX7300) += gpio-max7300.o
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
@ -285,6 +285,24 @@ config MFD_CS42L43_SDW
|
||||||
Select this to support the Cirrus Logic CS42L43 PC CODEC with
|
Select this to support the Cirrus Logic CS42L43 PC CODEC with
|
||||||
headphone and class D speaker drivers over SoundWire.
|
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
|
config MFD_MADERA
|
||||||
tristate "Cirrus Logic Madera codecs"
|
tristate "Cirrus Logic Madera codecs"
|
||||||
select MFD_CORE
|
select MFD_CORE
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ obj-$(CONFIG_MFD_CS42L43_SDW) += cs42l43-sdw.o
|
||||||
obj-$(CONFIG_MFD_ENE_KB3930) += ene-kb3930.o
|
obj-$(CONFIG_MFD_ENE_KB3930) += ene-kb3930.o
|
||||||
obj-$(CONFIG_MFD_EXYNOS_LPASS) += exynos-lpass.o
|
obj-$(CONFIG_MFD_EXYNOS_LPASS) += exynos-lpass.o
|
||||||
obj-$(CONFIG_MFD_GATEWORKS_GSC) += gateworks-gsc.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_LP873X) += lp873x.o
|
||||||
obj-$(CONFIG_MFD_TI_LP87565) += lp87565.o
|
obj-$(CONFIG_MFD_TI_LP87565) += lp87565.o
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
@ -128,6 +128,15 @@ config POWER_RESET_LINKSTATION
|
||||||
|
|
||||||
Say Y here if you have a Buffalo LinkStation LS421D/E.
|
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
|
config POWER_RESET_MSM
|
||||||
bool "Qualcomm MSM power-off driver"
|
bool "Qualcomm MSM power-off driver"
|
||||||
depends on ARCH_QCOM
|
depends on ARCH_QCOM
|
||||||
|
|
|
||||||
|
|
@ -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_GPIO_RESTART) += gpio-restart.o
|
||||||
obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o
|
obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o
|
||||||
obj-$(CONFIG_POWER_RESET_LINKSTATION) += linkstation-poweroff.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_MSM) += msm-poweroff.o
|
||||||
obj-$(CONFIG_POWER_RESET_MT6323) += mt6323-poweroff.o
|
obj-$(CONFIG_POWER_RESET_MT6323) += mt6323-poweroff.o
|
||||||
obj-$(CONFIG_POWER_RESET_QCOM_PON) += qcom-pon.o
|
obj-$(CONFIG_POWER_RESET_QCOM_PON) += qcom-pon.o
|
||||||
|
|
|
||||||
|
|
@ -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>");
|
||||||
|
|
@ -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",
|
dev_dbg(rtk->dev, "RTKit: buffer request for 0x%zx bytes at %pad\n",
|
||||||
buffer->size, &buffer->iova);
|
buffer->size, &buffer->iova);
|
||||||
|
|
||||||
if (buffer->iova &&
|
if (buffer->iova && !rtk->ops->shmem_setup) {
|
||||||
(!rtk->ops->shmem_setup || !rtk->ops->shmem_destroy)) {
|
|
||||||
err = -EINVAL;
|
err = -EINVAL;
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue