Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

spmi: add a spmi driver for Apple SoC

The connected PMU contains several useful nvmem cells such as RTC offset,
boot failure counters, reboot/shutdown selector, and a few others.
In addition M3+ machines have their USB-PD controller connected via SPMI.

Signed-off-by: Jean-Francois Bortolotti <jeff@borto.fr>
Reviewed-by: Sven Peter <sven@svenpeter.dev>
Reviewed-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Co-developed-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
Link: https://lore.kernel.org/r/20250409-spmi-v4-2-eb81ecfd1f64@gmail.com
Reviewed-by: Neal Gompa <neal@gompa.dev>
Signed-off-by: Stephen Boyd <sboyd@kernel.org>
Link: https://lore.kernel.org/r/20250518032330.2959766-4-sboyd@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Jean-Francois Bortolotti and committed by
Greg Kroah-Hartman
77ca75e8 8cc68226

+178
+1
MAINTAINERS
··· 2304 2304 F: drivers/pwm/pwm-apple.c 2305 2305 F: drivers/soc/apple/* 2306 2306 F: drivers/spi/spi-apple.c 2307 + F: drivers/spmi/spmi-apple-controller.c 2307 2308 F: drivers/video/backlight/apple_dwi_bl.c 2308 2309 F: drivers/watchdog/apple_wdt.c 2309 2310 F: include/dt-bindings/interrupt-controller/apple-aic.h
+8
drivers/spmi/Kconfig
··· 11 11 12 12 if SPMI 13 13 14 + config SPMI_APPLE 15 + tristate "Apple SoC SPMI Controller platform driver" 16 + depends on ARCH_APPLE || COMPILE_TEST 17 + help 18 + If you say yes to this option, support will be included for the 19 + SPMI controller present on many Apple SoCs, including the 20 + t8103 (M1) and t600x (M1 Pro/Max). 21 + 14 22 config SPMI_HISI3670 15 23 tristate "Hisilicon 3670 SPMI Controller" 16 24 select IRQ_DOMAIN_HIERARCHY
+1
drivers/spmi/Makefile
··· 4 4 # 5 5 obj-$(CONFIG_SPMI) += spmi.o spmi-devres.o 6 6 7 + obj-$(CONFIG_SPMI_APPLE) += spmi-apple-controller.o 7 8 obj-$(CONFIG_SPMI_HISI3670) += hisi-spmi-controller.o 8 9 obj-$(CONFIG_SPMI_MSM_PMIC_ARB) += spmi-pmic-arb.o 9 10 obj-$(CONFIG_SPMI_MTK_PMIF) += spmi-mtk-pmif.o
+168
drivers/spmi/spmi-apple-controller.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Apple SoC SPMI device driver 4 + * 5 + * Copyright The Asahi Linux Contributors 6 + * 7 + * Inspired by: 8 + * OpenBSD support Copyright (c) 2021 Mark Kettenis <kettenis@openbsd.org> 9 + * Correllium support Copyright (C) 2021 Corellium LLC 10 + * hisi-spmi-controller.c 11 + * spmi-pmic-arb.c Copyright (c) 2021, The Linux Foundation. 12 + */ 13 + 14 + #include <linux/io.h> 15 + #include <linux/iopoll.h> 16 + #include <linux/module.h> 17 + #include <linux/mod_devicetable.h> 18 + #include <linux/platform_device.h> 19 + #include <linux/spmi.h> 20 + 21 + /* SPMI Controller Registers */ 22 + #define SPMI_STATUS_REG 0 23 + #define SPMI_CMD_REG 0x4 24 + #define SPMI_RSP_REG 0x8 25 + 26 + #define SPMI_RX_FIFO_EMPTY BIT(24) 27 + 28 + #define REG_POLL_INTERVAL_US 10000 29 + #define REG_POLL_TIMEOUT_US (REG_POLL_INTERVAL_US * 5) 30 + 31 + struct apple_spmi { 32 + void __iomem *regs; 33 + }; 34 + 35 + #define poll_reg(spmi, reg, val, cond) \ 36 + readl_poll_timeout((spmi)->regs + (reg), (val), (cond), \ 37 + REG_POLL_INTERVAL_US, REG_POLL_TIMEOUT_US) 38 + 39 + static inline u32 apple_spmi_pack_cmd(u8 opc, u8 sid, u16 saddr, size_t len) 40 + { 41 + return opc | sid << 8 | saddr << 16 | (len - 1) | (1 << 15); 42 + } 43 + 44 + /* Wait for Rx FIFO to have something */ 45 + static int apple_spmi_wait_rx_not_empty(struct spmi_controller *ctrl) 46 + { 47 + struct apple_spmi *spmi = spmi_controller_get_drvdata(ctrl); 48 + int ret; 49 + u32 status; 50 + 51 + ret = poll_reg(spmi, SPMI_STATUS_REG, status, !(status & SPMI_RX_FIFO_EMPTY)); 52 + if (ret) { 53 + dev_err(&ctrl->dev, 54 + "failed to wait for RX FIFO not empty\n"); 55 + return ret; 56 + } 57 + 58 + return 0; 59 + } 60 + 61 + static int spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, 62 + u16 saddr, u8 *buf, size_t len) 63 + { 64 + struct apple_spmi *spmi = spmi_controller_get_drvdata(ctrl); 65 + u32 spmi_cmd = apple_spmi_pack_cmd(opc, sid, saddr, len); 66 + u32 rsp; 67 + size_t len_read = 0; 68 + u8 i; 69 + int ret; 70 + 71 + writel(spmi_cmd, spmi->regs + SPMI_CMD_REG); 72 + 73 + ret = apple_spmi_wait_rx_not_empty(ctrl); 74 + if (ret) 75 + return ret; 76 + 77 + /* Discard SPMI reply status */ 78 + readl(spmi->regs + SPMI_RSP_REG); 79 + 80 + /* Read SPMI data reply */ 81 + while (len_read < len) { 82 + rsp = readl(spmi->regs + SPMI_RSP_REG); 83 + i = 0; 84 + while ((len_read < len) && (i < 4)) { 85 + buf[len_read++] = ((0xff << (8 * i)) & rsp) >> (8 * i); 86 + i += 1; 87 + } 88 + } 89 + 90 + return 0; 91 + } 92 + 93 + static int spmi_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, 94 + u16 saddr, const u8 *buf, size_t len) 95 + { 96 + struct apple_spmi *spmi = spmi_controller_get_drvdata(ctrl); 97 + u32 spmi_cmd = apple_spmi_pack_cmd(opc, sid, saddr, len); 98 + size_t i = 0, j; 99 + int ret; 100 + 101 + writel(spmi_cmd, spmi->regs + SPMI_CMD_REG); 102 + 103 + while (i < len) { 104 + j = 0; 105 + spmi_cmd = 0; 106 + while ((j < 4) & (i < len)) 107 + spmi_cmd |= buf[i++] << (j++ * 8); 108 + 109 + writel(spmi_cmd, spmi->regs + SPMI_CMD_REG); 110 + } 111 + 112 + ret = apple_spmi_wait_rx_not_empty(ctrl); 113 + if (ret) 114 + return ret; 115 + 116 + /* Discard */ 117 + readl(spmi->regs + SPMI_RSP_REG); 118 + 119 + return 0; 120 + } 121 + 122 + static int apple_spmi_probe(struct platform_device *pdev) 123 + { 124 + struct apple_spmi *spmi; 125 + struct spmi_controller *ctrl; 126 + int ret; 127 + 128 + ctrl = devm_spmi_controller_alloc(&pdev->dev, sizeof(*spmi)); 129 + if (IS_ERR(ctrl)) 130 + return -ENOMEM; 131 + 132 + spmi = spmi_controller_get_drvdata(ctrl); 133 + 134 + spmi->regs = devm_platform_ioremap_resource(pdev, 0); 135 + if (IS_ERR(spmi->regs)) 136 + return PTR_ERR(spmi->regs); 137 + 138 + ctrl->dev.of_node = pdev->dev.of_node; 139 + 140 + ctrl->read_cmd = spmi_read_cmd; 141 + ctrl->write_cmd = spmi_write_cmd; 142 + 143 + ret = devm_spmi_controller_add(&pdev->dev, ctrl); 144 + if (ret) 145 + return dev_err_probe(&pdev->dev, ret, 146 + "spmi_controller_add failed\n"); 147 + 148 + return 0; 149 + } 150 + 151 + static const struct of_device_id apple_spmi_match_table[] = { 152 + { .compatible = "apple,spmi", }, 153 + {} 154 + }; 155 + MODULE_DEVICE_TABLE(of, apple_spmi_match_table); 156 + 157 + static struct platform_driver apple_spmi_driver = { 158 + .probe = apple_spmi_probe, 159 + .driver = { 160 + .name = "apple-spmi", 161 + .of_match_table = apple_spmi_match_table, 162 + }, 163 + }; 164 + module_platform_driver(apple_spmi_driver); 165 + 166 + MODULE_AUTHOR("Jean-Francois Bortolotti <jeff@borto.fr>"); 167 + MODULE_DESCRIPTION("Apple SoC SPMI driver"); 168 + MODULE_LICENSE("GPL");