1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
|
// 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");
|