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 -- To unsubscribe from this list: send the line "unsubscribe linux-gpio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html