// 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 #include #include #include #include #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_rv = 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 "); MODULE_LICENSE("Dual MIT/GPL"); MODULE_DESCRIPTION("Apple SMC GPIO driver");