Skip to content

Commit 9b21051

Browse files
marcanlag-linaro
authored andcommitted
gpio: Add new gpio-macsmc driver for Apple Macs
This driver implements the GPIO service on top of the SMC framework on Apple Mac machines. In particular, these are the GPIOs present in the PMU IC which are used to control power to certain on-board devices. Although the underlying hardware supports various pin config settings (input/output, open drain, etc.), this driver does not implement that functionality and leaves it up to the firmware to configure things properly. We also don't yet support interrupts/events. This is sufficient for device power control, which is the only thing we need to support at this point. More features will be implemented when needed. To our knowledge, only Apple Silicon Macs implement this SMC feature. Signed-off-by: Hector Martin <marcan@marcan.st> Reviewed-by: Bartosz Golaszewski <brgl@bgdev.pl> Reviewed-by: Linus Walleij <linus.walleij@linaro.org> Reviewed-by: Sven Peter <sven@kernel.org> Signed-off-by: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk> Reviewed-by: Alyssa Rosenzweig <alyssa@rosenzweig.io> Reviewed-by: Neal Gompa <neal@gompa.dev> Signed-off-by: Sven Peter <sven@kernel.org> Acked-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org> Link: https://lore.kernel.org/r/20250610-smc-6-15-v7-6-556cafd771d3@kernel.org Signed-off-by: Lee Jones <lee@kernel.org>
1 parent e038d98 commit 9b21051

4 files changed

Lines changed: 304 additions & 0 deletions

File tree

MAINTAINERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2357,6 +2357,7 @@ F: drivers/bluetooth/hci_bcm4377.c
23572357
F: drivers/clk/clk-apple-nco.c
23582358
F: drivers/cpufreq/apple-soc-cpufreq.c
23592359
F: drivers/dma/apple-admac.c
2360+
F: drivers/gpio/gpio-macsmc.c
23602361
F: drivers/pmdomain/apple/
23612362
F: drivers/i2c/busses/i2c-pasemi-core.c
23622363
F: drivers/i2c/busses/i2c-pasemi-platform.c

drivers/gpio/Kconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,6 +1464,16 @@ config GPIO_LP87565
14641464
This driver can also be built as a module. If so, the module will be
14651465
called gpio-lp87565.
14661466

1467+
config GPIO_MACSMC
1468+
tristate "Apple Mac SMC GPIO"
1469+
depends on MFD_MACSMC
1470+
help
1471+
Support for GPIOs controlled by the SMC microcontroller on Apple Mac
1472+
systems.
1473+
1474+
This driver can also be built as a module. If so, the module will be
1475+
called gpio-macsmc.
1476+
14671477
config GPIO_MADERA
14681478
tristate "Cirrus Logic Madera class codecs"
14691479
depends on PINCTRL_MADERA

drivers/gpio/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ obj-$(CONFIG_GPIO_LP873X) += gpio-lp873x.o
9999
obj-$(CONFIG_GPIO_LP87565) += gpio-lp87565.o
100100
obj-$(CONFIG_GPIO_LPC18XX) += gpio-lpc18xx.o
101101
obj-$(CONFIG_GPIO_LPC32XX) += gpio-lpc32xx.o
102+
obj-$(CONFIG_GPIO_MACSMC) += gpio-macsmc.o
102103
obj-$(CONFIG_GPIO_MADERA) += gpio-madera.o
103104
obj-$(CONFIG_GPIO_MAX3191X) += gpio-max3191x.o
104105
obj-$(CONFIG_GPIO_MAX7300) += gpio-max7300.o

drivers/gpio/gpio-macsmc.c

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
// SPDX-License-Identifier: GPL-2.0-only OR MIT
2+
/*
3+
* Apple SMC GPIO driver
4+
* Copyright The Asahi Linux Contributors
5+
*
6+
* This driver implements basic SMC PMU GPIO support that can read inputs
7+
* and write outputs. Mode changes and IRQ config are not yet implemented.
8+
*/
9+
10+
#include <linux/bitmap.h>
11+
#include <linux/device.h>
12+
#include <linux/gpio/driver.h>
13+
#include <linux/mfd/core.h>
14+
#include <linux/mfd/macsmc.h>
15+
16+
#define MAX_GPIO 64
17+
18+
/*
19+
* Commands 0-6 are, presumably, the intended API.
20+
* Command 0xff lets you get/set the pin configuration in detail directly,
21+
* but the bit meanings seem not to be stable between devices/PMU hardware
22+
* versions.
23+
*
24+
* We're going to try to make do with the low commands for now.
25+
* We don't implement pin mode changes at this time.
26+
*/
27+
28+
#define CMD_ACTION (0 << 24)
29+
#define CMD_OUTPUT (1 << 24)
30+
#define CMD_INPUT (2 << 24)
31+
#define CMD_PINMODE (3 << 24)
32+
#define CMD_IRQ_ENABLE (4 << 24)
33+
#define CMD_IRQ_ACK (5 << 24)
34+
#define CMD_IRQ_MODE (6 << 24)
35+
#define CMD_CONFIG (0xff << 24)
36+
37+
#define MODE_INPUT 0
38+
#define MODE_OUTPUT 1
39+
#define MODE_VALUE_0 0
40+
#define MODE_VALUE_1 2
41+
42+
#define IRQ_MODE_HIGH 0
43+
#define IRQ_MODE_LOW 1
44+
#define IRQ_MODE_RISING 2
45+
#define IRQ_MODE_FALLING 3
46+
#define IRQ_MODE_BOTH 4
47+
48+
#define CONFIG_MASK GENMASK(23, 16)
49+
#define CONFIG_VAL GENMASK(7, 0)
50+
51+
#define CONFIG_OUTMODE GENMASK(7, 6)
52+
#define CONFIG_IRQMODE GENMASK(5, 3)
53+
#define CONFIG_PULLDOWN BIT(2)
54+
#define CONFIG_PULLUP BIT(1)
55+
#define CONFIG_OUTVAL BIT(0)
56+
57+
/*
58+
* Output modes seem to differ depending on the PMU in use... ?
59+
* j274 / M1 (Sera PMU):
60+
* 0 = input
61+
* 1 = output
62+
* 2 = open drain
63+
* 3 = disable
64+
* j314 / M1Pro (Maverick PMU):
65+
* 0 = input
66+
* 1 = open drain
67+
* 2 = output
68+
* 3 = ?
69+
*/
70+
71+
struct macsmc_gpio {
72+
struct device *dev;
73+
struct apple_smc *smc;
74+
struct gpio_chip gc;
75+
76+
int first_index;
77+
};
78+
79+
static int macsmc_gpio_nr(smc_key key)
80+
{
81+
int low = hex_to_bin(key & 0xff);
82+
int high = hex_to_bin((key >> 8) & 0xff);
83+
84+
if (low < 0 || high < 0)
85+
return -1;
86+
87+
return low | (high << 4);
88+
}
89+
90+
static int macsmc_gpio_key(unsigned int offset)
91+
{
92+
return _SMC_KEY("gP\0\0") | hex_asc_hi(offset) << 8 | hex_asc_lo(offset);
93+
}
94+
95+
static int macsmc_gpio_find_first_gpio_index(struct macsmc_gpio *smcgp)
96+
{
97+
struct apple_smc *smc = smcgp->smc;
98+
smc_key key = macsmc_gpio_key(0);
99+
smc_key first_key, last_key;
100+
int start, count, ret;
101+
102+
/* Return early if the key is out of bounds */
103+
ret = apple_smc_get_key_by_index(smc, 0, &first_key);
104+
if (ret)
105+
return ret;
106+
if (key <= first_key)
107+
return -ENODEV;
108+
109+
ret = apple_smc_get_key_by_index(smc, smc->key_count - 1, &last_key);
110+
if (ret)
111+
return ret;
112+
if (key > last_key)
113+
return -ENODEV;
114+
115+
/* Binary search to find index of first SMC key bigger or equal to key */
116+
start = 0;
117+
count = smc->key_count;
118+
while (count > 1) {
119+
smc_key pkey;
120+
int pivot = start + ((count - 1) >> 1);
121+
122+
ret = apple_smc_get_key_by_index(smc, pivot, &pkey);
123+
if (ret < 0)
124+
return ret;
125+
126+
if (pkey == key)
127+
return pivot;
128+
129+
pivot++;
130+
131+
if (pkey < key) {
132+
count -= pivot - start;
133+
start = pivot;
134+
} else {
135+
count = pivot - start;
136+
}
137+
}
138+
139+
return start;
140+
}
141+
142+
static int macsmc_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
143+
{
144+
struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
145+
smc_key key = macsmc_gpio_key(offset);
146+
u32 val;
147+
int ret;
148+
149+
/* First try reading the explicit pin mode register */
150+
ret = apple_smc_rw_u32(smcgp->smc, key, CMD_PINMODE, &val);
151+
if (!ret)
152+
return (val & MODE_OUTPUT) ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
153+
154+
/*
155+
* Less common IRQ configs cause CMD_PINMODE to fail, and so does open drain mode.
156+
* Fall back to reading IRQ mode, which will only succeed for inputs.
157+
*/
158+
ret = apple_smc_rw_u32(smcgp->smc, key, CMD_IRQ_MODE, &val);
159+
return ret ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
160+
}
161+
162+
static int macsmc_gpio_get(struct gpio_chip *gc, unsigned int offset)
163+
{
164+
struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
165+
smc_key key = macsmc_gpio_key(offset);
166+
u32 cmd, val;
167+
int ret;
168+
169+
ret = macsmc_gpio_get_direction(gc, offset);
170+
if (ret < 0)
171+
return ret;
172+
173+
if (ret == GPIO_LINE_DIRECTION_OUT)
174+
cmd = CMD_OUTPUT;
175+
else
176+
cmd = CMD_INPUT;
177+
178+
ret = apple_smc_rw_u32(smcgp->smc, key, cmd, &val);
179+
if (ret < 0)
180+
return ret;
181+
182+
return val ? 1 : 0;
183+
}
184+
185+
static int macsmc_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
186+
{
187+
struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
188+
smc_key key = macsmc_gpio_key(offset);
189+
int ret;
190+
191+
value |= CMD_OUTPUT;
192+
ret = apple_smc_write_u32(smcgp->smc, key, CMD_OUTPUT | value);
193+
if (ret < 0)
194+
dev_err(smcgp->dev, "GPIO set failed %p4ch = 0x%x\n",
195+
&key, value);
196+
197+
return ret;
198+
}
199+
200+
static int macsmc_gpio_init_valid_mask(struct gpio_chip *gc,
201+
unsigned long *valid_mask, unsigned int ngpios)
202+
{
203+
struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
204+
int count;
205+
int i;
206+
207+
count = min(smcgp->smc->key_count, MAX_GPIO);
208+
209+
bitmap_zero(valid_mask, ngpios);
210+
211+
for (i = 0; i < count; i++) {
212+
int ret, gpio_nr;
213+
smc_key key;
214+
215+
ret = apple_smc_get_key_by_index(smcgp->smc, smcgp->first_index + i, &key);
216+
if (ret < 0)
217+
return ret;
218+
219+
if (key > SMC_KEY(gPff))
220+
break;
221+
222+
gpio_nr = macsmc_gpio_nr(key);
223+
if (gpio_nr < 0 || gpio_nr > MAX_GPIO) {
224+
dev_err(smcgp->dev, "Bad GPIO key %p4ch\n", &key);
225+
continue;
226+
}
227+
228+
set_bit(gpio_nr, valid_mask);
229+
}
230+
231+
return 0;
232+
}
233+
234+
static int macsmc_gpio_probe(struct platform_device *pdev)
235+
{
236+
struct macsmc_gpio *smcgp;
237+
struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
238+
smc_key key;
239+
int ret;
240+
241+
smcgp = devm_kzalloc(&pdev->dev, sizeof(*smcgp), GFP_KERNEL);
242+
if (!smcgp)
243+
return -ENOMEM;
244+
245+
smcgp->dev = &pdev->dev;
246+
smcgp->smc = smc;
247+
248+
smcgp->first_index = macsmc_gpio_find_first_gpio_index(smcgp);
249+
if (smcgp->first_index < 0)
250+
return smcgp->first_index;
251+
252+
ret = apple_smc_get_key_by_index(smc, smcgp->first_index, &key);
253+
if (ret < 0)
254+
return ret;
255+
256+
if (key > macsmc_gpio_key(MAX_GPIO - 1))
257+
return -ENODEV;
258+
259+
dev_info(smcgp->dev, "First GPIO key: %p4ch\n", &key);
260+
261+
smcgp->gc.label = "macsmc-pmu-gpio";
262+
smcgp->gc.owner = THIS_MODULE;
263+
smcgp->gc.get = macsmc_gpio_get;
264+
smcgp->gc.set_rv = macsmc_gpio_set;
265+
smcgp->gc.get_direction = macsmc_gpio_get_direction;
266+
smcgp->gc.init_valid_mask = macsmc_gpio_init_valid_mask;
267+
smcgp->gc.can_sleep = true;
268+
smcgp->gc.ngpio = MAX_GPIO;
269+
smcgp->gc.base = -1;
270+
smcgp->gc.parent = &pdev->dev;
271+
272+
return devm_gpiochip_add_data(&pdev->dev, &smcgp->gc, smcgp);
273+
}
274+
275+
static const struct of_device_id macsmc_gpio_of_table[] = {
276+
{ .compatible = "apple,smc-gpio", },
277+
{}
278+
};
279+
MODULE_DEVICE_TABLE(of, macsmc_gpio_of_table);
280+
281+
static struct platform_driver macsmc_gpio_driver = {
282+
.driver = {
283+
.name = "macsmc-gpio",
284+
.of_match_table = macsmc_gpio_of_table,
285+
},
286+
.probe = macsmc_gpio_probe,
287+
};
288+
module_platform_driver(macsmc_gpio_driver);
289+
290+
MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
291+
MODULE_LICENSE("Dual MIT/GPL");
292+
MODULE_DESCRIPTION("Apple SMC GPIO driver");

0 commit comments

Comments
 (0)