blob: 30ef258e76553c5fc446049e47f7ac510b3a1db1 [file] [log] [blame] [edit]
// 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 0xff 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 (0xff << 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 & 0xff);
int high = hex_to_bin((key >> 8) & 0xff);
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");