Re: [PATCH v2] gpio: winbond: add driver

From: Andy Shevchenko
Date: Thu Dec 28 2017 - 10:12:58 EST


On Fri, 2017-12-22 at 19:58 +0100, Maciej S. Szmigiero wrote:
> This commit adds GPIO driver for Winbond Super I/Os.
>
> Currently, only W83627UHG model (also known as Nuvoton NCT6627UD) is
> supported but in the future a support for other Winbond models, too,
> can
> be added to the driver.
>
> A module parameter "gpios" sets a bitmask of GPIO ports to enable (bit
> 0 is
> GPIO1, bit 1 is GPIO2, etc.).
> One should be careful which ports one tinkers with since some might be
> managed by the firmware (for functions like powering on and off,
> sleeping,
> BIOS recovery, etc.) and some of GPIO port pins are physically shared
> with
> other devices included in the Super I/O chip.

> +config GPIO_WINBOND
> + tristate "Winbond Super I/O GPIO support"
> + help
> + This option enables support for GPIOs found on Winbond
> Super I/O
> + chips.
> + Currently, only W83627UHG (also known as Nuvoton NCT6627UD)
> is
> + supported.
> +
> + You will need to provide a module parameter "gpios", or a
> + boot-time parameter "gpio_winbond.gpios" with a bitmask of
> GPIO
> + ports to enable (bit 0 is GPIO1, bit 1 is GPIO2, etc.).

1. Why it's required?
2. GPIO1 -> GPIO0, GPIO2 -> GPIO1, etc ?

> +
> + To compile this driver as a module, choose M here: the
> module will
> + be called gpio-winbond.
>

> +#include <linux/errno.h>
> +#include <linux/gpio.h>
> +#include <linux/ioport.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>

Perhaps, alphabetically ordered?

> +
> +#define WB_GPIO_DRIVER_NAME "gpio-winbond"
> +
> +#define WB_SIO_BASE 0x2e
> +#define WB_SIO_BASE_HIGH 0x4e

Is it my mail client or you didn't use TAB(s) as separator?

> +#define WB_SIO_CHIP_ID_W83627UHG 0xa230
> +#define WB_SIO_CHIP_ID_W83627UHG_MASK 0xfff0

GENMASK()

> +
> +/* not an actual device number, just a value meaning 'no device' */
> +#define WB_SIO_DEV_NONE 0xff



> +
> +#define WB_SIO_DEV_UARTB 3
> +#define WB_SIO_UARTB_REG_ENABLE 0x30
> +#define WB_SIO_UARTB_ENABLE_ON 0

What does it mean?

1. ???
2. Register offset
3. Bit to enable

?

> +
> +#define WB_SIO_DEV_UARTC 6
> +#define WB_SIO_UARTC_REG_ENABLE 0x30
> +#define WB_SIO_UARTC_ENABLE_ON 0
> +
> +#define WB_SIO_DEV_GPIO34 7
> +#define WB_SIO_GPIO34_REG_ENABLE 0x30

> +#define WB_SIO_GPIO34_ENABLE_4 1
> +#define WB_SIO_GPIO34_ENABLE_3 0

Perhaps swap them?

> +#define WB_SIO_GPIO34_REG_IO3 0xe0
> +#define WB_SIO_GPIO34_REG_DATA3 0xe1
> +#define WB_SIO_GPIO34_REG_INV3 0xe2
> +#define WB_SIO_GPIO34_REG_IO4 0xe4
> +#define WB_SIO_GPIO34_REG_DATA4 0xe5
> +#define WB_SIO_GPIO34_REG_INV4 0xe6
> +
> +#define WB_SIO_DEV_WDGPIO56 8

> +#define WB_SIO_WDGPIO56_REG_ENABLE 0x30

Why do we have duplication here?

> +#define WB_SIO_WDGPIO56_ENABLE_6 2
> +#define WB_SIO_WDGPIO56_ENABLE_5 1

Swap.

> +#define WB_SIO_WDGPIO56_REG_IO5 0xe0
> +#define WB_SIO_WDGPIO56_REG_DATA5 0xe1
> +#define WB_SIO_WDGPIO56_REG_INV5 0xe2
> +#define WB_SIO_WDGPIO56_REG_IO6 0xe4
> +#define WB_SIO_WDGPIO56_REG_DATA6 0xe5
> +#define WB_SIO_WDGPIO56_REG_INV6 0xe6

Duplication?

> +
> +#define WB_SIO_DEV_GPIO12 9
> +#define WB_SIO_GPIO12_REG_ENABLE 0x30
> +#define WB_SIO_GPIO12_ENABLE_2 1
> +#define WB_SIO_GPIO12_ENABLE_1 0
> +#define WB_SIO_GPIO12_REG_IO1 0xe0
> +#define WB_SIO_GPIO12_REG_DATA1 0xe1
> +#define WB_SIO_GPIO12_REG_INV1 0xe2
> +#define WB_SIO_GPIO12_REG_IO2 0xe4
> +#define WB_SIO_GPIO12_REG_DATA2 0xe5
> +#define WB_SIO_GPIO12_REG_INV2 0xe6
> +
> +#define WB_SIO_DEV_UARTD 0xd
> +#define WB_SIO_UARTD_REG_ENABLE 0x30
> +#define WB_SIO_UARTD_ENABLE_ON 0
> +
> +#define WB_SIO_DEV_UARTE 0xe
> +#define WB_SIO_UARTE_REG_ENABLE 0x30
> +#define WB_SIO_UARTE_ENABLE_ON 0
> +
> +#define WB_SIO_REG_LOGICAL 7
> +
> +#define WB_SIO_REG_CHIP_MSB 0x20
> +#define WB_SIO_REG_CHIP_LSB 0x21
> +
> +#define WB_SIO_REG_DPD 0x22
> +#define WB_SIO_REG_DPD_UARTA 4
> +#define WB_SIO_REG_DPD_UARTB 5
> +
> +#define WB_SIO_REG_IDPD 0x23
> +#define WB_SIO_REG_IDPD_UARTF 7
> +#define WB_SIO_REG_IDPD_UARTE 6
> +#define WB_SIO_REG_IDPD_UARTD 5
> +#define WB_SIO_REG_IDPD_UARTC 4
> +
> +#define WB_SIO_REG_GLOBAL_OPT 0x24
> +#define WB_SIO_REG_GO_ENFDC 1
> +
> +#define WB_SIO_REG_OVTGPIO3456 0x29
> +#define WB_SIO_REG_OG3456_G6PP 7
> +#define WB_SIO_REG_OG3456_G5PP 5
> +#define WB_SIO_REG_OG3456_G4PP 4
> +#define WB_SIO_REG_OG3456_G3PP 3
> +
> +#define WB_SIO_REG_I2C_PS 0x2A
> +#define WB_SIO_REG_I2CPS_I2CFS 1
> +
> +#define WB_SIO_REG_GPIO1_MF 0x2c
> +#define WB_SIO_REG_G1MF_G2PP 7
> +#define WB_SIO_REG_G1MF_G1PP 6
> +#define WB_SIO_REG_G1MF_FS 3
> +#define WB_SIO_REG_G1MF_FS_UARTB 3
> +#define WB_SIO_REG_G1MF_FS_GPIO1 2
> +#define WB_SIO_REG_G1MF_FS_IR 1
> +#define WB_SIO_REG_G1MF_FS_IR_OFF 0
> +

> +static u8 gpios;
> +static u8 ppgpios;
> +static u8 odgpios;
> +static bool pledgpio;
> +static bool beepgpio;
> +static bool i2cgpio;

Hmm... Too many global variables.

> +
> +static int winbond_sio_enter(u16 base)
> +{
> + if (request_muxed_region(base, 2, WB_GPIO_DRIVER_NAME) ==
> NULL) {

if (!request_...())

> + pr_err(WB_GPIO_DRIVER_NAME ": cannot enter SIO at
> address %x\n",
> + (unsigned int)base);

%x, %hx, %hhx. No explicit casting.

Moreover, please, use dev_err() instead or drop this message completely.

> + return -EBUSY;

> + }
> +

> + outb(WB_SIO_EXT_ENTER_KEY, base);
> + outb(WB_SIO_EXT_ENTER_KEY, base);

Comment why double write is needed.

> +
> + return 0;
> +}
> +
> +static void winbond_sio_select_logical(u16 base, u8 dev)
> +{
> + outb(WB_SIO_REG_LOGICAL, base);
> + outb(dev, base + 1);
> +}
> +
> +static void winbond_sio_leave(u16 base)
> +{
> + outb(WB_SIO_EXT_EXIT_KEY, base);
> +
> + release_region(base, 2);
> +}

> +static void winbond_sio_reg_write(u16 base, u8 reg, u8 data)
> +static u8 winbond_sio_reg_read(u16 base, u8 reg)
> +static void winbond_sio_reg_bset(u16 base, u8 reg, u8 bit)
> +static void winbond_sio_reg_bclear(u16 base, u8 reg, u8 bit)
> +static bool winbond_sio_reg_btest(u16 base, u8 reg, u8 bit)

regmap API?

> +static const struct winbond_gpio_info winbond_gpio_infos[6] = {

Certainly candidate for regmap API.

> + { /* 5 */
> + .dev = WB_SIO_DEV_WDGPIO56,
> + .enablereg = WB_SIO_WDGPIO56_REG_ENABLE,
> + .enablebit = WB_SIO_WDGPIO56_ENABLE_6,
> + .outputreg = WB_SIO_REG_OVTGPIO3456,
> + .outputppbit = WB_SIO_REG_OG3456_G6PP,
> + .ioreg = WB_SIO_WDGPIO56_REG_IO6,
> + .invreg = WB_SIO_WDGPIO56_REG_INV6,
> + .datareg = WB_SIO_WDGPIO56_REG_DATA6,
> + .conflict = {
> + .name = "FDC",
> + .dev = WB_SIO_DEV_NONE,
> + .testreg = WB_SIO_REG_GLOBAL_OPT,
> + .testbit = WB_SIO_REG_GO_ENFDC,
> + .warnonly = false
> + }
> + }
> +};
> +
> +/* returns whether changing a pin is allowed */
> +static bool winbond_gpio_get_info(unsigned int gpio_num,
> + const struct winbond_gpio_info
> **info)
> +{
> + bool allow_changing = true;
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(winbond_gpio_infos); i++) {
> + if (!(gpios & BIT(i)))
> + continue;

for_each_set_bit()

> +
> + if (gpio_num < 8)
> + break;
> +

> + gpio_num -= 8;

Yeah, consider to use % and / paired, see below.

> + }
> +
> + /*
> + * If for any reason we can't find this gpio number make sure
> we
> + * don't access the winbond_gpio_infos array beyond its
> bounds.
> + * Also, warn in this case, so we know something is seriously
> wrong.
> + */
> + if (WARN_ON(i >= ARRAY_SIZE(winbond_gpio_infos)))
> + i = 0;

Something is even more seriously wrong if you are going to mess with
GPIO 0.

You have to return an error here.

Although it should not happen at all, AFAIU.

> +
> + *info = &winbond_gpio_infos[i];
> +
> + /*
> + * GPIO2 (the second port) shares some pins with a basic PC
> + * functionality, which is very likely controlled by the
> firmware.
> + * Don't allow changing these pins by default.
> + */
> + if (i == 1) {
> + if (gpio_num == 0 && !pledgpio)
> + allow_changing = false;
> + else if (gpio_num == 1 && !beepgpio)
> + allow_changing = false;
> + else if ((gpio_num == 5 || gpio_num == 6) &&
> !i2cgpio)
> + allow_changing = false;

Instead of allow_changing perhaps you need to use gpio_valid_mask
(though it's not in upstream, yet? Linus?)

> + }
> +
> + return allow_changing;
> +}

> +static int winbond_gpio_direction_in(struct gpio_chip *gc,
> + unsigned int gpio_num)

It's not gpio_num, it's offset. That is how we usually refer to that
parameter in the drivers.

> +{
> + u16 *base = gpiochip_get_data(gc);
> + const struct winbond_gpio_info *info;
> + int ret;
> +
> + if (!winbond_gpio_get_info(gpio_num, &info))
> + return -EACCES;
> +
> + gpio_num %= 8;

Usually it goes paired with / 8 or alike.

Note, that
% followed by / makes *one* assembly command on some architectures.

Same comments to the rest of similar functions.

> +
> + ret = winbond_sio_enter(*base);
> + if (ret)
> + return ret;
> +
> + winbond_sio_select_logical(*base, info->dev);
> +
> + winbond_sio_reg_bset(*base, info->ioreg, gpio_num);
> +
> + winbond_sio_leave(*base);
> +
> + return 0;
> +}
> +
> +static int winbond_gpio_direction_out(struct gpio_chip *gc,
> + unsigned int gpio_num,
> + int val)
> +{
> + u16 *base = gpiochip_get_data(gc);

out*() and in*() take unsigned long. So should this driver provide.

> + return 0;
> +}
> +
> +static void winbond_gpio_set(struct gpio_chip *gc, unsigned int
> gpio_num,
> + int val)
> +{
> + u16 *base = gpiochip_get_data(gc);
> + const struct winbond_gpio_info *info;
> +
> + if (!winbond_gpio_get_info(gpio_num, &info))
> + return;
> +
> + gpio_num %= 8;
> +
> + if (winbond_sio_enter(*base) != 0)
> + return;
> +
> + winbond_sio_select_logical(*base, info->dev);
> +

> + if (winbond_sio_reg_btest(*base, info->invreg, gpio_num))
> + val = !val;
> +
> + if (val)

if (val ^ winbond_sio_reg_btest()) ?

> + winbond_sio_reg_bset(*base, info->datareg, gpio_num);
> + else
> + winbond_sio_reg_bclear(*base, info->datareg,
> gpio_num);

> +}
> +
> +static struct gpio_chip winbond_gpio_chip = {
> + .base = -1,
> + .label = WB_GPIO_DRIVER_NAME,

> + .owner = THIS_MODULE,



> + .can_sleep = true,
> + .get = winbond_gpio_get,
> + .direction_input = winbond_gpio_direction_in,
> + .set = winbond_gpio_set,
> + .direction_output = winbond_gpio_direction_out,
> +};
> +
> +static int winbond_gpio_probe(struct platform_device *pdev)
> +{
> + u16 *base = dev_get_platdata(&pdev->dev);
> + unsigned int i;
> +
> + if (base == NULL)
> + return -EINVAL;
> +
> + /*
> + * Add 8 gpios for every GPIO port that was enabled in gpios
> + * module parameter (that wasn't disabled earlier in
> + * winbond_gpio_configure() & co. due to, for example, a pin
> conflict).
> + */
> + winbond_gpio_chip.ngpio = 0;

> + for (i = 0; i < 5; i++)

5 is a magic.

> + if (gpios & BIT(i))
> + winbond_gpio_chip.ngpio += 8;

for_each_set_bit()

> +
> + if (gpios & BIT(5))
> + winbond_gpio_chip.ngpio += 5;

Comment needed for this one.

> +
> + winbond_gpio_chip.parent = &pdev->dev;
> +
> + return devm_gpiochip_add_data(&pdev->dev, &winbond_gpio_chip,
> base);
> +}
> +
> +static void winbond_gpio_configure_port0_pins(u16 base)
> +{
> + u8 val;
> +
> + val = winbond_sio_reg_read(base, WB_SIO_REG_GPIO1_MF);
> + if ((val & WB_SIO_REG_G1MF_FS) == WB_SIO_REG_G1MF_FS_GPIO1)
> + return;
> +
> + pr_warn(WB_GPIO_DRIVER_NAME
> + ": GPIO1 pins were connected to something else
> (%.2x), fixing\n",
> + (unsigned int)val);
> +
> + val &= ~WB_SIO_REG_G1MF_FS;
> + val |= WB_SIO_REG_G1MF_FS_GPIO1;
> +
> + winbond_sio_reg_write(base, WB_SIO_REG_GPIO1_MF, val);
> +}
> +
> +static void winbond_gpio_configure_port1_check_i2c(u16 base)
> +{
> + i2cgpio = !winbond_sio_reg_btest(base, WB_SIO_REG_I2C_PS,
> + WB_SIO_REG_I2CPS_I2CFS);
> + if (!i2cgpio)
> + pr_warn(WB_GPIO_DRIVER_NAME
> + ": disabling GPIO2.5 and GPIO2.6 as I2C is
> enabled\n");
> +}
> +
> +static bool winbond_gpio_configure_port(u16 base, unsigned int idx)
> +{
> + const struct winbond_gpio_info *info =
> &winbond_gpio_infos[idx];
> + const struct winbond_gpio_port_conflict *conflict = &info-
> >conflict;
> +
> + /* is there a possible conflicting device defined? */
> + if (conflict->name != NULL) {
> + if (conflict->dev != WB_SIO_DEV_NONE)
> + winbond_sio_select_logical(base, conflict-
> >dev);
> +
> + if (winbond_sio_reg_btest(base, conflict->testreg,
> + conflict->testbit)) {
> + if (conflict->warnonly)
> + pr_warn(WB_GPIO_DRIVER_NAME
> + ": enabled GPIO%u share pins
> with active %s\n",
> + idx + 1, conflict->name);
> + else {
> + pr_warn(WB_GPIO_DRIVER_NAME
> + ": disabling GPIO%u as %s is
> enabled\n",
> + idx + 1, conflict->name);
> + return false;
> + }
> + }
> + }
> +
> + /* GPIO1 and GPIO2 need some (additional) special handling */
> + if (idx == 0)
> + winbond_gpio_configure_port0_pins(base);
> + else if (idx == 1)
> + winbond_gpio_configure_port1_check_i2c(base);
> +
> + winbond_sio_select_logical(base, info->dev);
> +
> + winbond_sio_reg_bset(base, info->enablereg, info->enablebit);
> +
> + if (ppgpios & BIT(idx))
> + winbond_sio_reg_bset(base, info->outputreg,
> + info->outputppbit);
> + else if (odgpios & BIT(idx))
> + winbond_sio_reg_bclear(base, info->outputreg,
> + info->outputppbit);
> + else
> + pr_notice(WB_GPIO_DRIVER_NAME ": GPIO%u pins are
> %s\n", idx + 1,
> + winbond_sio_reg_btest(base, info-
> >outputreg,
> + info->outputppbit) ?
> + "push-pull" :
> + "open drain");
> +
> + return true;
> +}
> +
> +static int winbond_gpio_configure(u16 base)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(winbond_gpio_infos); i++) {
> + if (!(gpios & BIT(i)))
> + continue;
> +
> + if (!winbond_gpio_configure_port(base, i))
> + gpios &= ~BIT(i);
> + }
> +
> + if (!(gpios & GENMASK(ARRAY_SIZE(winbond_gpio_infos) - 1,
> 0))) {
> + pr_err(WB_GPIO_DRIVER_NAME
> + ": please use 'gpios' module parameter to
> select some active GPIO ports to enable\n");
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static struct platform_device *winbond_gpio_pdev;
> +
> +/* probes chip at provided I/O base address, initializes and
> registers it */
> +static int winbond_gpio_try_probe_init(u16 base)

No.

Introduce

struct winbond_sio_device {
struct device *dev;
unsigned long base;
};


Use it everywhere, including driver data.

> +{
> + u16 chip;
> + int ret;
> +
> + ret = winbond_sio_enter(base);
> + if (ret)
> + return ret;
> +
> + chip = winbond_sio_reg_read(base, WB_SIO_REG_CHIP_MSB) << 8;
> + chip |= winbond_sio_reg_read(base, WB_SIO_REG_CHIP_LSB);
> +
> + pr_notice(WB_GPIO_DRIVER_NAME
> + ": chip ID at %hx is %.4x\n",
> + (unsigned int)base,
> + (unsigned int)chip);

No explicit casting.

If you do such, you need to think twice what you *do wrong*.

There are really rare cases when it's needed.

> +
> + if ((chip & WB_SIO_CHIP_ID_W83627UHG_MASK) !=
> + WB_SIO_CHIP_ID_W83627UHG) {
> + pr_err(WB_GPIO_DRIVER_NAME
> + ": not an our chip\n");
> + winbond_sio_leave(base);
> + return -ENODEV;
> + }
> +
> + ret = winbond_gpio_configure(base);
> +
> + winbond_sio_leave(base);
> +
> + if (ret)
> + return ret;
> +
> + winbond_gpio_pdev =
> platform_device_alloc(WB_GPIO_DRIVER_NAME, -1);
> + if (winbond_gpio_pdev == NULL)
> + return -ENOMEM;
> +
> + ret = platform_device_add_data(winbond_gpio_pdev,
> + &base, sizeof(base));
> + if (ret) {
> + pr_err(WB_GPIO_DRIVER_NAME
> + ": cannot add platform data\n");
> + goto ret_put;
> + }
> +
> + ret = platform_device_add(winbond_gpio_pdev);
> + if (ret) {
> + pr_err(WB_GPIO_DRIVER_NAME
> + ": cannot add platform device\n");
> + goto ret_put;
> + }
> +
> + return 0;
> +
> +ret_put:
> + platform_device_put(winbond_gpio_pdev);

> + winbond_gpio_pdev = NULL;

???

> +
> + return ret;
> +}
>

> +static int __init winbond_gpio_mod_init(void)
> +{
> + int ret;
> +
> + if (ppgpios & odgpios) {
> + pr_err(WB_GPIO_DRIVER_NAME

#define pr_fmt

> + ": some GPIO ports are set both to push-pull
> and open drain mode at the same time\n");
> + return -EINVAL;
> + }
> +
> + ret = platform_driver_register(&winbond_gpio_pdriver);
> + if (ret)
> + return ret;
> +
> + ret = winbond_gpio_try_probe_init(WB_SIO_BASE);
> + if (ret == -ENODEV || ret == -EBUSY)
> + ret = winbond_gpio_try_probe_init(WB_SIO_BASE_HIGH);
> + if (ret)
> + goto ret_unreg;
> +
> + return 0;
> +
> +ret_unreg:
> + platform_driver_unregister(&winbond_gpio_pdriver);
> +
> + return ret;

Oy vey, is it really right place to do this?

> +}
> +
> +static void __exit winbond_gpio_mod_exit(void)
> +{

> + platform_device_unregister(winbond_gpio_pdev);
> + platform_driver_unregister(&winbond_gpio_pdriver);

Hmm... what?

> +}
> +
> +module_init(winbond_gpio_mod_init);
> +module_exit(winbond_gpio_mod_exit);
>

> +MODULE_AUTHOR("Maciej S. Szmigiero <mail@xxxxxxxxxxxxxxxxxxxxx>");
> +MODULE_DESCRIPTION("GPIO interface for Winbond Super I/O chips");

> +MODULE_LICENSE("GPL");

Does it match SPDX identifier?

--
Andy Shevchenko <andriy.shevchenko@xxxxxxxxxxxxxxx>
Intel Finland Oy