diff mbox series

[v2] gpio: winbond: add driver

Message ID 309acd17-5244-da8c-a28e-dace15ada4fb@maciej.szmigiero.name
State New
Headers show
Series [v2] gpio: winbond: add driver | expand

Commit Message

Maciej S. Szmigiero Dec. 22, 2017, 6:58 p.m. UTC
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.

Signed-off-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
---
Changes from v1:
* Added SPDX license tag,

* Removed gpiobase parameter,

* Changed uint{8,16}_t types to u{8,16},

* Added kerneldoc descriptions of driver structures,

* Reformatted winbond_gpio_infos array fields so they are on separate
lines,

* Added few comments here and there as requested,

* Moved port configuration code from separate winbond_gpio_configure_X()
functions to one, common, parametrized winbond_gpio_configure_port()
function.

Didn't change "linux/errno.h" and "linux/gpio.h" includes to
"linux/driver.h" since there is no such file in the current linux-gpio
tree and so the driver would not compile with this change.
Other GPIO drivers are using these former two include files, too.

 drivers/gpio/Kconfig        |  15 +
 drivers/gpio/Makefile       |   1 +
 drivers/gpio/gpio-winbond.c | 758 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 774 insertions(+)
 create mode 100644 drivers/gpio/gpio-winbond.c

--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

William Breathitt Gray Dec. 24, 2017, 10:42 p.m. UTC | #1
On Fri, Dec 22, 2017 at 07:58:49PM +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.
>
>Signed-off-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
>---
>Changes from v1:
>* Added SPDX license tag,
>
>* Removed gpiobase parameter,
>
>* Changed uint{8,16}_t types to u{8,16},
>
>* Added kerneldoc descriptions of driver structures,
>
>* Reformatted winbond_gpio_infos array fields so they are on separate
>lines,
>
>* Added few comments here and there as requested,
>
>* Moved port configuration code from separate winbond_gpio_configure_X()
>functions to one, common, parametrized winbond_gpio_configure_port()
>function.
>
>Didn't change "linux/errno.h" and "linux/gpio.h" includes to
>"linux/driver.h" since there is no such file in the current linux-gpio
>tree and so the driver would not compile with this change.
>Other GPIO drivers are using these former two include files, too.

Hi Maciej,

Sorry for the late response; it looks like you already made it to
version 2 of this patch from Linus' initial review. I'll leave the GPIO
subsystem related code to him, and stick to the ISA bus style LPC
interface communication in my review. ;)

First a quick clarification: I suspect Linus meant to suggest

    +#include <linux/gpio/driver.h>

as an alternative to the "linux/errno.h" and "linux/gpio.h" includes.

>
> drivers/gpio/Kconfig        |  15 +
> drivers/gpio/Makefile       |   1 +
> drivers/gpio/gpio-winbond.c | 758 ++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 774 insertions(+)
> create mode 100644 drivers/gpio/gpio-winbond.c
>
>diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
>index 395669bfcc26..3384a4675a0c 100644
>--- a/drivers/gpio/Kconfig
>+++ b/drivers/gpio/Kconfig
>@@ -698,6 +698,21 @@ config GPIO_TS5500
> 	  blocks of the TS-5500: DIO1, DIO2 and the LCD port, and the TS-5600
> 	  LCD port.
> 
>+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.).
>+
>+	  To compile this driver as a module, choose M here: the module will
>+	  be called gpio-winbond.
>+
> config GPIO_WS16C48
> 	tristate "WinSystems WS16C48 GPIO support"
> 	depends on ISA_BUS_API
>diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
>index bc5dd673fa11..ff3d36d0a443 100644
>--- a/drivers/gpio/Makefile
>+++ b/drivers/gpio/Makefile
>@@ -139,6 +139,7 @@ obj-$(CONFIG_GPIO_VIPERBOARD)	+= gpio-viperboard.o
> obj-$(CONFIG_GPIO_VR41XX)	+= gpio-vr41xx.o
> obj-$(CONFIG_GPIO_VX855)	+= gpio-vx855.o
> obj-$(CONFIG_GPIO_WHISKEY_COVE)	+= gpio-wcove.o
>+obj-$(CONFIG_GPIO_WINBOND)	+= gpio-winbond.o
> obj-$(CONFIG_GPIO_WM831X)	+= gpio-wm831x.o
> obj-$(CONFIG_GPIO_WM8350)	+= gpio-wm8350.o
> obj-$(CONFIG_GPIO_WM8994)	+= gpio-wm8994.o
>diff --git a/drivers/gpio/gpio-winbond.c b/drivers/gpio/gpio-winbond.c
>new file mode 100644
>index 000000000000..385855fb6c9e
>--- /dev/null
>+++ b/drivers/gpio/gpio-winbond.c
>@@ -0,0 +1,758 @@
>+// SPDX-License-Identifier: GPL-2.0+
>+/*
>+ * GPIO interface for Winbond Super I/O chips
>+ * Currently, only W83627UHG (Nuvoton NCT6627UD) is supported.
>+ *
>+ * Author: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
>+ */
>+
>+#include <linux/errno.h>
>+#include <linux/gpio.h>
>+#include <linux/ioport.h>
>+#include <linux/module.h>
>+#include <linux/platform_device.h>

I suggest taking a look at the "linux/isa.h" file. For ISA-style
communication such as you would have for LPC interface device, the ISA
subsystem can be more practical to utilize than creating a platform
device.

The 104-IDIO-16 GPIO driver can serve as a good template for ISA-style
drivers; you can find it at drivers/gpio/gpio-104-idio-16.c

>+
>+#define WB_GPIO_DRIVER_NAME "gpio-winbond"
>+
>+#define WB_SIO_BASE 0x2e
>+#define WB_SIO_BASE_HIGH 0x4e
>+
>+#define WB_SIO_EXT_ENTER_KEY 0x87
>+#define WB_SIO_EXT_EXIT_KEY 0xaa
>+
>+#define WB_SIO_CHIP_ID_W83627UHG 0xa230
>+#define WB_SIO_CHIP_ID_W83627UHG_MASK 0xfff0
>+
>+/* 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
>+
>+#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
>+#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
>+#define WB_SIO_WDGPIO56_ENABLE_6 2
>+#define WB_SIO_WDGPIO56_ENABLE_5 1
>+#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
>+
>+#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;
>+
>+static int winbond_sio_enter(u16 base)
>+{
>+	if (request_muxed_region(base, 2, WB_GPIO_DRIVER_NAME) == NULL) {
>+		pr_err(WB_GPIO_DRIVER_NAME ": cannot enter SIO at address %x\n",
>+		       (unsigned int)base);
>+		return -EBUSY;
>+	}
>+
>+	outb(WB_SIO_EXT_ENTER_KEY, base);
>+	outb(WB_SIO_EXT_ENTER_KEY, base);
>+
>+	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)
>+{
>+	outb(reg, base);
>+	outb(data, base + 1);
>+}
>+
>+static u8 winbond_sio_reg_read(u16 base, u8 reg)
>+{
>+	outb(reg, base);
>+	return inb(base + 1);
>+}
>+
>+static void winbond_sio_reg_bset(u16 base, u8 reg, u8 bit)
>+{
>+	u8 val;
>+
>+	val = winbond_sio_reg_read(base, reg);
>+	val |= BIT(bit);
>+	winbond_sio_reg_write(base, reg, val);
>+}
>+
>+static void winbond_sio_reg_bclear(u16 base, u8 reg, u8 bit)
>+{
>+	u8 val;
>+
>+	val = winbond_sio_reg_read(base, reg);
>+	val &= ~BIT(bit);
>+	winbond_sio_reg_write(base, reg, val);
>+}
>+
>+static bool winbond_sio_reg_btest(u16 base, u8 reg, u8 bit)
>+{
>+	return winbond_sio_reg_read(base, reg) & BIT(bit);
>+}
>+
>+/**
>+ * struct winbond_gpio_port_conflict - possibly conflicting device information
>+ * @name:	device name (NULL means no conflicting device defined)
>+ * @dev:	Super I/O logical device number where the testreg register
>+ *		is located (or WB_SIO_DEV_NONE - don't select any
>+ *		logical device)
>+ * @testreg:	register number where the testbit bit is located
>+ * @testbit:	index of a bit to check whether an actual conflict exists
>+ * @warnonly:	if set then a conflict isn't fatal (just warn about it),
>+ *		otherwise disable the particular GPIO port if a conflict
>+ *		is detected
>+ */
>+struct winbond_gpio_port_conflict {
>+	const char *name;
>+	u8 dev;
>+	u8 testreg;
>+	u8 testbit;
>+	bool warnonly;
>+};
>+
>+/**
>+ * struct winbond_gpio_info - information about a particular GPIO port (device)
>+ * @dev:		Super I/O logical device number of the registers
>+ *			specified below
>+ * @enablereg:		port enable bit register number
>+ * @enablebit:		index of a port enable bit
>+ * @outputreg:		output driver mode bit register number
>+ * @outputppbit:	index of a push-pull output driver mode bit
>+ * @ioreg:		data direction register number
>+ * @invreg:		pin data inversion register number
>+ * @datareg:		pin data register number
>+ * @conflict:		description of a device that possibly conflicts with
>+ *			this port
>+ */
>+struct winbond_gpio_info {
>+	u8 dev;
>+	u8 enablereg;
>+	u8 enablebit;
>+	u8 outputreg;
>+	u8 outputppbit;
>+	u8 ioreg;
>+	u8 invreg;
>+	u8 datareg;
>+	struct winbond_gpio_port_conflict conflict;
>+};
>+
>+static const struct winbond_gpio_info winbond_gpio_infos[6] = {
>+	{ /* 0 */
>+		.dev = WB_SIO_DEV_GPIO12,
>+		.enablereg = WB_SIO_GPIO12_REG_ENABLE,
>+		.enablebit = WB_SIO_GPIO12_ENABLE_1,
>+		.outputreg = WB_SIO_REG_GPIO1_MF,
>+		.outputppbit = WB_SIO_REG_G1MF_G1PP,
>+		.ioreg = WB_SIO_GPIO12_REG_IO1,
>+		.invreg = WB_SIO_GPIO12_REG_INV1,
>+		.datareg = WB_SIO_GPIO12_REG_DATA1,
>+		.conflict = {
>+			.name = "UARTB",
>+			.dev = WB_SIO_DEV_UARTB,
>+			.testreg = WB_SIO_UARTB_REG_ENABLE,
>+			.testbit = WB_SIO_UARTB_ENABLE_ON,
>+			.warnonly = true
>+		}
>+	},
>+	{ /* 1 */
>+		.dev = WB_SIO_DEV_GPIO12,
>+		.enablereg = WB_SIO_GPIO12_REG_ENABLE,
>+		.enablebit = WB_SIO_GPIO12_ENABLE_2,
>+		.outputreg = WB_SIO_REG_GPIO1_MF,
>+		.outputppbit = WB_SIO_REG_G1MF_G2PP,
>+		.ioreg = WB_SIO_GPIO12_REG_IO2,
>+		.invreg = WB_SIO_GPIO12_REG_INV2,
>+		.datareg = WB_SIO_GPIO12_REG_DATA2
>+		/* special conflict handling so doesn't use conflict data */
>+	},
>+	{ /* 2 */
>+		.dev = WB_SIO_DEV_GPIO34,
>+		.enablereg = WB_SIO_GPIO34_REG_ENABLE,
>+		.enablebit = WB_SIO_GPIO34_ENABLE_3,
>+		.outputreg = WB_SIO_REG_OVTGPIO3456,
>+		.outputppbit = WB_SIO_REG_OG3456_G3PP,
>+		.ioreg = WB_SIO_GPIO34_REG_IO3,
>+		.invreg = WB_SIO_GPIO34_REG_INV3,
>+		.datareg = WB_SIO_GPIO34_REG_DATA3,
>+		.conflict = {
>+			.name = "UARTC",
>+			.dev = WB_SIO_DEV_UARTC,
>+			.testreg = WB_SIO_UARTC_REG_ENABLE,
>+			.testbit = WB_SIO_UARTC_ENABLE_ON,
>+			.warnonly = true
>+		}
>+	},
>+	{ /* 3 */
>+		.dev = WB_SIO_DEV_GPIO34,
>+		.enablereg = WB_SIO_GPIO34_REG_ENABLE,
>+		.enablebit = WB_SIO_GPIO34_ENABLE_4,
>+		.outputreg = WB_SIO_REG_OVTGPIO3456,
>+		.outputppbit = WB_SIO_REG_OG3456_G4PP,
>+		.ioreg = WB_SIO_GPIO34_REG_IO4,
>+		.invreg = WB_SIO_GPIO34_REG_INV4,
>+		.datareg = WB_SIO_GPIO34_REG_DATA4,
>+		.conflict = {
>+			.name = "UARTD",
>+			.dev = WB_SIO_DEV_UARTD,
>+			.testreg = WB_SIO_UARTD_REG_ENABLE,
>+			.testbit = WB_SIO_UARTD_ENABLE_ON,
>+			.warnonly = true
>+		}
>+	},
>+	{ /* 4 */
>+		.dev = WB_SIO_DEV_WDGPIO56,
>+		.enablereg = WB_SIO_WDGPIO56_REG_ENABLE,
>+		.enablebit = WB_SIO_WDGPIO56_ENABLE_5,
>+		.outputreg = WB_SIO_REG_OVTGPIO3456,
>+		.outputppbit = WB_SIO_REG_OG3456_G5PP,
>+		.ioreg = WB_SIO_WDGPIO56_REG_IO5,
>+		.invreg = WB_SIO_WDGPIO56_REG_INV5,
>+		.datareg = WB_SIO_WDGPIO56_REG_DATA5,
>+		.conflict = {
>+			.name = "UARTE",
>+			.dev = WB_SIO_DEV_UARTE,
>+			.testreg = WB_SIO_UARTE_REG_ENABLE,
>+			.testbit = WB_SIO_UARTE_ENABLE_ON,
>+			.warnonly = true
>+		}
>+	},
>+	{ /* 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;
>+
>+		if (gpio_num < 8)
>+			break;
>+
>+		gpio_num -= 8;
>+	}
>+
>+	/*
>+	 * 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;
>+
>+	*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;
>+	}
>+
>+	return allow_changing;
>+}
>+
>+static int winbond_gpio_get(struct gpio_chip *gc, unsigned int gpio_num)
>+{
>+	u16 *base = gpiochip_get_data(gc);
>+	const struct winbond_gpio_info *info;
>+	bool val;
>+
>+	winbond_gpio_get_info(gpio_num, &info);
>+	gpio_num %= 8;
>+
>+	val = winbond_sio_enter(*base);
>+	if (val)
>+		return val;
>+
>+	winbond_sio_select_logical(*base, info->dev);
>+
>+	val = winbond_sio_reg_btest(*base, info->datareg, gpio_num);
>+	if (winbond_sio_reg_btest(*base, info->invreg, gpio_num))
>+		val = !val;
>+
>+	winbond_sio_leave(*base);
>+
>+	return val;
>+}
>+
>+static int winbond_gpio_direction_in(struct gpio_chip *gc,
>+				     unsigned int gpio_num)
>+{
>+	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;
>+
>+	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);
>+	const struct winbond_gpio_info *info;
>+	int ret;
>+
>+	if (!winbond_gpio_get_info(gpio_num, &info))
>+		return -EACCES;
>+
>+	gpio_num %= 8;
>+
>+	ret = winbond_sio_enter(*base);
>+	if (ret)
>+		return ret;
>+
>+	winbond_sio_select_logical(*base, info->dev);
>+
>+	winbond_sio_reg_bclear(*base, info->ioreg, gpio_num);
>+	if (winbond_sio_reg_btest(*base, info->invreg, gpio_num))
>+		val = !val;
>+
>+	if (val)
>+		winbond_sio_reg_bset(*base, info->datareg, gpio_num);
>+	else
>+		winbond_sio_reg_bclear(*base, info->datareg, gpio_num);
>+
>+	winbond_sio_leave(*base);
>+
>+	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)
>+		winbond_sio_reg_bset(*base, info->datareg, gpio_num);
>+	else
>+		winbond_sio_reg_bclear(*base, info->datareg, gpio_num);
>+
>+	winbond_sio_leave(*base);
>+}
>+
>+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++)
>+		if (gpios & BIT(i))
>+			winbond_gpio_chip.ngpio += 8;
>+
>+	if (gpios & BIT(5))
>+		winbond_gpio_chip.ngpio += 5;
>+
>+	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)
>+{
>+	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);
>+
>+	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;
>+	}

These platform_device functions can all go away if you take advantage of
the ISA subsystem; the ISA driver handles multiple device allocations
for you, and feeds each new device structure to the registered probe
callback you set.

By the way Linus, this is the ISA bus equivalent of an "autodetect" you
were hoping for in your version 1 responses. It is not a true autodetect
in the sense that hardware does not determine the device, but rather the
ISA subsystem fakes detection of the devices to feed to the probe
callback so that the subsequent driver code fits the device driver model
closer to how other subsystems expect it; thus the difference between an
ISA and PCI device are abstracted away by their respective ISA and PCI
bus drivers.

>+
>+	return 0;
>+
>+ret_put:
>+	platform_device_put(winbond_gpio_pdev);
>+	winbond_gpio_pdev = NULL;
>+
>+	return ret;
>+}
>+
>+static struct platform_driver winbond_gpio_pdriver = {
>+	.driver = {
>+		.name	= WB_GPIO_DRIVER_NAME,
>+	},
>+	.probe		= winbond_gpio_probe,
>+};

Reimplement this as a struct isa_driver with respective probe callback.

>+
>+static int __init winbond_gpio_mod_init(void)
>+{
>+	int ret;
>+
>+	if (ppgpios & odgpios) {
>+		pr_err(WB_GPIO_DRIVER_NAME
>+			": 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;
>+}
>+
>+static void __exit winbond_gpio_mod_exit(void)
>+{
>+	platform_device_unregister(winbond_gpio_pdev);
>+	platform_driver_unregister(&winbond_gpio_pdriver);
>+}
>+
>+module_init(winbond_gpio_mod_init);
>+module_exit(winbond_gpio_mod_exit);

The winbond_gpio_mod_init and winbond_gpio_mod_exit functions can be
entirely removed as the ISA bus driver handles this for you. Replace the
module_init and module_exit macros with simply a module_isa_driver
macro.

The winbond_gpio_try_probe_init function call should now be moved to the
probe callback.

I see that you have a hard-coded WB_SIO_BASE I/O port address. Rather, I
would suggest implementing a "base" module parameter array similar to
the 104-IDIO-16 driver; this will match the current convention used by
ISA-style drivers (I know in this case we only have one device, but it's
good to follow convention for other users familar with the ISA
subsystem).

For reference, when I want to load the 104-IDIO-16 GPIO driver to handle
2 distinct 104-idio-16 devices (one located at 0x200 and the other at
0x300) on my system, I do something like the following:

    # modprobe gpio-104-idio-16 base=0x200,0x300 irq=3,5

Your Winbond driver will only have the "base" module parameter, but you
get the idea: the user can pass in the base address for each specific
device.

By the way, don't hesitate to ask for more information on the ISA
subsystem -- a lot of maintainers are unaware that it even exists since
so few devices nowadays use ISA-style communication -- but I'm always
happy to help. :)

William Breathitt Gray

>+
>+/* This parameter sets which GPIO devices (ports) we enable */
>+module_param(gpios, byte, 0444);
>+MODULE_PARM_DESC(gpios,
>+		 "bitmask of GPIO ports to enable (bit 0 - GPIO1, bit 1 - GPIO2, etc.");
>+
>+/*
>+ * These two parameters below set how we configure GPIO ports output drivers.
>+ * It can't be a one bitmask since we need three values per port: push-pull,
>+ * open-drain and keep as-is (this is the default).
>+ */
>+module_param(ppgpios, byte, 0444);
>+MODULE_PARM_DESC(ppgpios,
>+		 "bitmask of GPIO ports to set to push-pull mode (bit 0 - GPIO1, bit 1 - GPIO2, etc.");
>+
>+module_param(odgpios, byte, 0444);
>+MODULE_PARM_DESC(odgpios,
>+		 "bitmask of GPIO ports to set to open drain mode (bit 0 - GPIO1, bit 1 - GPIO2, etc.");
>+
>+/*
>+ * GPIO2.0 and GPIO2.1 control a basic PC functionality that we
>+ * don't allow tinkering with by default (it is very likely that the
>+ * firmware owns these pins).
>+ * These two parameters below allow overriding these prohibitions.
>+ */
>+module_param(pledgpio, bool, 0644);
>+MODULE_PARM_DESC(pledgpio,
>+		 "enable changing value of GPIO2.0 bit (Power LED), default no.");
>+
>+module_param(beepgpio, bool, 0644);
>+MODULE_PARM_DESC(beepgpio,
>+		 "enable changing value of GPIO2.1 bit (BEEP), default no.");
>+
>+MODULE_AUTHOR("Maciej S. Szmigiero <mail@maciej.szmigiero.name>");
>+MODULE_DESCRIPTION("GPIO interface for Winbond Super I/O chips");
>+MODULE_LICENSE("GPL");
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Maciej S. Szmigiero Dec. 25, 2017, midnight UTC | #2
On 24.12.2017 23:42, William Breathitt Gray wrote:
> On Fri, Dec 22, 2017 at 07:58:49PM +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.
>>
>> Signed-off-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
>> ---
>> Changes from v1:
(..)
>>
>> Didn't change "linux/errno.h" and "linux/gpio.h" includes to
>> "linux/driver.h" since there is no such file in the current linux-gpio
>> tree and so the driver would not compile with this change.
>> Other GPIO drivers are using these former two include files, too.

Hi William,

> 
> Hi Maciej,
> 
> Sorry for the late response; it looks like you already made it to
> version 2 of this patch from Linus' initial review. I'll leave the GPIO
> subsystem related code to him, and stick to the ISA bus style LPC
> interface communication in my review. ;)
> 
> First a quick clarification: I suspect Linus meant to suggest
> 
>     +#include <linux/gpio/driver.h>
> 
> as an alternative to the "linux/errno.h" and "linux/gpio.h" includes.

Thanks for your overall input and clarification, will change these
headers to "linux/gpio/driver.h" in a respin.

(..)
>> diff --git a/drivers/gpio/gpio-winbond.c b/drivers/gpio/gpio-winbond.c
>> new file mode 100644
>> index 000000000000..385855fb6c9e
>> --- /dev/null
>> +++ b/drivers/gpio/gpio-winbond.c
>> @@ -0,0 +1,758 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * GPIO interface for Winbond Super I/O chips
>> + * Currently, only W83627UHG (Nuvoton NCT6627UD) is supported.
>> + *
>> + * Author: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
>> + */
>> +
>> +#include <linux/errno.h>
>> +#include <linux/gpio.h>
>> +#include <linux/ioport.h>
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
> 
> I suggest taking a look at the "linux/isa.h" file. For ISA-style
> communication such as you would have for LPC interface device, the ISA
> subsystem can be more practical to utilize than creating a platform
> device.
> 
> The 104-IDIO-16 GPIO driver can serve as a good template for ISA-style
> drivers; you can find it at drivers/gpio/gpio-104-idio-16.c

OK, will convert the driver to use the ISA instead of platform bus.

(..)
>> +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)
>> +{
>> +	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);
>> +
>> +	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;
>> +	}
> 
> These platform_device functions can all go away if you take advantage of
> the ISA subsystem; the ISA driver handles multiple device allocations
> for you, and feeds each new device structure to the registered probe
> callback you set.

OK, I see (although the driver supports just one chip per system since
motherboards don't have multiple Super I/O chips).

> 
> By the way Linus, this is the ISA bus equivalent of an "autodetect" you
> were hoping for in your version 1 responses. It is not a true autodetect
> in the sense that hardware does not determine the device, but rather the
> ISA subsystem fakes detection of the devices to feed to the probe
> callback so that the subsequent driver code fits the device driver model
> closer to how other subsystems expect it; thus the difference between an
> ISA and PCI device are abstracted away by their respective ISA and PCI
> bus drivers.

OK.

>> +
>> +	return 0;
>> +
>> +ret_put:
>> +	platform_device_put(winbond_gpio_pdev);
>> +	winbond_gpio_pdev = NULL;
>> +
>> +	return ret;
>> +}
>> +
>> +static struct platform_driver winbond_gpio_pdriver = {
>> +	.driver = {
>> +		.name	= WB_GPIO_DRIVER_NAME,
>> +	},
>> +	.probe		= winbond_gpio_probe,
>> +};
> 
> Reimplement this as a struct isa_driver with respective probe callback.

OK.

>> +
>> +static int __init winbond_gpio_mod_init(void)
>> +{
>> +	int ret;
>> +
>> +	if (ppgpios & odgpios) {
>> +		pr_err(WB_GPIO_DRIVER_NAME
>> +			": 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;
>> +}
>> +
>> +static void __exit winbond_gpio_mod_exit(void)
>> +{
>> +	platform_device_unregister(winbond_gpio_pdev);
>> +	platform_driver_unregister(&winbond_gpio_pdriver);
>> +}
>> +
>> +module_init(winbond_gpio_mod_init);
>> +module_exit(winbond_gpio_mod_exit);
> 
> The winbond_gpio_mod_init and winbond_gpio_mod_exit functions can be
> entirely removed as the ISA bus driver handles this for you. Replace the
> module_init and module_exit macros with simply a module_isa_driver
> macro.

OK.

> The winbond_gpio_try_probe_init function call should now be moved to the
> probe callback.
> 
> I see that you have a hard-coded WB_SIO_BASE I/O port address. Rather, I
> would suggest implementing a "base" module parameter array similar to
> the 104-IDIO-16 driver; this will match the current convention used by
> ISA-style drivers (I know in this case we only have one device, but it's
> good to follow convention for other users familar with the ISA
> subsystem).
>
> For reference, when I want to load the 104-IDIO-16 GPIO driver to handle
> 2 distinct 104-idio-16 devices (one located at 0x200 and the other at
> 0x300) on my system, I do something like the following:
> 
>     # modprobe gpio-104-idio-16 base=0x200,0x300 irq=3,5
> 
> Your Winbond driver will only have the "base" module parameter, but you
> get the idea: the user can pass in the base address for each specific
> device.

The device has just two hardcoded I/O port addresses, so 99% of users
would probably want the driver to probe these two addresses
automatically without them needing to provide the base address
explicitly.

However, I can add the "base" module parameter as an override - the
driver then will simply use it if it is provided, otherwise will try
the hardcoded addresses.

> By the way, don't hesitate to ask for more information on the ISA
> subsystem -- a lot of maintainers are unaware that it even exists since
> so few devices nowadays use ISA-style communication -- but I'm always
> happy to help. :)

Okay, thanks.

> William Breathitt Gray

Best regards,
Maciej Szmigiero
 
>> +
>> +/* This parameter sets which GPIO devices (ports) we enable */
>> +module_param(gpios, byte, 0444);
>> +MODULE_PARM_DESC(gpios,
>> +		 "bitmask of GPIO ports to enable (bit 0 - GPIO1, bit 1 - GPIO2, etc.");
>> +
>> +/*
>> + * These two parameters below set how we configure GPIO ports output drivers.
>> + * It can't be a one bitmask since we need three values per port: push-pull,
>> + * open-drain and keep as-is (this is the default).
>> + */
>> +module_param(ppgpios, byte, 0444);
>> +MODULE_PARM_DESC(ppgpios,
>> +		 "bitmask of GPIO ports to set to push-pull mode (bit 0 - GPIO1, bit 1 - GPIO2, etc.");
>> +
>> +module_param(odgpios, byte, 0444);
>> +MODULE_PARM_DESC(odgpios,
>> +		 "bitmask of GPIO ports to set to open drain mode (bit 0 - GPIO1, bit 1 - GPIO2, etc.");
>> +
>> +/*
>> + * GPIO2.0 and GPIO2.1 control a basic PC functionality that we
>> + * don't allow tinkering with by default (it is very likely that the
>> + * firmware owns these pins).
>> + * These two parameters below allow overriding these prohibitions.
>> + */
>> +module_param(pledgpio, bool, 0644);
>> +MODULE_PARM_DESC(pledgpio,
>> +		 "enable changing value of GPIO2.0 bit (Power LED), default no.");
>> +
>> +module_param(beepgpio, bool, 0644);
>> +MODULE_PARM_DESC(beepgpio,
>> +		 "enable changing value of GPIO2.1 bit (BEEP), default no.");
>> +
>> +MODULE_AUTHOR("Maciej S. Szmigiero <mail@maciej.szmigiero.name>");
>> +MODULE_DESCRIPTION("GPIO interface for Winbond Super I/O chips");
>> +MODULE_LICENSE("GPL");

--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Maciej S. Szmigiero Dec. 25, 2017, 2:48 p.m. UTC | #3
On 24.12.2017 23:42, William Breathitt Gray wrote:
(..)
> By the way, don't hesitate to ask for more information on the ISA
> subsystem -- a lot of maintainers are unaware that it even exists since
> so few devices nowadays use ISA-style communication -- but I'm always
> happy to help. :)

It turns out that all this ISA bus is behind CONFIG_ISA_BUS_API which
cannot be automatically selected by this driver due to a recursive
dependency conflict with CONFIG_STX104.

All the existing ISA bus drivers seem to depend on CONFIG_ISA_BUS_API
instead of selecting it but IMHO this is wrong because:
1) This Kconfig option doesn't really enable or disable any bus support
but building of a library of some common boilerplate code.
Libraries are normally selected by drivers needing them and only provided
as an user-selectable option if there is a possibility that a out-of-tree
module would need it,

2) On x86_64 this option (or rather, its parent option CONFIG_ISA_BUS)
cannot be enabled without CONFIG_EXPERT,

3) This device isn't really a ISA bus device any more than, for example,
a 8250 serial port or a PC-style parallel port and these don't need
that an user explicitly enables "ISA bus support" in his kernel
configuration.

To be clear I'm fine with converting this driver to use the ISA bus (in
fact, I have already done so), but I think that currently this would be
a regression from user-friendliness perspective due to the points above.

> William Breathitt Gray

Best regards,
Maciej Szmigiero
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
William Breathitt Gray Dec. 27, 2017, 12:24 a.m. UTC | #4
On Mon, Dec 25, 2017 at 03:48:16PM +0100, Maciej S. Szmigiero wrote:
>On 24.12.2017 23:42, William Breathitt Gray wrote:
>(..)
>> By the way, don't hesitate to ask for more information on the ISA
>> subsystem -- a lot of maintainers are unaware that it even exists since
>> so few devices nowadays use ISA-style communication -- but I'm always
>> happy to help. :)
>
>It turns out that all this ISA bus is behind CONFIG_ISA_BUS_API which
>cannot be automatically selected by this driver due to a recursive
>dependency conflict with CONFIG_STX104.

Hmm, you may have discovered a bug in my STX104 Kconfig option. I'm
CCing Jonathan Cameron to keep him in the loop if I send a patch to fix
this issue down the road.

Here are the error messages printed on my system when I add a select
ISA_BUS_API line to the GPIO_WINBOND Kconfig option in the version 2 of
your patch:

drivers/gpio/Kconfig:13:error: recursive dependency detected!
For a resolution refer to Documentation/kbuild/kconfig-language.txt
subsection "Kconfig recursive dependency limitations"
drivers/gpio/Kconfig:13:	symbol GPIOLIB is selected by STX104
For a resolution refer to Documentation/kbuild/kconfig-language.txt
subsection "Kconfig recursive dependency limitations"
drivers/iio/adc/Kconfig:659:	symbol STX104 depends on ISA_BUS_API
For a resolution refer to Documentation/kbuild/kconfig-language.txt
subsection "Kconfig recursive dependency limitations"
arch/Kconfig:818:	symbol ISA_BUS_API is selected by GPIO_WINBOND
For a resolution refer to Documentation/kbuild/kconfig-language.txt
subsection "Kconfig recursive dependency limitations"
drivers/gpio/Kconfig:701:	symbol GPIO_WINBOND depends on GPIOLIB

The issue seems to relate to the select GPIOLIB line for the STX104
Kconfig option (which has a ISA_BUS_API dependency). Switching GPIOLIB
to be a dependency, or alternatively selecting ISA_BUS_API, alleviates
the recursion.

Linus, is my use of select GPIOLIB for the STX104 Kconfig option
appropriate in this context -- or should it instead be part of the
depends on line? The STX104 driver includes linux/gpio/driver.h and
makes use of the devm_gpiochip_add_data function to add support for some
minor auxililary GPIO lines on the STX104 device.

>
>All the existing ISA bus drivers seem to depend on CONFIG_ISA_BUS_API
>instead of selecting it but IMHO this is wrong because:
>1) This Kconfig option doesn't really enable or disable any bus support
>but building of a library of some common boilerplate code.
>Libraries are normally selected by drivers needing them and only provided
>as an user-selectable option if there is a possibility that a out-of-tree
>module would need it,
>
>2) On x86_64 this option (or rather, its parent option CONFIG_ISA_BUS)
>cannot be enabled without CONFIG_EXPERT,
>
>3) This device isn't really a ISA bus device any more than, for example,
>a 8250 serial port or a PC-style parallel port and these don't need
>that an user explicitly enables "ISA bus support" in his kernel
>configuration.

I can see what you mean about selecting ISA_BUS_API rather than having
it as a dependency for drivers. Part of the reason I added the
CONFIG_EXPERT dependency for CONFIG_ISA_BUS -- as well as having
CONFIG_ISA_BUS_API be a dependency for the drivers themselves -- was to
hide the ISA-style drivers which blindly poke at I/O port addresses,
lest a niave user enable all available drivers and unintentionally brick
their system when the drivers execute.

I think there is still merit in masking dangerous drivers such as this,
since the expected behavior nowadays is for the driver to probe for the
device before poking at memory; since ISA-style communication lacks a
standard method of detecting devices in hardware, these devices
generally pose a danger when loaded by niave users.

However, I think CONFIG_EXPERT by itself is sufficient enough masking
to help prevent niave users from enabling these drivers on a whim.
Linus and Jonathan, do you have any objections if I replace the
ISA_BUS_API dependencies on my drivers to respective select lines? I
think this would have the benefit of resolving the Kconfig recursive
dependency issue too.

>
>To be clear I'm fine with converting this driver to use the ISA bus (in
>fact, I have already done so), but I think that currently this would be
>a regression from user-friendliness perspective due to the points above.

Regardless of the Kconfig decisions we make, continue to utilize the ISA
bus driver in your code as this API is the correct one to use for your
LPC devices. Kconfig improvements can be made later on, separate from
the driver code, so there's no need to let that be a deciding factor in
getting the driver itself right -- although I do agree that having a
Kconfig setup that does not appeal solely to the masochists is an
important end goal. ;)

William Breathitt Gray

>
>> William Breathitt Gray
>
>Best regards,
>Maciej Szmigiero
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Linus Walleij Dec. 27, 2017, 11:16 a.m. UTC | #5
On Wed, Dec 27, 2017 at 1:24 AM, William Breathitt Gray
<vilhelm.gray@gmail.com> wrote:

> Here are the error messages printed on my system when I add a select
> ISA_BUS_API line to the GPIO_WINBOND Kconfig option in the version 2 of
> your patch:
>
> drivers/gpio/Kconfig:13:error: recursive dependency detected!
> For a resolution refer to Documentation/kbuild/kconfig-language.txt
> subsection "Kconfig recursive dependency limitations"
> drivers/gpio/Kconfig:13:        symbol GPIOLIB is selected by STX104
> For a resolution refer to Documentation/kbuild/kconfig-language.txt
> subsection "Kconfig recursive dependency limitations"
> drivers/iio/adc/Kconfig:659:    symbol STX104 depends on ISA_BUS_API
> For a resolution refer to Documentation/kbuild/kconfig-language.txt
> subsection "Kconfig recursive dependency limitations"
> arch/Kconfig:818:       symbol ISA_BUS_API is selected by GPIO_WINBOND
> For a resolution refer to Documentation/kbuild/kconfig-language.txt
> subsection "Kconfig recursive dependency limitations"
> drivers/gpio/Kconfig:701:       symbol GPIO_WINBOND depends on GPIOLIB

So STX104 depends on ISA_BUS_API which in turn is
selected by GPIO_WINBOND which also depends on GPIOLIB.

> The issue seems to relate to the select GPIOLIB line for the STX104
> Kconfig option (which has a ISA_BUS_API dependency). Switching GPIOLIB
> to be a dependency, or alternatively selecting ISA_BUS_API, alleviates
> the recursion.
>
> Linus, is my use of select GPIOLIB for the STX104 Kconfig option
> appropriate in this context -- or should it instead be part of the
> depends on line? The STX104 driver includes linux/gpio/driver.h and
> makes use of the devm_gpiochip_add_data function to add support for some
> minor auxililary GPIO lines on the STX104 device.

In the STX104 case, it seems to be appropriate to
select GPIOLIB, as it is a GPIO provider, not consumer.

Usually I prefer that drivers just select what they need so I don't
have to run around in the whole kernel tree and turn things on
to the left and right before I can finally select my driver, but
maybe that is just me.

The other ISA GPIO drivers depends on ISA_BUS_API, I guess
in difference from the symbol GPIOLIB it cannot be universally
selected, so shouldn't this driver also just depends on ISA_BUS_API
and select it from the machine or wherever?

Yours,
Linus Walleij
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Maciej S. Szmigiero Dec. 27, 2017, 6:42 p.m. UTC | #6
On 27.12.2017 01:24, William Breathitt Gray wrote:
> On Mon, Dec 25, 2017 at 03:48:16PM +0100, Maciej S. Szmigiero wrote:
(..)
>> All the existing ISA bus drivers seem to depend on CONFIG_ISA_BUS_API
>> instead of selecting it but IMHO this is wrong because:
>> 1) This Kconfig option doesn't really enable or disable any bus support
>> but building of a library of some common boilerplate code.
>> Libraries are normally selected by drivers needing them and only provided
>> as an user-selectable option if there is a possibility that a out-of-tree
>> module would need it,
>>
>> 2) On x86_64 this option (or rather, its parent option CONFIG_ISA_BUS)
>> cannot be enabled without CONFIG_EXPERT,
>>
>> 3) This device isn't really a ISA bus device any more than, for example,
>> a 8250 serial port or a PC-style parallel port and these don't need
>> that an user explicitly enables "ISA bus support" in his kernel
>> configuration.
> 
> I can see what you mean about selecting ISA_BUS_API rather than having
> it as a dependency for drivers. Part of the reason I added the
> CONFIG_EXPERT dependency for CONFIG_ISA_BUS -- as well as having
> CONFIG_ISA_BUS_API be a dependency for the drivers themselves -- was to
> hide the ISA-style drivers which blindly poke at I/O port addresses,
> lest a niave user enable all available drivers and unintentionally brick
> their system when the drivers execute.
> 
> I think there is still merit in masking dangerous drivers such as this,
> since the expected behavior nowadays is for the driver to probe for the
> device before poking at memory; since ISA-style communication lacks a
> standard method of detecting devices in hardware, these devices
> generally pose a danger when loaded by niave users.

This driver accesses the same Super I/O chip as w83627ehf hwmon and
w83627hf_wdt watchdog drivers.
In addition to this, there are loads of other hwmon and watchdog drivers
for x86 Super I/Os in the tree, most of them using the same probing and
communication style.
There are even existing GPIO drivers for some Super I/Os like gpio-it87
and gpio-f7188x.

None of these drivers need CONFIG_EXPERT to be selected.

Also, CONFIG_EXPERT is described as "Configure standard kernel features"
and that "[it] allows certain base kernel options and settings to be
disabled or tweaked" for "specialized environments".
Enabling this driver is not about changing "standard kernel feature" or
a "base kernel option [or] setting".

> However, I think CONFIG_EXPERT by itself is sufficient enough masking
> to help prevent niave users from enabling these drivers on a whim.
> Linus and Jonathan, do you have any objections if I replace the
> ISA_BUS_API dependencies on my drivers to respective select lines? I
> think this would have the benefit of resolving the Kconfig recursive
> dependency issue too.
> 
>>
>> To be clear I'm fine with converting this driver to use the ISA bus (in
>> fact, I have already done so), but I think that currently this would be
>> a regression from user-friendliness perspective due to the points above.
> 
> Regardless of the Kconfig decisions we make, continue to utilize the ISA
> bus driver in your code as this API is the correct one to use for your
> LPC devices. Kconfig improvements can be made later on, separate from
> the driver code, so there's no need to let that be a deciding factor in
> getting the driver itself right -- although I do agree that having a
> Kconfig setup that does not appeal solely to the masochists is an
> important end goal. ;)

I'm fine with using the ISA bus for this driver, however I cannot submit
a driver for inclusion that causes an error during kernel configuration,
while, on the other hand, I think (for the reasons described above) that
it shouldn't be dependent on the CONFIG_EXPERT option.

> 
> William Breathitt Gray

Maciej Szmigiero
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
William Breathitt Gray Dec. 28, 2017, 4:33 a.m. UTC | #7
On Wed, Dec 27, 2017 at 12:16:53PM +0100, Linus Walleij wrote:
>On Wed, Dec 27, 2017 at 1:24 AM, William Breathitt Gray
><vilhelm.gray@gmail.com> wrote:
>
>> The issue seems to relate to the select GPIOLIB line for the STX104
>> Kconfig option (which has a ISA_BUS_API dependency). Switching GPIOLIB
>> to be a dependency, or alternatively selecting ISA_BUS_API, alleviates
>> the recursion.
>>
>> Linus, is my use of select GPIOLIB for the STX104 Kconfig option
>> appropriate in this context -- or should it instead be part of the
>> depends on line? The STX104 driver includes linux/gpio/driver.h and
>> makes use of the devm_gpiochip_add_data function to add support for some
>> minor auxililary GPIO lines on the STX104 device.
>
>In the STX104 case, it seems to be appropriate to
>select GPIOLIB, as it is a GPIO provider, not consumer.
>
>Usually I prefer that drivers just select what they need so I don't
>have to run around in the whole kernel tree and turn things on
>to the left and right before I can finally select my driver, but
>maybe that is just me.
>
>The other ISA GPIO drivers depends on ISA_BUS_API, I guess
>in difference from the symbol GPIOLIB it cannot be universally
>selected, so shouldn't this driver also just depends on ISA_BUS_API
>and select it from the machine or wherever?

When I initially submitted the PC/104 device drivers, I added
ISA_BUS_API to their depends on lines in order to mask the respective
drivers from users who do not have a PC/104 bus on their system; in
retrospect, I shouldn't have done it this way. I later added a proper
CONFIG_PC104 option to achieve the masking I initially intended.

The correct semantic would be to treat ISA_BUS_API as we do GPIOLIB:
allow drivers to select it when needed. The reason is that
CONFIG_ISA_BUS_API simply enables the compilation of the
drivers/base/isa.c file. This file has no other Kconfig dependencies and
simply provides a thin layer of code to eliminate some boilerplate
common to ISA-style device drivers; no hardware interactions occur
within this code, just pure software abstractions.

Given that CONFIG_PC104 now fulfills the original purpose of adding
ISA_BUS_API to the depends on line for the PC/104 device drivers, and
that ISA_BUS_API can be selected as necessary with no danger to the
integrity of the system, I think it would be appropriate to change all
the existing ISA_BUS_API depends on lines to respective select lines.
Does that logic make sense?

William Breathitt Gray

>
>Yours,
>Linus Walleij
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
William Breathitt Gray Dec. 28, 2017, 4:58 a.m. UTC | #8
On Wed, Dec 27, 2017 at 07:42:21PM +0100, Maciej S. Szmigiero wrote:
>On 27.12.2017 01:24, William Breathitt Gray wrote:
>> On Mon, Dec 25, 2017 at 03:48:16PM +0100, Maciej S. Szmigiero wrote:
>(..)
>>> All the existing ISA bus drivers seem to depend on CONFIG_ISA_BUS_API
>>> instead of selecting it but IMHO this is wrong because:
>>> 1) This Kconfig option doesn't really enable or disable any bus support
>>> but building of a library of some common boilerplate code.
>>> Libraries are normally selected by drivers needing them and only provided
>>> as an user-selectable option if there is a possibility that a out-of-tree
>>> module would need it,
>>>
>>> 2) On x86_64 this option (or rather, its parent option CONFIG_ISA_BUS)
>>> cannot be enabled without CONFIG_EXPERT,
>>>
>>> 3) This device isn't really a ISA bus device any more than, for example,
>>> a 8250 serial port or a PC-style parallel port and these don't need
>>> that an user explicitly enables "ISA bus support" in his kernel
>>> configuration.
>> 
>> I can see what you mean about selecting ISA_BUS_API rather than having
>> it as a dependency for drivers. Part of the reason I added the
>> CONFIG_EXPERT dependency for CONFIG_ISA_BUS -- as well as having
>> CONFIG_ISA_BUS_API be a dependency for the drivers themselves -- was to
>> hide the ISA-style drivers which blindly poke at I/O port addresses,
>> lest a niave user enable all available drivers and unintentionally brick
>> their system when the drivers execute.
>> 
>> I think there is still merit in masking dangerous drivers such as this,
>> since the expected behavior nowadays is for the driver to probe for the
>> device before poking at memory; since ISA-style communication lacks a
>> standard method of detecting devices in hardware, these devices
>> generally pose a danger when loaded by niave users.
>
>This driver accesses the same Super I/O chip as w83627ehf hwmon and
>w83627hf_wdt watchdog drivers.
>In addition to this, there are loads of other hwmon and watchdog drivers
>for x86 Super I/Os in the tree, most of them using the same probing and
>communication style.
>There are even existing GPIO drivers for some Super I/Os like gpio-it87
>and gpio-f7188x.
>
>None of these drivers need CONFIG_EXPERT to be selected.
>
>Also, CONFIG_EXPERT is described as "Configure standard kernel features"
>and that "[it] allows certain base kernel options and settings to be
>disabled or tweaked" for "specialized environments".
>Enabling this driver is not about changing "standard kernel feature" or
>a "base kernel option [or] setting".

I'm sorry, I didn't make it quite clear in my previous reply. I agree
with you that CONFIG_EXPERT shouldn't be necessary for this driver -- in
the end, a select ISA_BUS_API line should be all that's needed to have
ISA bus driver support for your driver.

My reference to the CONFIG_EXPERT option is for masking options related
to other ISA-style buses not commonly found in desktop systems. Devices
like the Super I/O chip wouldn't fall into this category since LPC is
pretty common and the relevant I/O port addresses are usually well
known. Ultimately, the Winbond GPIO driver should not need CONFIG_EXPERT
to be enabled in order to select ISA_BUS_API.

William Breathitt Gray

>> 
>> William Breathitt Gray
>
>Maciej Szmigiero
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Linus Walleij Dec. 28, 2017, 12:52 p.m. UTC | #9
On Thu, Dec 28, 2017 at 5:33 AM, William Breathitt Gray
<vilhelm.gray@gmail.com> wrote:

> Given that CONFIG_PC104 now fulfills the original purpose of adding
> ISA_BUS_API to the depends on line for the PC/104 device drivers, and
> that ISA_BUS_API can be selected as necessary with no danger to the
> integrity of the system, I think it would be appropriate to change all
> the existing ISA_BUS_API depends on lines to respective select lines.
> Does that logic make sense?

I'm *BAD* at Kconfig.

But it makes sense to me. I just suspect that if you turn these into selects,
you will see the same thing that Maciej is seeing and then you get to deal
with it :D

So by all means, try it out!

Yours,
Linus Walleij
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Andy Shevchenko Dec. 28, 2017, 3:12 p.m. UTC | #10
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@maciej.szmigiero.name>");
> +MODULE_DESCRIPTION("GPIO interface for Winbond Super I/O chips");

> +MODULE_LICENSE("GPL");

Does it match SPDX identifier?
Andy Shevchenko Dec. 28, 2017, 3:18 p.m. UTC | #11
On Fri, 2017-12-22 at 19:58 +0100, Maciej S. Szmigiero wrote:
> This commit adds GPIO driver for Winbond Super I/Os.

Linus, since you asked me to review this, I can conclude that it is a
bit far from readiness. Couple of more iterations needed.

Please, also answer to a comment in review in regard to gpio_valid_mask
use.
Maciej S. Szmigiero Dec. 28, 2017, 10:45 p.m. UTC | #12
On 28.12.2017 05:58, William Breathitt Gray wrote:
> On Wed, Dec 27, 2017 at 07:42:21PM +0100, Maciej S. Szmigiero wrote:
>> On 27.12.2017 01:24, William Breathitt Gray wrote:
>>> On Mon, Dec 25, 2017 at 03:48:16PM +0100, Maciej S. Szmigiero wrote:
>> (..)
>>>> All the existing ISA bus drivers seem to depend on CONFIG_ISA_BUS_API
>>>> instead of selecting it but IMHO this is wrong because:
>>>> 1) This Kconfig option doesn't really enable or disable any bus support
>>>> but building of a library of some common boilerplate code.
>>>> Libraries are normally selected by drivers needing them and only provided
>>>> as an user-selectable option if there is a possibility that a out-of-tree
>>>> module would need it,
>>>>
>>>> 2) On x86_64 this option (or rather, its parent option CONFIG_ISA_BUS)
>>>> cannot be enabled without CONFIG_EXPERT,
>>>>
>>>> 3) This device isn't really a ISA bus device any more than, for example,
>>>> a 8250 serial port or a PC-style parallel port and these don't need
>>>> that an user explicitly enables "ISA bus support" in his kernel
>>>> configuration.
>>>
>>> I can see what you mean about selecting ISA_BUS_API rather than having
>>> it as a dependency for drivers. Part of the reason I added the
>>> CONFIG_EXPERT dependency for CONFIG_ISA_BUS -- as well as having
>>> CONFIG_ISA_BUS_API be a dependency for the drivers themselves -- was to
>>> hide the ISA-style drivers which blindly poke at I/O port addresses,
>>> lest a niave user enable all available drivers and unintentionally brick
>>> their system when the drivers execute.
>>>
>>> I think there is still merit in masking dangerous drivers such as this,
>>> since the expected behavior nowadays is for the driver to probe for the
>>> device before poking at memory; since ISA-style communication lacks a
>>> standard method of detecting devices in hardware, these devices
>>> generally pose a danger when loaded by niave users.
>>
>> This driver accesses the same Super I/O chip as w83627ehf hwmon and
>> w83627hf_wdt watchdog drivers.
>> In addition to this, there are loads of other hwmon and watchdog drivers
>> for x86 Super I/Os in the tree, most of them using the same probing and
>> communication style.
>> There are even existing GPIO drivers for some Super I/Os like gpio-it87
>> and gpio-f7188x.
>>
>> None of these drivers need CONFIG_EXPERT to be selected.
>>
>> Also, CONFIG_EXPERT is described as "Configure standard kernel features"
>> and that "[it] allows certain base kernel options and settings to be
>> disabled or tweaked" for "specialized environments".
>> Enabling this driver is not about changing "standard kernel feature" or
>> a "base kernel option [or] setting".
> 
> I'm sorry, I didn't make it quite clear in my previous reply. I agree
> with you that CONFIG_EXPERT shouldn't be necessary for this driver -- in
> the end, a select ISA_BUS_API line should be all that's needed to have
> ISA bus driver support for your driver.

My apologies for misunderstanding you.

> My reference to the CONFIG_EXPERT option is for masking options related
> to other ISA-style buses not commonly found in desktop systems. Devices
> like the Super I/O chip wouldn't fall into this category since LPC is
> pretty common and the relevant I/O port addresses are usually well
> known. Ultimately, the Winbond GPIO driver should not need CONFIG_EXPERT
> to be enabled in order to select ISA_BUS_API.

Thanks for the explanation, it is clear for me now what you had on mind.

> William Breathitt Gray
> 

Maciej Szmigiero
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Maciej S. Szmigiero Dec. 28, 2017, 11:44 p.m. UTC | #13
On 28.12.2017 16:12, Andy Shevchenko wrote:
> 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?

It is required bacause "[o]ne 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".

> 2. GPIO1 -> GPIO0, GPIO2 -> GPIO1, etc ?

The chip datasheet calls GPIO devices and their pins "GPIO1", "GPIO2",
etc., however the driver uses a more common zero-based indexing (so,
for example, we don't waste the zeroth bit).

> 
>> +
>> +	  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?

Isn't 'e' < 'g' < 'i' < 'm' < 'p' ?

BTW. The ISA bus version has slightly different includes.

> 
>> +
>> +#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?

Will use tabs as separators between name and value in this type of macro.

>> +#define WB_SIO_CHIP_ID_W83627UHG 0xa230
>> +#define WB_SIO_CHIP_ID_W83627UHG_MASK 0xfff0
> 
> GENMASK()

Ok.

>> +
>> +/* 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. ???

Super I/O logical device number for UART B.

> 2. Register offset

(Device) enable register offset.

> 3. Bit to enable

(Device) enable register bit index.

> ?
> 
>> +
>> +#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?

Ok.
 
>> +#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?

Registers with offset >= 0x30 are
specific for a particular device.

That's a register in a different device
(which happen to have similar function as
register 0x30 in, for example, UARTC but
nothing in the datasheet guarantees that
such mapping will be universal).

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

Ok.

> 
>> +#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?

Again, it's a different device.

>> +
>> +#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.

All of them, besides i2cgpio, are module parameters, which require
global variables.

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

Ok.

>> +		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.

Ok.

>> +		return -EBUSY;
> 
>> +	}
>> +
> 
>> +	outb(WB_SIO_EXT_ENTER_KEY, base);
>> +	outb(WB_SIO_EXT_ENTER_KEY, base);
> 
> Comment why double write is needed.

Ok.

>> +
>> +	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?

Looking at the regmap API:
* It does not support a I/O port space,

* It does not support a shared (muxed) register space, where one needs
to first request the space for a particular driver, then can perform
required sequence of operations, then should release the space,

* It does not support multiple logical devices sharing a register
address space where one needs first to select the logical device, then
perform the required sequence of operations.

>> +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()

Do we really want to replace a simple 6-iteration "for" loop with a
one that (possibly) does a function call at setup time then an another
per iteration?

>> +
>> +		if (gpio_num < 8)
>> +			break;
>> +
> 
>> +		gpio_num -= 8;
> 
> Yeah, consider to use % and / paired, see below.

Here it is only a simple subtraction of 8 lines per each set bit in
'gpios', how do you suggest to replace it with a faster
division-with-reminder operation (I guess it isn't supposed to be 
"if (gpio_num / 8 == 0) break;")?

>> +	}
>> +
>> +	/*
>> +	 * 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.

Yes, this condition should never happen in principle (it's only a
defensive programming check) so I think that printing a warning and
letting it read the first GPIO that is present while disallowing write
access would be fine in this very unlikely situation.

>> +
>> +	*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?)

Can't find such identifier in the gpio tree (devel branch).

>> +	}
>> +
>> +	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.

Ok, will change the parameter name.

>> +{
>> +	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.

I kind of understand what you had in mind, but I have a simpler solution:
I will simply return the reduced gpio number (or offset) from
winbond_gpio_get_info() function.
This way no division (or reminder) operation is necessary at all.

>> +
>> +	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.

Ok, the port number will be changed unsigned long type.

>> +	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()) ?

Ok.

>> +		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.

Ok, will replace with ARRAY_SIZE as it is done in similar places.

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

The same situation as with the for_each_set_bit() remark above.

>> +
>> +	if (gpios & BIT(5))
>> +		winbond_gpio_chip.ngpio += 5;
> 
> Comment needed for this one.

Ok.

>> +
>> +	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.

This code is rewritten in the ISA bus version.

>> +{
>> +	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.

Ok.

>> +
>> +	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;
> 
> ???

This code is rewritten in the ISA bus version.

>> +
>> +	return ret;
>> +}
>>
> 
>> +static int __init winbond_gpio_mod_init(void)
>> +{
>> +	int ret;
>> +
>> +	if (ppgpios & odgpios) {
>> +		pr_err(WB_GPIO_DRIVER_NAME
> 
> #define pr_fmt

Ok.
 
>> +			": 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?

This code is rewritten in the ISA bus version.

>> +}
>> +
>> +static void __exit winbond_gpio_mod_exit(void)
>> +{
> 
>> +	platform_device_unregister(winbond_gpio_pdev);
>> +	platform_driver_unregister(&winbond_gpio_pdriver);
> 
> Hmm... what?

This code is rewritten in the ISA bus version.
 
>> +}
>> +
>> +module_init(winbond_gpio_mod_init);
>> +module_exit(winbond_gpio_mod_exit);
>>
> 
>> +MODULE_AUTHOR("Maciej S. Szmigiero <mail@maciej.szmigiero.name>");
>> +MODULE_DESCRIPTION("GPIO interface for Winbond Super I/O chips");
> 
>> +MODULE_LICENSE("GPL");
> 
> Does it match SPDX identifier?
> 

Yes, since according to MODULE_LICENSE() macro comment "GPL" means "GNU
Public License v2 or later".

Maciej
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Andy Shevchenko Dec. 29, 2017, 10:27 a.m. UTC | #14
On Fri, 2017-12-29 at 00:44 +0100, Maciej S. Szmigiero wrote:
> On 28.12.2017 16:12, Andy Shevchenko wrote:
> > On Fri, 2017-12-22 at 19:58 +0100, Maciej S. Szmigiero wrote:
> > > 

> > > +	  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?
> 
> It is required bacause "[o]ne 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".

I would like to be clear, I was asking about module parameters. Nowadays
we won't have new parameters to the kernel.

Is there any strong argument to do it so? Is it one above? Can we detect
as much as possible run time?

> > 2. GPIO1 -> GPIO0, GPIO2 -> GPIO1, etc ?
> 
> The chip datasheet calls GPIO devices and their pins "GPIO1", "GPIO2",
> etc., however the driver uses a more common zero-based indexing (so,
> for example, we don't waste the zeroth bit).

I dunno what makes less confusion here.

One way, is to provide labels according to data sheet (not so flexible,
though), another waste 0th bit for good.

Linus, what's your opinion?

> > > +#include <linux/errno.h>
> > > +#include <linux/gpio.h>
> > > +#include <linux/ioport.h>
> > > +#include <linux/module.h>
> > > +#include <linux/platform_device.h>
> > 
> > Perhaps, alphabetically ordered?
> 
> Isn't 'e' < 'g' < 'i' < 'm' < 'p' ?

Ah, indeed!

> > > +
> > > +#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. ???
> 
> Super I/O logical device number for UART B.
> 
> > 2. Register offset
> 
> (Device) enable register offset.
> 
> > 3. Bit to enable
> 
> (Device) enable register bit index.


> > > +#define WB_SIO_DEV_WDGPIO56 8
> > > +#define WB_SIO_WDGPIO56_REG_ENABLE 0x30
> > 
> > Why do we have duplication here?
> 
> Registers with offset >= 0x30 are
> specific for a particular device.
> 
> That's a register in a different device
> (which happen to have similar function as
> register 0x30 in, for example, UARTC but
> nothing in the datasheet guarantees that
> such mapping will be universal).

OK, then can you put a comment like this one before this very definition
block (with offsets >= 0x30) and put a one line comment before each
*different* device.

> > > +static u8 gpios;
> > > +static u8 ppgpios;
> > > +static u8 odgpios;
> > > +static bool pledgpio;
> > > +static bool beepgpio;
> > > +static bool i2cgpio;
> > 
> > Hmm... Too many global variables.
> 
> All of them, besides i2cgpio, are module parameters, which require
> global variables.

Same question as on top of this mail.
Why do we need all of them?

> > > +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?
> 
> Looking at the regmap API:
> * It does not support a I/O port space,

Not an issue, you may provide your own IO accessors.

> * It does not support a shared (muxed) register space, where one needs
> to first request the space for a particular driver, then can perform
> required sequence of operations, then should release the space,

If it's an MFD, it would be done at MFD level and shared across devices.
Also, there is no problem to remap same IO space as many times as
driver(s) want(s) to.

> * It does not support multiple logical devices sharing a register
> address space where one needs first to select the logical device, then
> perform the required sequence of operations.

We have MFD for precisely that kind of devices.

> > > +	for (i = 0; i < ARRAY_SIZE(winbond_gpio_infos); i++) {
> > > +		if (!(gpios & BIT(i)))
> > > +			continue;
> > 
> > for_each_set_bit()
> 
> Do we really want to replace a simple 6-iteration "for" loop with a
> one that (possibly) does a function call at setup time then an another
> per iteration?

Yes. Rationale is:
- makes less LOCs
- makes code more readable and understandble at a glance
- unloads optimization stuff to compiler's shoulders.

> 
> > > +
> > > +		if (gpio_num < 8)
> > > +			break;
> > > +
> > > +		gpio_num -= 8;
> > 
> > Yeah, consider to use % and / paired, see below.
> 
> Here it is only a simple subtraction of 8 lines per each set bit in
> 'gpios', how do you suggest to replace it with a faster
> division-with-reminder operation (I guess it isn't supposed to be 
> "if (gpio_num / 8 == 0) break;")?

Looking above and below, I actually missed that what you need is
hweight().

> > > +	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.
> 
> Yes, this condition should never happen in principle (it's only a
> defensive programming check) so I think that printing a warning and
> letting it read the first GPIO that is present while disallowing write
> access would be fine in this very unlikely situation.

I think it's just a waste of effort. If this happens it happens to all
GPIO drivers, otherwise it might be some HW issue with memory, for
example, that again makes entire system unstable.

% git grep -n WARN -- drivers/gpio/ | cut -f1 -d: | sort -u
drivers/gpio/devres.c
drivers/gpio/gpio-aspeed.c
drivers/gpio/gpio-brcmstb.c
drivers/gpio/gpio-bt8xx.c
drivers/gpio/gpio-davinci.c
drivers/gpio/gpio-grgpio.c
drivers/gpio/gpiolib-acpi.c
drivers/gpio/gpiolib.c
drivers/gpio/gpiolib-of.c
drivers/gpio/gpio-omap.c
drivers/gpio/gpio-tegra186.c
drivers/gpio/gpio-thunderx.c
drivers/gpio/gpio-twl4030.c
drivers/gpio/gpio-tz1090.c
drivers/gpio/gpio-uniphier.c
drivers/gpio/gpio-zynq.c

Basically above list minus 4 library files (and perhaps even less to
care about similar "should never happen" cases).

$ ls -1 drivers/gpio/ | wc -l
147

> > > +	/*
> > > +	 * 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?)
> 
> Can't find such identifier in the gpio tree (devel branch).

It's not there, that's why I asked Linus to comment.

> > > +	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.
> 
> I kind of understand what you had in mind, but I have a simpler
> solution:
> I will simply return the reduced gpio number (or offset) from
> winbond_gpio_get_info() function.
> This way no division (or reminder) operation is necessary at all.

Sounds even better!

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

Just to be clear, I put "question mark" at the end of my proposals in
which I completely leave choice on author(s) / maintainer(s).

> > > +	/*
> > > +	 * 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.
> 
> Ok, will replace with ARRAY_SIZE as it is done in similar places.
> 
> > > +		if (gpios & BIT(i))
> > > +			winbond_gpio_chip.ngpio += 8;
> > 
> > for_each_set_bit()
> 
> The same situation as with the for_each_set_bit() remark above.

I believe hweight() is suitable here.
William Breathitt Gray Dec. 29, 2017, 4:09 p.m. UTC | #15
On Fri, Dec 29, 2017 at 12:27:12PM +0200, Andy Shevchenko wrote:
>On Fri, 2017-12-29 at 00:44 +0100, Maciej S. Szmigiero wrote:
>> On 28.12.2017 16:12, Andy Shevchenko wrote:
>> > On Fri, 2017-12-22 at 19:58 +0100, Maciej S. Szmigiero wrote:
>> > > 
>
>> > > +	  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?
>> 
>> It is required bacause "[o]ne 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".
>
>I would like to be clear, I was asking about module parameters. Nowadays
>we won't have new parameters to the kernel.
>
>Is there any strong argument to do it so? Is it one above? Can we detect
>as much as possible run time?

The Low Pin Count (LPC) bus is a modern computer bus which to software
resembles the legacy ISA bus of the 1980s. Unfortunately, this means
port addresses for devices are assumed based on convention and manual
configuration rather than hardware detection -- as such, there is a
danger of clobbering another device's address space since addressing
must be resolved manually.

Maciej, although the base address for the Winbond Super I/O chip cannot
be probed, does the chip itself offer configuration information for how
many GPIO are available -- or instead is the total number of GPIO
supported by firmware always the same and it is left up to the user to
make sure they do not clobber other devices' address spaces during use?
My suspicion is the latter, but I figure I may as well ask.

If so, I suggest simply allowing access to all supported GPIO to the
user. My reasoning is this: the user is the one loading the module
parameter, so they should already know which GPIO they intend to use --
as such, if they wouldn't have requested it in your "gpios" module
parameter, they won't use it when the gpiochip is registered. Thus, the
"gpios" module parameter can be removed and ngpios simply set to the
total number of supported GPIO.

For example, the 104-IDIO-16 GPIO driver supports 32 GPIO lines because
104-IDIO-16 devices have 32 available GPIO lines. However, this same
driver may be use for 104-IDIO-8 devices which are firmware compatible
with 104-IDIO-16 devices. In this case, despite 104-IDIO-8 devices only
having 16 GPIO lines, ngpios is still set to 32 because the user already
knows that the upper 16 GPIO lines are not available for their device,
despite the driver permitting access to them.

One problem I do see is how to handle your "ppgpios" and "odgpios"
module parameters, which allow the configuration of push-pull mode and
open drain mode respectfully. I would like to see these module
parameters go aways as well and leave it up for the user to configure at
runtime. Linus, does the GPIO subsystem have a method of dynamically
setting these kind of pin configurations?

William Breathitt Gray

>
>-- 
>Andy Shevchenko <andriy.shevchenko@linux.intel.com>
>Intel Finland Oy
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Maciej S. Szmigiero Dec. 30, 2017, 9:02 p.m. UTC | #16
On 29.12.2017 11:27, Andy Shevchenko wrote:
> On Fri, 2017-12-29 at 00:44 +0100, Maciej S. Szmigiero wrote:
>> On 28.12.2017 16:12, Andy Shevchenko wrote:
>>> On Fri, 2017-12-22 at 19:58 +0100, Maciej S. Szmigiero wrote:
>>>>
> 
>>>> +	  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?
>>
>> It is required bacause "[o]ne 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".
> 
> I would like to be clear, I was asking about module parameters. Nowadays
> we won't have new parameters to the kernel.
> 
> Is there any strong argument to do it so? Is it one above? Can we detect
> as much as possible run time?

These GPIO devices are not described anywhere by the platform AFAIK.
It is also very likely that these particular devices and their pins that
aren't used internally by the firmware aren't even configured correctly.

That's why we don't really have a general ability to detect which GPIO
device(s) should be configured and how.

Other issue is that random hacking and reverse engineering of
motherboard signals (SIO GPIO lines are often used internally to
implement switching of various motherboard functions, like LED control,
main / backup BIOS swapping, etc.) will probably represent the most
common use case for this driver so its settings should be changeable
without needing to modify the kernel.

Don't think about this driver like a ready driver for some platform,
like a driver for a Foo motherboard lights or a Bar laptop extra keys.
In such cases a dedicated driver should be written (which would then
interact with LED and input subsystems, respectively).

>>> 2. GPIO1 -> GPIO0, GPIO2 -> GPIO1, etc ?
>>
>> The chip datasheet calls GPIO devices and their pins "GPIO1", "GPIO2",
>> etc., however the driver uses a more common zero-based indexing (so,
>> for example, we don't waste the zeroth bit).
> 
> I dunno what makes less confusion here.
> 
> One way, is to provide labels according to data sheet (not so flexible,
> though), another waste 0th bit for good.
> 
> Linus, what's your opinion?
>
(..)
>>>> +#define WB_SIO_DEV_WDGPIO56 8
>>>> +#define WB_SIO_WDGPIO56_REG_ENABLE 0x30
>>>
>>> Why do we have duplication here?
>>
>> Registers with offset >= 0x30 are
>> specific for a particular device.
>>
>> That's a register in a different device
>> (which happen to have similar function as
>> register 0x30 in, for example, UARTC but
>> nothing in the datasheet guarantees that
>> such mapping will be universal).
> 
> OK, then can you put a comment like this one before this very definition
> block (with offsets >= 0x30) and put a one line comment before each
> *different* device.

Ok.

>>>> +static u8 gpios;
>>>> +static u8 ppgpios;
>>>> +static u8 odgpios;
>>>> +static bool pledgpio;
>>>> +static bool beepgpio;
>>>> +static bool i2cgpio;
>>>
>>> Hmm... Too many global variables.
>>
>> All of them, besides i2cgpio, are module parameters, which require
>> global variables.
> 
> Same question as on top of this mail.
> Why do we need all of them?

I've responded above to this module parameters issue.

>>>> +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?
>>
>> Looking at the regmap API:
>> * It does not support a I/O port space,
> 
> Not an issue, you may provide your own IO accessors.
I meant here that there is no ready regmap support for the I/O port
space like there is for AC'97, I2C, MMIO, SPI, SPMI and W1, sorry if I
wasn't clear enough.

>> * It does not support a shared (muxed) register space, where one needs
>> to first request the space for a particular driver, then can perform
>> required sequence of operations, then should release the space,
> 
> If it's an MFD, it would be done at MFD level and shared across devices.
> Also, there is no problem to remap same IO space as many times as
> driver(s) want(s) to.
> 
>> * It does not support multiple logical devices sharing a register
>> address space where one needs first to select the logical device, then
>> perform the required sequence of operations.
> 
> We have MFD for precisely that kind of devices.

Looks like MFD infrastructure isn't generally used for Super I/Os:
it seems that currently no driver for this type of hardware uses it,
despite there being a lot of Super I/O hwmon and watchdog drivers in
the kernel tree (and even two existing GPIO ones).

Also, w83627ehf hwmon and w83627hf_wdt watchdog drivers (which access
the same hardware as this driver) would need to be rewritten to use
this infrastructure, too.

Overall, I think it is just an overkill for such a small driver.

>>>> +	for (i = 0; i < ARRAY_SIZE(winbond_gpio_infos); i++) {
>>>> +		if (!(gpios & BIT(i)))
>>>> +			continue;
>>>
>>> for_each_set_bit()
>>
>> Do we really want to replace a simple 6-iteration "for" loop with a
>> one that (possibly) does a function call at setup time then an another
>> per iteration?
> 
> Yes. Rationale is:
> - makes less LOCs
> - makes code more readable and understandble at a glance
> - unloads optimization stuff to compiler's shoulders.

Ok.

>>
>>>> +
>>>> +		if (gpio_num < 8)
>>>> +			break;
>>>> +
>>>> +		gpio_num -= 8;
>>>
>>> Yeah, consider to use % and / paired, see below.
>>
>> Here it is only a simple subtraction of 8 lines per each set bit in
>> 'gpios', how do you suggest to replace it with a faster
>> division-with-reminder operation (I guess it isn't supposed to be 
>> "if (gpio_num / 8 == 0) break;")?
> 
> Looking above and below, I actually missed that what you need is
> hweight().

Here hweight() felt like a poor fit, but it was used in the code below.

>>>> +	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.
>>
>> Yes, this condition should never happen in principle (it's only a
>> defensive programming check) so I think that printing a warning and
>> letting it read the first GPIO that is present while disallowing write
>> access would be fine in this very unlikely situation.
> 
> I think it's just a waste of effort. If this happens it happens to all
> GPIO drivers, otherwise it might be some HW issue with memory, for
> example, that again makes entire system unstable.
> 
> % git grep -n WARN -- drivers/gpio/ | cut -f1 -d: | sort -u
> drivers/gpio/devres.c
> drivers/gpio/gpio-aspeed.c
> drivers/gpio/gpio-brcmstb.c
> drivers/gpio/gpio-bt8xx.c
> drivers/gpio/gpio-davinci.c
> drivers/gpio/gpio-grgpio.c
> drivers/gpio/gpiolib-acpi.c
> drivers/gpio/gpiolib.c
> drivers/gpio/gpiolib-of.c
> drivers/gpio/gpio-omap.c
> drivers/gpio/gpio-tegra186.c
> drivers/gpio/gpio-thunderx.c
> drivers/gpio/gpio-twl4030.c
> drivers/gpio/gpio-tz1090.c
> drivers/gpio/gpio-uniphier.c
> drivers/gpio/gpio-zynq.c
> 
> Basically above list minus 4 library files (and perhaps even less to
> care about similar "should never happen" cases).
> 
> $ ls -1 drivers/gpio/ | wc -l
> 147

I've removed this check now but I cannot say I agree that checks like
this are just a waste of effort: in fact only yesterday I've hit a
userspace-triggerable NULL pointer dereference in the pktcdvd driver
caused by bdev_get_queue() dereferencing an intermediate pointer blindly.

Of course, the actual cause of this bug probably lies somewhere else, but
a "soft" failure of returning an error (or printing a warning, or not
performing the requested operation, etc.) is no doubt preferred over a
"hard" failure of a kernel NULL pointer dereference.

Although in case of bdev_get_queue() function it can be argued that
this check wasn't done for performance reasons, since it is a core
blkdev API, one really cannot say so about winbond_gpio_get_info() in
this driver.

(..)
>>>> +	if (winbond_sio_reg_btest(*base, info->invreg, gpio_num))
>>>> +		val = !val;
>>>> +
>>>> +	if (val)
>>>
>>> if (val ^ winbond_sio_reg_btest()) ?
>>
>> Ok.
> 
> Just to be clear, I put "question mark" at the end of my proposals in
> which I completely leave choice on author(s) / maintainer(s).

All right, so I left this one as-is, since I think it is clearer this
way.

>>>> +	/*
>>>> +	 * 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.
>>
>> Ok, will replace with ARRAY_SIZE as it is done in similar places.
>>
>>>> +		if (gpios & BIT(i))
>>>> +			winbond_gpio_chip.ngpio += 8;
>>>
>>> for_each_set_bit()
>>
>> The same situation as with the for_each_set_bit() remark above.
> 
> I believe hweight() is suitable here.
> 

I've adapted the code to use hweight() here.

Maciej
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Maciej S. Szmigiero Dec. 30, 2017, 9:02 p.m. UTC | #17
On 29.12.2017 17:09, William Breathitt Gray wrote:
> On Fri, Dec 29, 2017 at 12:27:12PM +0200, Andy Shevchenko wrote:
>> On Fri, 2017-12-29 at 00:44 +0100, Maciej S. Szmigiero wrote:
>>> On 28.12.2017 16:12, Andy Shevchenko wrote:
>>>> On Fri, 2017-12-22 at 19:58 +0100, Maciej S. Szmigiero wrote:
>>>>>
>>
>>>>> +	  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?
>>>
>>> It is required bacause "[o]ne 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".
>>
>> I would like to be clear, I was asking about module parameters. Nowadays
>> we won't have new parameters to the kernel.
>>
>> Is there any strong argument to do it so? Is it one above? Can we detect
>> as much as possible run time?
> 
> The Low Pin Count (LPC) bus is a modern computer bus which to software
> resembles the legacy ISA bus of the 1980s. Unfortunately, this means
> port addresses for devices are assumed based on convention and manual
> configuration rather than hardware detection -- as such, there is a
> danger of clobbering another device's address space since addressing
> must be resolved manually.

Yes, although according to the datasheet this particular device only
supports two hardcoded I/O bases (and is soldered on a motherboard) so
a random conflict is unlikely.

> 
> Maciej, although the base address for the Winbond Super I/O chip cannot
> be probed, does the chip itself offer configuration information for how
> many GPIO are available -- or instead is the total number of GPIO
> supported by firmware always the same and it is left up to the user to
> make sure they do not clobber other devices' address spaces during use?

As I wrote in my previous message to Andy unfortunately we don't really
have a general ability to detect which GPIO device(s) should be configured
and how since it is very likely that these particular devices and their
pins that aren't used internally by the firmware aren't even configured
correctly.
And we can't simply enable all since these would possibly reconfigure
these that really should be managed by the firmware or that share pins
with other devices like UARTs.

> My suspicion is the latter, but I figure I may as well ask.
> 
> If so, I suggest simply allowing access to all supported GPIO to the
> user. My reasoning is this: the user is the one loading the module
> parameter, so they should already know which GPIO they intend to use --
> as such, if they wouldn't have requested it in your "gpios" module
> parameter, they won't use it when the gpiochip is registered. Thus, the
> "gpios" module parameter can be removed and ngpios simply set to the
> total number of supported GPIO.

As I wrote above, we can't blindly enable all GPIO ports.

> For example, the 104-IDIO-16 GPIO driver supports 32 GPIO lines because
> 104-IDIO-16 devices have 32 available GPIO lines. However, this same
> driver may be use for 104-IDIO-8 devices which are firmware compatible
> with 104-IDIO-16 devices. In this case, despite 104-IDIO-8 devices only
> having 16 GPIO lines, ngpios is still set to 32 because the user already
> knows that the upper 16 GPIO lines are not available for their device,
> despite the driver permitting access to them.
> 
> One problem I do see is how to handle your "ppgpios" and "odgpios"
> module parameters, which allow the configuration of push-pull mode and
> open drain mode respectfully. I would like to see these module
> parameters go aways as well and leave it up for the user to configure at
> runtime. Linus, does the GPIO subsystem have a method of dynamically
> setting these kind of pin configurations?

The problem here is that this device doesn't support per-pin output
driver configuration, only per-GPIO device (8 pins) configuration.

The GPIO subsystem exports an independent configuration for each pin,
so implementing a GPIO output driver configuration method this way
would be advertising a capability that the device doesn't really have.

> William Breathitt Gray

Maciej Szmigiero
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox series

Patch

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 395669bfcc26..3384a4675a0c 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -698,6 +698,21 @@  config GPIO_TS5500
 	  blocks of the TS-5500: DIO1, DIO2 and the LCD port, and the TS-5600
 	  LCD port.
 
+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.).
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called gpio-winbond.
+
 config GPIO_WS16C48
 	tristate "WinSystems WS16C48 GPIO support"
 	depends on ISA_BUS_API
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index bc5dd673fa11..ff3d36d0a443 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -139,6 +139,7 @@  obj-$(CONFIG_GPIO_VIPERBOARD)	+= gpio-viperboard.o
 obj-$(CONFIG_GPIO_VR41XX)	+= gpio-vr41xx.o
 obj-$(CONFIG_GPIO_VX855)	+= gpio-vx855.o
 obj-$(CONFIG_GPIO_WHISKEY_COVE)	+= gpio-wcove.o
+obj-$(CONFIG_GPIO_WINBOND)	+= gpio-winbond.o
 obj-$(CONFIG_GPIO_WM831X)	+= gpio-wm831x.o
 obj-$(CONFIG_GPIO_WM8350)	+= gpio-wm8350.o
 obj-$(CONFIG_GPIO_WM8994)	+= gpio-wm8994.o
diff --git a/drivers/gpio/gpio-winbond.c b/drivers/gpio/gpio-winbond.c
new file mode 100644
index 000000000000..385855fb6c9e
--- /dev/null
+++ b/drivers/gpio/gpio-winbond.c
@@ -0,0 +1,758 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * GPIO interface for Winbond Super I/O chips
+ * Currently, only W83627UHG (Nuvoton NCT6627UD) is supported.
+ *
+ * Author: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
+ */
+
+#include <linux/errno.h>
+#include <linux/gpio.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#define WB_GPIO_DRIVER_NAME "gpio-winbond"
+
+#define WB_SIO_BASE 0x2e
+#define WB_SIO_BASE_HIGH 0x4e
+
+#define WB_SIO_EXT_ENTER_KEY 0x87
+#define WB_SIO_EXT_EXIT_KEY 0xaa
+
+#define WB_SIO_CHIP_ID_W83627UHG 0xa230
+#define WB_SIO_CHIP_ID_W83627UHG_MASK 0xfff0
+
+/* 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
+
+#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
+#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
+#define WB_SIO_WDGPIO56_ENABLE_6 2
+#define WB_SIO_WDGPIO56_ENABLE_5 1
+#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
+
+#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;
+
+static int winbond_sio_enter(u16 base)
+{
+	if (request_muxed_region(base, 2, WB_GPIO_DRIVER_NAME) == NULL) {
+		pr_err(WB_GPIO_DRIVER_NAME ": cannot enter SIO at address %x\n",
+		       (unsigned int)base);
+		return -EBUSY;
+	}
+
+	outb(WB_SIO_EXT_ENTER_KEY, base);
+	outb(WB_SIO_EXT_ENTER_KEY, base);
+
+	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)
+{
+	outb(reg, base);
+	outb(data, base + 1);
+}
+
+static u8 winbond_sio_reg_read(u16 base, u8 reg)
+{
+	outb(reg, base);
+	return inb(base + 1);
+}
+
+static void winbond_sio_reg_bset(u16 base, u8 reg, u8 bit)
+{
+	u8 val;
+
+	val = winbond_sio_reg_read(base, reg);
+	val |= BIT(bit);
+	winbond_sio_reg_write(base, reg, val);
+}
+
+static void winbond_sio_reg_bclear(u16 base, u8 reg, u8 bit)
+{
+	u8 val;
+
+	val = winbond_sio_reg_read(base, reg);
+	val &= ~BIT(bit);
+	winbond_sio_reg_write(base, reg, val);
+}
+
+static bool winbond_sio_reg_btest(u16 base, u8 reg, u8 bit)
+{
+	return winbond_sio_reg_read(base, reg) & BIT(bit);
+}
+
+/**
+ * struct winbond_gpio_port_conflict - possibly conflicting device information
+ * @name:	device name (NULL means no conflicting device defined)
+ * @dev:	Super I/O logical device number where the testreg register
+ *		is located (or WB_SIO_DEV_NONE - don't select any
+ *		logical device)
+ * @testreg:	register number where the testbit bit is located
+ * @testbit:	index of a bit to check whether an actual conflict exists
+ * @warnonly:	if set then a conflict isn't fatal (just warn about it),
+ *		otherwise disable the particular GPIO port if a conflict
+ *		is detected
+ */
+struct winbond_gpio_port_conflict {
+	const char *name;
+	u8 dev;
+	u8 testreg;
+	u8 testbit;
+	bool warnonly;
+};
+
+/**
+ * struct winbond_gpio_info - information about a particular GPIO port (device)
+ * @dev:		Super I/O logical device number of the registers
+ *			specified below
+ * @enablereg:		port enable bit register number
+ * @enablebit:		index of a port enable bit
+ * @outputreg:		output driver mode bit register number
+ * @outputppbit:	index of a push-pull output driver mode bit
+ * @ioreg:		data direction register number
+ * @invreg:		pin data inversion register number
+ * @datareg:		pin data register number
+ * @conflict:		description of a device that possibly conflicts with
+ *			this port
+ */
+struct winbond_gpio_info {
+	u8 dev;
+	u8 enablereg;
+	u8 enablebit;
+	u8 outputreg;
+	u8 outputppbit;
+	u8 ioreg;
+	u8 invreg;
+	u8 datareg;
+	struct winbond_gpio_port_conflict conflict;
+};
+
+static const struct winbond_gpio_info winbond_gpio_infos[6] = {
+	{ /* 0 */
+		.dev = WB_SIO_DEV_GPIO12,
+		.enablereg = WB_SIO_GPIO12_REG_ENABLE,
+		.enablebit = WB_SIO_GPIO12_ENABLE_1,
+		.outputreg = WB_SIO_REG_GPIO1_MF,
+		.outputppbit = WB_SIO_REG_G1MF_G1PP,
+		.ioreg = WB_SIO_GPIO12_REG_IO1,
+		.invreg = WB_SIO_GPIO12_REG_INV1,
+		.datareg = WB_SIO_GPIO12_REG_DATA1,
+		.conflict = {
+			.name = "UARTB",
+			.dev = WB_SIO_DEV_UARTB,
+			.testreg = WB_SIO_UARTB_REG_ENABLE,
+			.testbit = WB_SIO_UARTB_ENABLE_ON,
+			.warnonly = true
+		}
+	},
+	{ /* 1 */
+		.dev = WB_SIO_DEV_GPIO12,
+		.enablereg = WB_SIO_GPIO12_REG_ENABLE,
+		.enablebit = WB_SIO_GPIO12_ENABLE_2,
+		.outputreg = WB_SIO_REG_GPIO1_MF,
+		.outputppbit = WB_SIO_REG_G1MF_G2PP,
+		.ioreg = WB_SIO_GPIO12_REG_IO2,
+		.invreg = WB_SIO_GPIO12_REG_INV2,
+		.datareg = WB_SIO_GPIO12_REG_DATA2
+		/* special conflict handling so doesn't use conflict data */
+	},
+	{ /* 2 */
+		.dev = WB_SIO_DEV_GPIO34,
+		.enablereg = WB_SIO_GPIO34_REG_ENABLE,
+		.enablebit = WB_SIO_GPIO34_ENABLE_3,
+		.outputreg = WB_SIO_REG_OVTGPIO3456,
+		.outputppbit = WB_SIO_REG_OG3456_G3PP,
+		.ioreg = WB_SIO_GPIO34_REG_IO3,
+		.invreg = WB_SIO_GPIO34_REG_INV3,
+		.datareg = WB_SIO_GPIO34_REG_DATA3,
+		.conflict = {
+			.name = "UARTC",
+			.dev = WB_SIO_DEV_UARTC,
+			.testreg = WB_SIO_UARTC_REG_ENABLE,
+			.testbit = WB_SIO_UARTC_ENABLE_ON,
+			.warnonly = true
+		}
+	},
+	{ /* 3 */
+		.dev = WB_SIO_DEV_GPIO34,
+		.enablereg = WB_SIO_GPIO34_REG_ENABLE,
+		.enablebit = WB_SIO_GPIO34_ENABLE_4,
+		.outputreg = WB_SIO_REG_OVTGPIO3456,
+		.outputppbit = WB_SIO_REG_OG3456_G4PP,
+		.ioreg = WB_SIO_GPIO34_REG_IO4,
+		.invreg = WB_SIO_GPIO34_REG_INV4,
+		.datareg = WB_SIO_GPIO34_REG_DATA4,
+		.conflict = {
+			.name = "UARTD",
+			.dev = WB_SIO_DEV_UARTD,
+			.testreg = WB_SIO_UARTD_REG_ENABLE,
+			.testbit = WB_SIO_UARTD_ENABLE_ON,
+			.warnonly = true
+		}
+	},
+	{ /* 4 */
+		.dev = WB_SIO_DEV_WDGPIO56,
+		.enablereg = WB_SIO_WDGPIO56_REG_ENABLE,
+		.enablebit = WB_SIO_WDGPIO56_ENABLE_5,
+		.outputreg = WB_SIO_REG_OVTGPIO3456,
+		.outputppbit = WB_SIO_REG_OG3456_G5PP,
+		.ioreg = WB_SIO_WDGPIO56_REG_IO5,
+		.invreg = WB_SIO_WDGPIO56_REG_INV5,
+		.datareg = WB_SIO_WDGPIO56_REG_DATA5,
+		.conflict = {
+			.name = "UARTE",
+			.dev = WB_SIO_DEV_UARTE,
+			.testreg = WB_SIO_UARTE_REG_ENABLE,
+			.testbit = WB_SIO_UARTE_ENABLE_ON,
+			.warnonly = true
+		}
+	},
+	{ /* 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;
+
+		if (gpio_num < 8)
+			break;
+
+		gpio_num -= 8;
+	}
+
+	/*
+	 * 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;
+
+	*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;
+	}
+
+	return allow_changing;
+}
+
+static int winbond_gpio_get(struct gpio_chip *gc, unsigned int gpio_num)
+{
+	u16 *base = gpiochip_get_data(gc);
+	const struct winbond_gpio_info *info;
+	bool val;
+
+	winbond_gpio_get_info(gpio_num, &info);
+	gpio_num %= 8;
+
+	val = winbond_sio_enter(*base);
+	if (val)
+		return val;
+
+	winbond_sio_select_logical(*base, info->dev);
+
+	val = winbond_sio_reg_btest(*base, info->datareg, gpio_num);
+	if (winbond_sio_reg_btest(*base, info->invreg, gpio_num))
+		val = !val;
+
+	winbond_sio_leave(*base);
+
+	return val;
+}
+
+static int winbond_gpio_direction_in(struct gpio_chip *gc,
+				     unsigned int gpio_num)
+{
+	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;
+
+	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);
+	const struct winbond_gpio_info *info;
+	int ret;
+
+	if (!winbond_gpio_get_info(gpio_num, &info))
+		return -EACCES;
+
+	gpio_num %= 8;
+
+	ret = winbond_sio_enter(*base);
+	if (ret)
+		return ret;
+
+	winbond_sio_select_logical(*base, info->dev);
+
+	winbond_sio_reg_bclear(*base, info->ioreg, gpio_num);
+	if (winbond_sio_reg_btest(*base, info->invreg, gpio_num))
+		val = !val;
+
+	if (val)
+		winbond_sio_reg_bset(*base, info->datareg, gpio_num);
+	else
+		winbond_sio_reg_bclear(*base, info->datareg, gpio_num);
+
+	winbond_sio_leave(*base);
+
+	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)
+		winbond_sio_reg_bset(*base, info->datareg, gpio_num);
+	else
+		winbond_sio_reg_bclear(*base, info->datareg, gpio_num);
+
+	winbond_sio_leave(*base);
+}
+
+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++)
+		if (gpios & BIT(i))
+			winbond_gpio_chip.ngpio += 8;
+
+	if (gpios & BIT(5))
+		winbond_gpio_chip.ngpio += 5;
+
+	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)
+{
+	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);
+
+	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 struct platform_driver winbond_gpio_pdriver = {
+	.driver = {
+		.name	= WB_GPIO_DRIVER_NAME,
+	},
+	.probe		= winbond_gpio_probe,
+};
+
+static int __init winbond_gpio_mod_init(void)
+{
+	int ret;
+
+	if (ppgpios & odgpios) {
+		pr_err(WB_GPIO_DRIVER_NAME
+			": 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;
+}
+
+static void __exit winbond_gpio_mod_exit(void)
+{
+	platform_device_unregister(winbond_gpio_pdev);
+	platform_driver_unregister(&winbond_gpio_pdriver);
+}
+
+module_init(winbond_gpio_mod_init);
+module_exit(winbond_gpio_mod_exit);
+
+/* This parameter sets which GPIO devices (ports) we enable */
+module_param(gpios, byte, 0444);
+MODULE_PARM_DESC(gpios,
+		 "bitmask of GPIO ports to enable (bit 0 - GPIO1, bit 1 - GPIO2, etc.");
+
+/*
+ * These two parameters below set how we configure GPIO ports output drivers.
+ * It can't be a one bitmask since we need three values per port: push-pull,
+ * open-drain and keep as-is (this is the default).
+ */
+module_param(ppgpios, byte, 0444);
+MODULE_PARM_DESC(ppgpios,
+		 "bitmask of GPIO ports to set to push-pull mode (bit 0 - GPIO1, bit 1 - GPIO2, etc.");
+
+module_param(odgpios, byte, 0444);
+MODULE_PARM_DESC(odgpios,
+		 "bitmask of GPIO ports to set to open drain mode (bit 0 - GPIO1, bit 1 - GPIO2, etc.");
+
+/*
+ * GPIO2.0 and GPIO2.1 control a basic PC functionality that we
+ * don't allow tinkering with by default (it is very likely that the
+ * firmware owns these pins).
+ * These two parameters below allow overriding these prohibitions.
+ */
+module_param(pledgpio, bool, 0644);
+MODULE_PARM_DESC(pledgpio,
+		 "enable changing value of GPIO2.0 bit (Power LED), default no.");
+
+module_param(beepgpio, bool, 0644);
+MODULE_PARM_DESC(beepgpio,
+		 "enable changing value of GPIO2.1 bit (BEEP), default no.");
+
+MODULE_AUTHOR("Maciej S. Szmigiero <mail@maciej.szmigiero.name>");
+MODULE_DESCRIPTION("GPIO interface for Winbond Super I/O chips");
+MODULE_LICENSE("GPL");