Skip to content

Commit 205e985

Browse files
committed
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 <[email protected]>
1 parent d55f4cc commit 205e985

3 files changed

Lines changed: 250 additions & 0 deletions

File tree

drivers/gpio/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,6 +1281,17 @@ config GPIO_LP87565
12811281
This driver can also be built as a module. If so, the module will be
12821282
called gpio-lp87565.
12831283

1284+
config GPIO_MACSMC
1285+
tristate "Apple Mac SMC GPIO"
1286+
depends on APPLE_SMC
1287+
default ARCH_APPLE
1288+
help
1289+
Support for GPIOs controlled by the SMC microcontroller on Apple Mac
1290+
systems.
1291+
1292+
This driver can also be built as a module. If so, the module will be
1293+
called gpio-macsmc.
1294+
12841295
config GPIO_MADERA
12851296
tristate "Cirrus Logic Madera class codecs"
12861297
depends on PINCTRL_MADERA

drivers/gpio/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ obj-$(CONFIG_GPIO_LP873X) += gpio-lp873x.o
8585
obj-$(CONFIG_GPIO_LP87565) += gpio-lp87565.o
8686
obj-$(CONFIG_GPIO_LPC18XX) += gpio-lpc18xx.o
8787
obj-$(CONFIG_GPIO_LPC32XX) += gpio-lpc32xx.o
88+
obj-$(CONFIG_GPIO_MACSMC) += gpio-macsmc.o
8889
obj-$(CONFIG_GPIO_MADERA) += gpio-madera.o
8990
obj-$(CONFIG_GPIO_MAX3191X) += gpio-max3191x.o
9091
obj-$(CONFIG_GPIO_MAX7300) += gpio-max7300.o

drivers/gpio/gpio-macsmc.c

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
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_get_direction(struct gpio_chip *gc, unsigned int offset)
96+
{
97+
struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
98+
smc_key key = macsmc_gpio_key(offset);
99+
u32 val;
100+
int ret;
101+
102+
/* First try reading the explicit pin mode register */
103+
ret = apple_smc_rw_u32(smcgp->smc, key, CMD_PINMODE, &val);
104+
if (!ret)
105+
return (val & MODE_OUTPUT) ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
106+
107+
/*
108+
* Less common IRQ configs cause CMD_PINMODE to fail, and so does open drain mode.
109+
* Fall back to reading IRQ mode, which will only succeed for inputs.
110+
*/
111+
ret = apple_smc_rw_u32(smcgp->smc, key, CMD_IRQ_MODE, &val);
112+
return (!ret) ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT;
113+
}
114+
115+
static int macsmc_gpio_get(struct gpio_chip *gc, unsigned int offset)
116+
{
117+
struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
118+
smc_key key = macsmc_gpio_key(offset);
119+
u32 val;
120+
int ret;
121+
122+
ret = macsmc_gpio_get_direction(gc, offset);
123+
if (ret < 0)
124+
return ret;
125+
126+
if (ret == GPIO_LINE_DIRECTION_OUT)
127+
ret = apple_smc_rw_u32(smcgp->smc, key, CMD_OUTPUT, &val);
128+
else
129+
ret = apple_smc_rw_u32(smcgp->smc, key, CMD_INPUT, &val);
130+
131+
if (ret < 0)
132+
return ret;
133+
134+
return val ? 1 : 0;
135+
}
136+
137+
static void macsmc_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
138+
{
139+
struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
140+
smc_key key = macsmc_gpio_key(offset);
141+
int ret;
142+
143+
value |= CMD_OUTPUT;
144+
ret = apple_smc_write_u32(smcgp->smc, key, CMD_OUTPUT | value);
145+
if (ret < 0)
146+
dev_err(smcgp->dev, "GPIO set failed %p4ch = 0x%x\n", &key, value);
147+
}
148+
149+
static int macsmc_gpio_init_valid_mask(struct gpio_chip *gc,
150+
unsigned long *valid_mask, unsigned int ngpios)
151+
{
152+
struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
153+
int count = apple_smc_get_key_count(smcgp->smc) - smcgp->first_index;
154+
int i;
155+
156+
if (count > MAX_GPIO)
157+
count = MAX_GPIO;
158+
159+
bitmap_zero(valid_mask, ngpios);
160+
161+
for (i = 0; i < count; i++) {
162+
smc_key key;
163+
int gpio_nr;
164+
int ret = apple_smc_get_key_by_index(smcgp->smc, smcgp->first_index + i, &key);
165+
166+
if (ret < 0)
167+
return ret;
168+
169+
if (key > SMC_KEY(gPff))
170+
break;
171+
172+
gpio_nr = macsmc_gpio_nr(key);
173+
if (gpio_nr < 0 || gpio_nr > MAX_GPIO) {
174+
dev_err(smcgp->dev, "Bad GPIO key %p4ch\n", &key);
175+
continue;
176+
}
177+
178+
set_bit(gpio_nr, valid_mask);
179+
}
180+
181+
return 0;
182+
}
183+
184+
static int macsmc_gpio_probe(struct platform_device *pdev)
185+
{
186+
struct macsmc_gpio *smcgp;
187+
struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
188+
smc_key key;
189+
int ret;
190+
191+
smcgp = devm_kzalloc(&pdev->dev, sizeof(*smcgp), GFP_KERNEL);
192+
if (!smcgp)
193+
return -ENOMEM;
194+
195+
pdev->dev.of_node = of_get_child_by_name(pdev->dev.parent->of_node, "gpio");
196+
197+
smcgp->dev = &pdev->dev;
198+
smcgp->smc = smc;
199+
smcgp->first_index = apple_smc_find_first_key_index(smc, SMC_KEY(gP00));
200+
201+
if (smcgp->first_index >= apple_smc_get_key_count(smc))
202+
return -ENODEV;
203+
204+
ret = apple_smc_get_key_by_index(smc, smcgp->first_index, &key);
205+
if (ret < 0)
206+
return ret;
207+
208+
if (key > macsmc_gpio_key(MAX_GPIO - 1))
209+
return -ENODEV;
210+
211+
dev_info(smcgp->dev, "First GPIO key: %p4ch\n", &key);
212+
213+
smcgp->gc.label = "macsmc-pmu-gpio";
214+
smcgp->gc.owner = THIS_MODULE;
215+
smcgp->gc.get = macsmc_gpio_get;
216+
smcgp->gc.set = macsmc_gpio_set;
217+
smcgp->gc.get_direction = macsmc_gpio_get_direction;
218+
smcgp->gc.init_valid_mask = macsmc_gpio_init_valid_mask;
219+
smcgp->gc.can_sleep = true;
220+
smcgp->gc.ngpio = MAX_GPIO;
221+
smcgp->gc.base = -1;
222+
smcgp->gc.parent = &pdev->dev;
223+
224+
return devm_gpiochip_add_data(&pdev->dev, &smcgp->gc, smcgp);
225+
}
226+
227+
static struct platform_driver macsmc_gpio_driver = {
228+
.driver = {
229+
.name = "macsmc-gpio",
230+
},
231+
.probe = macsmc_gpio_probe,
232+
};
233+
module_platform_driver(macsmc_gpio_driver);
234+
235+
MODULE_AUTHOR("Hector Martin <[email protected]>");
236+
MODULE_LICENSE("Dual MIT/GPL");
237+
MODULE_DESCRIPTION("Apple SMC GPIO driver");
238+
MODULE_ALIAS("platform:macsmc-gpio");

0 commit comments

Comments
 (0)