Skip to content

Commit b232bd4

Browse files
marcanjannau
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>
1 parent 1d51544 commit b232bd4

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
@@ -1420,6 +1420,17 @@ config GPIO_LP87565
14201420
This driver can also be built as a module. If so, the module will be
14211421
called gpio-lp87565.
14221422

1423+
config GPIO_MACSMC
1424+
tristate "Apple Mac SMC GPIO"
1425+
depends on APPLE_SMC
1426+
default ARCH_APPLE
1427+
help
1428+
Support for GPIOs controlled by the SMC microcontroller on Apple Mac
1429+
systems.
1430+
1431+
This driver can also be built as a module. If so, the module will be
1432+
called gpio-macsmc.
1433+
14231434
config GPIO_MADERA
14241435
tristate "Cirrus Logic Madera class codecs"
14251436
depends on PINCTRL_MADERA

drivers/gpio/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ obj-$(CONFIG_GPIO_LP873X) += gpio-lp873x.o
9292
obj-$(CONFIG_GPIO_LP87565) += gpio-lp87565.o
9393
obj-$(CONFIG_GPIO_LPC18XX) += gpio-lpc18xx.o
9494
obj-$(CONFIG_GPIO_LPC32XX) += gpio-lpc32xx.o
95+
obj-$(CONFIG_GPIO_MACSMC) += gpio-macsmc.o
9596
obj-$(CONFIG_GPIO_MADERA) += gpio-madera.o
9697
obj-$(CONFIG_GPIO_MAX3191X) += gpio-max3191x.o
9798
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 <marcan@marcan.st>");
236+
MODULE_LICENSE("Dual MIT/GPL");
237+
MODULE_DESCRIPTION("Apple SMC GPIO driver");
238+
MODULE_ALIAS("platform:macsmc-gpio");

0 commit comments

Comments
 (0)