293 lines
6.8 KiB
C
293 lines
6.8 KiB
C
// 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");
|