// 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 0 xff 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 (0 xff << 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 & 0 xff);
int high = hex_to_bin((key >> 8 ) & 0 xff);
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 = 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" );
Messung V0.5 in Prozent C=98 H=97 G=97
¤ Dauer der Verarbeitung: 0.10 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland