[PATCH 5/6] gpio: Add new gpio-macsmc driver for Apple Macs
Andy Shevchenko
andy.shevchenko at gmail.com
Thu Sep 1 11:55:23 PDT 2022
On Thu, Sep 1, 2022 at 5:17 PM Russell King <rmk+kernel at armlinux.org.uk> wrote:
>
> From: Hector Martin <marcan at marcan.st>
>
> This driver implements the GPIO service on top of the SMC framework
> on Apple Mac machines. In particular, these are the GPIOs present in the
> PMU IC which are used to control power to certain on-board devices.
>
> Although the underlying hardware supports various pin config settings
> (input/output, open drain, etc.), this driver does not implement that
> functionality and leaves it up to the firmware to configure things
> properly. We also don't yet support interrupts/events. This is
> sufficient for device power control, which is the only thing we need to
> support at this point. More features will be implemented when needed.
>
> To our knowledge, only Apple Silicon Macs implement this SMC feature.
...
> +/*
> + * 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.
Probably for debugging purposes...
> + *
> + * We're going to try to make do with the low commands for now.
> + * We don't implement pin mode changes at this time.
> + */
...
> +/*
> + * output modes seem to differ depending on the PMU in use... ?
Output
> + * 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;
You might save some CPU cycles / code by shuffling members around.
Usually we put gpio_chip as a first one, so pointer arithmetics to get
it becomes a bit simpler, but it needs to be checked by the tool and
also paying attention to what is used in critical paths (so
performance-wise).
> + 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);
> +}
NIH hex2bin().
> +static int macsmc_gpio_key(unsigned int offset)
> +{
> + return _SMC_KEY("gP\0\0") | (hex_asc_hi(offset) << 8) | hex_asc_lo(offset);
> +}
NIH hex_byte_pack().
...
> + /* 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_IN : GPIO_LINE_DIRECTION_OUT;
What is the meaning of val in this case?
...
> + if (ret == GPIO_LINE_DIRECTION_OUT)
> + ret = apple_smc_rw_u32(smcgp->smc, key, CMD_OUTPUT, &val);
> + else
> + ret = apple_smc_rw_u32(smcgp->smc, key, CMD_INPUT, &val);
> +
Unnecessary blank line.
> + if (ret < 0)
> + return ret;
...
> + 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);
Strange specifier... It seems like a hashed pointer with added (or
skipped? I don't remember) '4ch'. Perhaps you meant one of '%pE',
'%p4cc'?
Ditto for other cases.
...
> + struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
> + int count = apple_smc_get_key_count(smcgp->smc) - smcgp->first_index;
I would split this assignment and move it closer to the first user.
> + int i;
> +
> + if (count > MAX_GPIO)
> + count = MAX_GPIO;
Hmm... When can it be the case?
> + bitmap_zero(valid_mask, ngpios);
> +
> + for (i = 0; i < count; i++) {
> + smc_key key;
> + int gpio_nr;
> + int ret = apple_smc_get_key_by_index(smcgp->smc, smcgp->first_index + i, &key);
Ditto.
> +
> + 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);
Yeah, according to the code it will print something you didn't want.
> + continue;
> + }
> +
> + set_bit(gpio_nr, valid_mask);
> + }
> +
> + return 0;
> +}
...
> + pdev->dev.of_node = of_get_child_by_name(pdev->dev.parent->of_node, "gpio");
Can we use fwnode APIs instead?
Or do you really need this?
--
With Best Regards,
Andy Shevchenko
More information about the linux-arm-kernel
mailing list