162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * GPIO driver for Fintek and Nuvoton Super-I/O chips
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2010-2013 LaCie
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Author: Simon Guinot <simon.guinot@sequanux.org>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#define DRVNAME "gpio-f7188x"
1162306a36Sopenharmony_ci#define pr_fmt(fmt) DRVNAME ": " fmt
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/init.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include <linux/io.h>
1762306a36Sopenharmony_ci#include <linux/gpio/driver.h>
1862306a36Sopenharmony_ci#include <linux/bitops.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/*
2162306a36Sopenharmony_ci * Super-I/O registers
2262306a36Sopenharmony_ci */
2362306a36Sopenharmony_ci#define SIO_LDSEL		0x07	/* Logical device select */
2462306a36Sopenharmony_ci#define SIO_DEVID		0x20	/* Device ID (2 bytes) */
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define SIO_UNLOCK_KEY		0x87	/* Key to enable Super-I/O */
2762306a36Sopenharmony_ci#define SIO_LOCK_KEY		0xAA	/* Key to disable Super-I/O */
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci/*
3062306a36Sopenharmony_ci * Fintek devices.
3162306a36Sopenharmony_ci */
3262306a36Sopenharmony_ci#define SIO_FINTEK_DEVREV	0x22	/* Fintek Device revision */
3362306a36Sopenharmony_ci#define SIO_FINTEK_MANID	0x23    /* Fintek ID (2 bytes) */
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci#define SIO_FINTEK_ID		0x1934  /* Manufacturer ID */
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#define SIO_F71869_ID		0x0814	/* F71869 chipset ID */
3862306a36Sopenharmony_ci#define SIO_F71869A_ID		0x1007	/* F71869A chipset ID */
3962306a36Sopenharmony_ci#define SIO_F71882_ID		0x0541	/* F71882 chipset ID */
4062306a36Sopenharmony_ci#define SIO_F71889_ID		0x0909	/* F71889 chipset ID */
4162306a36Sopenharmony_ci#define SIO_F71889A_ID		0x1005	/* F71889A chipset ID */
4262306a36Sopenharmony_ci#define SIO_F81866_ID		0x1010	/* F81866 chipset ID */
4362306a36Sopenharmony_ci#define SIO_F81804_ID		0x1502  /* F81804 chipset ID, same for F81966 */
4462306a36Sopenharmony_ci#define SIO_F81865_ID		0x0704	/* F81865 chipset ID */
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci#define SIO_LD_GPIO_FINTEK	0x06	/* GPIO logical device */
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci/*
4962306a36Sopenharmony_ci * Nuvoton devices.
5062306a36Sopenharmony_ci */
5162306a36Sopenharmony_ci#define SIO_NCT6126D_ID		0xD283  /* NCT6126D chipset ID */
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci#define SIO_LD_GPIO_NUVOTON	0x07	/* GPIO logical device */
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cienum chips {
5762306a36Sopenharmony_ci	f71869,
5862306a36Sopenharmony_ci	f71869a,
5962306a36Sopenharmony_ci	f71882fg,
6062306a36Sopenharmony_ci	f71889a,
6162306a36Sopenharmony_ci	f71889f,
6262306a36Sopenharmony_ci	f81866,
6362306a36Sopenharmony_ci	f81804,
6462306a36Sopenharmony_ci	f81865,
6562306a36Sopenharmony_ci	nct6126d,
6662306a36Sopenharmony_ci};
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistatic const char * const f7188x_names[] = {
6962306a36Sopenharmony_ci	"f71869",
7062306a36Sopenharmony_ci	"f71869a",
7162306a36Sopenharmony_ci	"f71882fg",
7262306a36Sopenharmony_ci	"f71889a",
7362306a36Sopenharmony_ci	"f71889f",
7462306a36Sopenharmony_ci	"f81866",
7562306a36Sopenharmony_ci	"f81804",
7662306a36Sopenharmony_ci	"f81865",
7762306a36Sopenharmony_ci	"nct6126d",
7862306a36Sopenharmony_ci};
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_cistruct f7188x_sio {
8162306a36Sopenharmony_ci	int addr;
8262306a36Sopenharmony_ci	int device;
8362306a36Sopenharmony_ci	enum chips type;
8462306a36Sopenharmony_ci};
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_cistruct f7188x_gpio_bank {
8762306a36Sopenharmony_ci	struct gpio_chip chip;
8862306a36Sopenharmony_ci	unsigned int regbase;
8962306a36Sopenharmony_ci	struct f7188x_gpio_data *data;
9062306a36Sopenharmony_ci};
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistruct f7188x_gpio_data {
9362306a36Sopenharmony_ci	struct f7188x_sio *sio;
9462306a36Sopenharmony_ci	int nr_bank;
9562306a36Sopenharmony_ci	struct f7188x_gpio_bank *bank;
9662306a36Sopenharmony_ci};
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci/*
9962306a36Sopenharmony_ci * Super-I/O functions.
10062306a36Sopenharmony_ci */
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_cistatic inline int superio_inb(int base, int reg)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	outb(reg, base);
10562306a36Sopenharmony_ci	return inb(base + 1);
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_cistatic int superio_inw(int base, int reg)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	int val;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	outb(reg++, base);
11362306a36Sopenharmony_ci	val = inb(base + 1) << 8;
11462306a36Sopenharmony_ci	outb(reg, base);
11562306a36Sopenharmony_ci	val |= inb(base + 1);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	return val;
11862306a36Sopenharmony_ci}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_cistatic inline void superio_outb(int base, int reg, int val)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	outb(reg, base);
12362306a36Sopenharmony_ci	outb(val, base + 1);
12462306a36Sopenharmony_ci}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_cistatic inline int superio_enter(int base)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	/* Don't step on other drivers' I/O space by accident. */
12962306a36Sopenharmony_ci	if (!request_muxed_region(base, 2, DRVNAME)) {
13062306a36Sopenharmony_ci		pr_err("I/O address 0x%04x already in use\n", base);
13162306a36Sopenharmony_ci		return -EBUSY;
13262306a36Sopenharmony_ci	}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	/* According to the datasheet the key must be send twice. */
13562306a36Sopenharmony_ci	outb(SIO_UNLOCK_KEY, base);
13662306a36Sopenharmony_ci	outb(SIO_UNLOCK_KEY, base);
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	return 0;
13962306a36Sopenharmony_ci}
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_cistatic inline void superio_select(int base, int ld)
14262306a36Sopenharmony_ci{
14362306a36Sopenharmony_ci	outb(SIO_LDSEL, base);
14462306a36Sopenharmony_ci	outb(ld, base + 1);
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_cistatic inline void superio_exit(int base)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	outb(SIO_LOCK_KEY, base);
15062306a36Sopenharmony_ci	release_region(base, 2);
15162306a36Sopenharmony_ci}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci/*
15462306a36Sopenharmony_ci * GPIO chip.
15562306a36Sopenharmony_ci */
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_cistatic int f7188x_gpio_get_direction(struct gpio_chip *chip, unsigned offset);
15862306a36Sopenharmony_cistatic int f7188x_gpio_direction_in(struct gpio_chip *chip, unsigned offset);
15962306a36Sopenharmony_cistatic int f7188x_gpio_get(struct gpio_chip *chip, unsigned offset);
16062306a36Sopenharmony_cistatic int f7188x_gpio_direction_out(struct gpio_chip *chip,
16162306a36Sopenharmony_ci				     unsigned offset, int value);
16262306a36Sopenharmony_cistatic void f7188x_gpio_set(struct gpio_chip *chip, unsigned offset, int value);
16362306a36Sopenharmony_cistatic int f7188x_gpio_set_config(struct gpio_chip *chip, unsigned offset,
16462306a36Sopenharmony_ci				  unsigned long config);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci#define F7188X_GPIO_BANK(_base, _ngpio, _regbase, _label)			\
16762306a36Sopenharmony_ci	{								\
16862306a36Sopenharmony_ci		.chip = {						\
16962306a36Sopenharmony_ci			.label            = _label,			\
17062306a36Sopenharmony_ci			.owner            = THIS_MODULE,		\
17162306a36Sopenharmony_ci			.get_direction    = f7188x_gpio_get_direction,	\
17262306a36Sopenharmony_ci			.direction_input  = f7188x_gpio_direction_in,	\
17362306a36Sopenharmony_ci			.get              = f7188x_gpio_get,		\
17462306a36Sopenharmony_ci			.direction_output = f7188x_gpio_direction_out,	\
17562306a36Sopenharmony_ci			.set              = f7188x_gpio_set,		\
17662306a36Sopenharmony_ci			.set_config	  = f7188x_gpio_set_config,	\
17762306a36Sopenharmony_ci			.base             = _base,			\
17862306a36Sopenharmony_ci			.ngpio            = _ngpio,			\
17962306a36Sopenharmony_ci			.can_sleep        = true,			\
18062306a36Sopenharmony_ci		},							\
18162306a36Sopenharmony_ci		.regbase = _regbase,					\
18262306a36Sopenharmony_ci	}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci#define f7188x_gpio_dir(base) ((base) + 0)
18562306a36Sopenharmony_ci#define f7188x_gpio_data_out(base) ((base) + 1)
18662306a36Sopenharmony_ci#define f7188x_gpio_data_in(base) ((base) + 2)
18762306a36Sopenharmony_ci/* Output mode register (0:open drain 1:push-pull). */
18862306a36Sopenharmony_ci#define f7188x_gpio_out_mode(base) ((base) + 3)
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci#define f7188x_gpio_dir_invert(type)	((type) == nct6126d)
19162306a36Sopenharmony_ci#define f7188x_gpio_data_single(type)	((type) == nct6126d)
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_cistatic struct f7188x_gpio_bank f71869_gpio_bank[] = {
19462306a36Sopenharmony_ci	F7188X_GPIO_BANK(0, 6, 0xF0, DRVNAME "-0"),
19562306a36Sopenharmony_ci	F7188X_GPIO_BANK(10, 8, 0xE0, DRVNAME "-1"),
19662306a36Sopenharmony_ci	F7188X_GPIO_BANK(20, 8, 0xD0, DRVNAME "-2"),
19762306a36Sopenharmony_ci	F7188X_GPIO_BANK(30, 8, 0xC0, DRVNAME "-3"),
19862306a36Sopenharmony_ci	F7188X_GPIO_BANK(40, 8, 0xB0, DRVNAME "-4"),
19962306a36Sopenharmony_ci	F7188X_GPIO_BANK(50, 5, 0xA0, DRVNAME "-5"),
20062306a36Sopenharmony_ci	F7188X_GPIO_BANK(60, 6, 0x90, DRVNAME "-6"),
20162306a36Sopenharmony_ci};
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_cistatic struct f7188x_gpio_bank f71869a_gpio_bank[] = {
20462306a36Sopenharmony_ci	F7188X_GPIO_BANK(0, 6, 0xF0, DRVNAME "-0"),
20562306a36Sopenharmony_ci	F7188X_GPIO_BANK(10, 8, 0xE0, DRVNAME "-1"),
20662306a36Sopenharmony_ci	F7188X_GPIO_BANK(20, 8, 0xD0, DRVNAME "-2"),
20762306a36Sopenharmony_ci	F7188X_GPIO_BANK(30, 8, 0xC0, DRVNAME "-3"),
20862306a36Sopenharmony_ci	F7188X_GPIO_BANK(40, 8, 0xB0, DRVNAME "-4"),
20962306a36Sopenharmony_ci	F7188X_GPIO_BANK(50, 5, 0xA0, DRVNAME "-5"),
21062306a36Sopenharmony_ci	F7188X_GPIO_BANK(60, 8, 0x90, DRVNAME "-6"),
21162306a36Sopenharmony_ci	F7188X_GPIO_BANK(70, 8, 0x80, DRVNAME "-7"),
21262306a36Sopenharmony_ci};
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_cistatic struct f7188x_gpio_bank f71882_gpio_bank[] = {
21562306a36Sopenharmony_ci	F7188X_GPIO_BANK(0, 8, 0xF0, DRVNAME "-0"),
21662306a36Sopenharmony_ci	F7188X_GPIO_BANK(10, 8, 0xE0, DRVNAME "-1"),
21762306a36Sopenharmony_ci	F7188X_GPIO_BANK(20, 8, 0xD0, DRVNAME "-2"),
21862306a36Sopenharmony_ci	F7188X_GPIO_BANK(30, 4, 0xC0, DRVNAME "-3"),
21962306a36Sopenharmony_ci	F7188X_GPIO_BANK(40, 4, 0xB0, DRVNAME "-4"),
22062306a36Sopenharmony_ci};
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_cistatic struct f7188x_gpio_bank f71889a_gpio_bank[] = {
22362306a36Sopenharmony_ci	F7188X_GPIO_BANK(0, 7, 0xF0, DRVNAME "-0"),
22462306a36Sopenharmony_ci	F7188X_GPIO_BANK(10, 7, 0xE0, DRVNAME "-1"),
22562306a36Sopenharmony_ci	F7188X_GPIO_BANK(20, 8, 0xD0, DRVNAME "-2"),
22662306a36Sopenharmony_ci	F7188X_GPIO_BANK(30, 8, 0xC0, DRVNAME "-3"),
22762306a36Sopenharmony_ci	F7188X_GPIO_BANK(40, 8, 0xB0, DRVNAME "-4"),
22862306a36Sopenharmony_ci	F7188X_GPIO_BANK(50, 5, 0xA0, DRVNAME "-5"),
22962306a36Sopenharmony_ci	F7188X_GPIO_BANK(60, 8, 0x90, DRVNAME "-6"),
23062306a36Sopenharmony_ci	F7188X_GPIO_BANK(70, 8, 0x80, DRVNAME "-7"),
23162306a36Sopenharmony_ci};
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_cistatic struct f7188x_gpio_bank f71889_gpio_bank[] = {
23462306a36Sopenharmony_ci	F7188X_GPIO_BANK(0, 7, 0xF0, DRVNAME "-0"),
23562306a36Sopenharmony_ci	F7188X_GPIO_BANK(10, 7, 0xE0, DRVNAME "-1"),
23662306a36Sopenharmony_ci	F7188X_GPIO_BANK(20, 8, 0xD0, DRVNAME "-2"),
23762306a36Sopenharmony_ci	F7188X_GPIO_BANK(30, 8, 0xC0, DRVNAME "-3"),
23862306a36Sopenharmony_ci	F7188X_GPIO_BANK(40, 8, 0xB0, DRVNAME "-4"),
23962306a36Sopenharmony_ci	F7188X_GPIO_BANK(50, 5, 0xA0, DRVNAME "-5"),
24062306a36Sopenharmony_ci	F7188X_GPIO_BANK(60, 8, 0x90, DRVNAME "-6"),
24162306a36Sopenharmony_ci	F7188X_GPIO_BANK(70, 8, 0x80, DRVNAME "-7"),
24262306a36Sopenharmony_ci};
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_cistatic struct f7188x_gpio_bank f81866_gpio_bank[] = {
24562306a36Sopenharmony_ci	F7188X_GPIO_BANK(0, 8, 0xF0, DRVNAME "-0"),
24662306a36Sopenharmony_ci	F7188X_GPIO_BANK(10, 8, 0xE0, DRVNAME "-1"),
24762306a36Sopenharmony_ci	F7188X_GPIO_BANK(20, 8, 0xD0, DRVNAME "-2"),
24862306a36Sopenharmony_ci	F7188X_GPIO_BANK(30, 8, 0xC0, DRVNAME "-3"),
24962306a36Sopenharmony_ci	F7188X_GPIO_BANK(40, 8, 0xB0, DRVNAME "-4"),
25062306a36Sopenharmony_ci	F7188X_GPIO_BANK(50, 8, 0xA0, DRVNAME "-5"),
25162306a36Sopenharmony_ci	F7188X_GPIO_BANK(60, 8, 0x90, DRVNAME "-6"),
25262306a36Sopenharmony_ci	F7188X_GPIO_BANK(70, 8, 0x80, DRVNAME "-7"),
25362306a36Sopenharmony_ci	F7188X_GPIO_BANK(80, 8, 0x88, DRVNAME "-8"),
25462306a36Sopenharmony_ci};
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_cistatic struct f7188x_gpio_bank f81804_gpio_bank[] = {
25862306a36Sopenharmony_ci	F7188X_GPIO_BANK(0, 8, 0xF0, DRVNAME "-0"),
25962306a36Sopenharmony_ci	F7188X_GPIO_BANK(10, 8, 0xE0, DRVNAME "-1"),
26062306a36Sopenharmony_ci	F7188X_GPIO_BANK(20, 8, 0xD0, DRVNAME "-2"),
26162306a36Sopenharmony_ci	F7188X_GPIO_BANK(50, 8, 0xA0, DRVNAME "-3"),
26262306a36Sopenharmony_ci	F7188X_GPIO_BANK(60, 8, 0x90, DRVNAME "-4"),
26362306a36Sopenharmony_ci	F7188X_GPIO_BANK(70, 8, 0x80, DRVNAME "-5"),
26462306a36Sopenharmony_ci	F7188X_GPIO_BANK(90, 8, 0x98, DRVNAME "-6"),
26562306a36Sopenharmony_ci};
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_cistatic struct f7188x_gpio_bank f81865_gpio_bank[] = {
26862306a36Sopenharmony_ci	F7188X_GPIO_BANK(0, 8, 0xF0, DRVNAME "-0"),
26962306a36Sopenharmony_ci	F7188X_GPIO_BANK(10, 8, 0xE0, DRVNAME "-1"),
27062306a36Sopenharmony_ci	F7188X_GPIO_BANK(20, 8, 0xD0, DRVNAME "-2"),
27162306a36Sopenharmony_ci	F7188X_GPIO_BANK(30, 8, 0xC0, DRVNAME "-3"),
27262306a36Sopenharmony_ci	F7188X_GPIO_BANK(40, 8, 0xB0, DRVNAME "-4"),
27362306a36Sopenharmony_ci	F7188X_GPIO_BANK(50, 8, 0xA0, DRVNAME "-5"),
27462306a36Sopenharmony_ci	F7188X_GPIO_BANK(60, 5, 0x90, DRVNAME "-6"),
27562306a36Sopenharmony_ci};
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_cistatic struct f7188x_gpio_bank nct6126d_gpio_bank[] = {
27862306a36Sopenharmony_ci	F7188X_GPIO_BANK(0, 8, 0xE0, DRVNAME "-0"),
27962306a36Sopenharmony_ci	F7188X_GPIO_BANK(10, 8, 0xE4, DRVNAME "-1"),
28062306a36Sopenharmony_ci	F7188X_GPIO_BANK(20, 8, 0xE8, DRVNAME "-2"),
28162306a36Sopenharmony_ci	F7188X_GPIO_BANK(30, 8, 0xEC, DRVNAME "-3"),
28262306a36Sopenharmony_ci	F7188X_GPIO_BANK(40, 8, 0xF0, DRVNAME "-4"),
28362306a36Sopenharmony_ci	F7188X_GPIO_BANK(50, 8, 0xF4, DRVNAME "-5"),
28462306a36Sopenharmony_ci	F7188X_GPIO_BANK(60, 8, 0xF8, DRVNAME "-6"),
28562306a36Sopenharmony_ci	F7188X_GPIO_BANK(70, 8, 0xFC, DRVNAME "-7"),
28662306a36Sopenharmony_ci};
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_cistatic int f7188x_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
28962306a36Sopenharmony_ci{
29062306a36Sopenharmony_ci	int err;
29162306a36Sopenharmony_ci	struct f7188x_gpio_bank *bank = gpiochip_get_data(chip);
29262306a36Sopenharmony_ci	struct f7188x_sio *sio = bank->data->sio;
29362306a36Sopenharmony_ci	u8 dir;
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	err = superio_enter(sio->addr);
29662306a36Sopenharmony_ci	if (err)
29762306a36Sopenharmony_ci		return err;
29862306a36Sopenharmony_ci	superio_select(sio->addr, sio->device);
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	dir = superio_inb(sio->addr, f7188x_gpio_dir(bank->regbase));
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	superio_exit(sio->addr);
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	if (f7188x_gpio_dir_invert(sio->type))
30562306a36Sopenharmony_ci		dir = ~dir;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	if (dir & BIT(offset))
30862306a36Sopenharmony_ci		return GPIO_LINE_DIRECTION_OUT;
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	return GPIO_LINE_DIRECTION_IN;
31162306a36Sopenharmony_ci}
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_cistatic int f7188x_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
31462306a36Sopenharmony_ci{
31562306a36Sopenharmony_ci	int err;
31662306a36Sopenharmony_ci	struct f7188x_gpio_bank *bank = gpiochip_get_data(chip);
31762306a36Sopenharmony_ci	struct f7188x_sio *sio = bank->data->sio;
31862306a36Sopenharmony_ci	u8 dir;
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	err = superio_enter(sio->addr);
32162306a36Sopenharmony_ci	if (err)
32262306a36Sopenharmony_ci		return err;
32362306a36Sopenharmony_ci	superio_select(sio->addr, sio->device);
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	dir = superio_inb(sio->addr, f7188x_gpio_dir(bank->regbase));
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	if (f7188x_gpio_dir_invert(sio->type))
32862306a36Sopenharmony_ci		dir |= BIT(offset);
32962306a36Sopenharmony_ci	else
33062306a36Sopenharmony_ci		dir &= ~BIT(offset);
33162306a36Sopenharmony_ci	superio_outb(sio->addr, f7188x_gpio_dir(bank->regbase), dir);
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci	superio_exit(sio->addr);
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	return 0;
33662306a36Sopenharmony_ci}
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_cistatic int f7188x_gpio_get(struct gpio_chip *chip, unsigned offset)
33962306a36Sopenharmony_ci{
34062306a36Sopenharmony_ci	int err;
34162306a36Sopenharmony_ci	struct f7188x_gpio_bank *bank = gpiochip_get_data(chip);
34262306a36Sopenharmony_ci	struct f7188x_sio *sio = bank->data->sio;
34362306a36Sopenharmony_ci	u8 dir, data;
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	err = superio_enter(sio->addr);
34662306a36Sopenharmony_ci	if (err)
34762306a36Sopenharmony_ci		return err;
34862306a36Sopenharmony_ci	superio_select(sio->addr, sio->device);
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	dir = superio_inb(sio->addr, f7188x_gpio_dir(bank->regbase));
35162306a36Sopenharmony_ci	dir = !!(dir & BIT(offset));
35262306a36Sopenharmony_ci	if (f7188x_gpio_data_single(sio->type) || dir)
35362306a36Sopenharmony_ci		data = superio_inb(sio->addr, f7188x_gpio_data_out(bank->regbase));
35462306a36Sopenharmony_ci	else
35562306a36Sopenharmony_ci		data = superio_inb(sio->addr, f7188x_gpio_data_in(bank->regbase));
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	superio_exit(sio->addr);
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	return !!(data & BIT(offset));
36062306a36Sopenharmony_ci}
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_cistatic int f7188x_gpio_direction_out(struct gpio_chip *chip,
36362306a36Sopenharmony_ci				     unsigned offset, int value)
36462306a36Sopenharmony_ci{
36562306a36Sopenharmony_ci	int err;
36662306a36Sopenharmony_ci	struct f7188x_gpio_bank *bank = gpiochip_get_data(chip);
36762306a36Sopenharmony_ci	struct f7188x_sio *sio = bank->data->sio;
36862306a36Sopenharmony_ci	u8 dir, data_out;
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci	err = superio_enter(sio->addr);
37162306a36Sopenharmony_ci	if (err)
37262306a36Sopenharmony_ci		return err;
37362306a36Sopenharmony_ci	superio_select(sio->addr, sio->device);
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	data_out = superio_inb(sio->addr, f7188x_gpio_data_out(bank->regbase));
37662306a36Sopenharmony_ci	if (value)
37762306a36Sopenharmony_ci		data_out |= BIT(offset);
37862306a36Sopenharmony_ci	else
37962306a36Sopenharmony_ci		data_out &= ~BIT(offset);
38062306a36Sopenharmony_ci	superio_outb(sio->addr, f7188x_gpio_data_out(bank->regbase), data_out);
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci	dir = superio_inb(sio->addr, f7188x_gpio_dir(bank->regbase));
38362306a36Sopenharmony_ci	if (f7188x_gpio_dir_invert(sio->type))
38462306a36Sopenharmony_ci		dir &= ~BIT(offset);
38562306a36Sopenharmony_ci	else
38662306a36Sopenharmony_ci		dir |= BIT(offset);
38762306a36Sopenharmony_ci	superio_outb(sio->addr, f7188x_gpio_dir(bank->regbase), dir);
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci	superio_exit(sio->addr);
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	return 0;
39262306a36Sopenharmony_ci}
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_cistatic void f7188x_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
39562306a36Sopenharmony_ci{
39662306a36Sopenharmony_ci	int err;
39762306a36Sopenharmony_ci	struct f7188x_gpio_bank *bank = gpiochip_get_data(chip);
39862306a36Sopenharmony_ci	struct f7188x_sio *sio = bank->data->sio;
39962306a36Sopenharmony_ci	u8 data_out;
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci	err = superio_enter(sio->addr);
40262306a36Sopenharmony_ci	if (err)
40362306a36Sopenharmony_ci		return;
40462306a36Sopenharmony_ci	superio_select(sio->addr, sio->device);
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	data_out = superio_inb(sio->addr, f7188x_gpio_data_out(bank->regbase));
40762306a36Sopenharmony_ci	if (value)
40862306a36Sopenharmony_ci		data_out |= BIT(offset);
40962306a36Sopenharmony_ci	else
41062306a36Sopenharmony_ci		data_out &= ~BIT(offset);
41162306a36Sopenharmony_ci	superio_outb(sio->addr, f7188x_gpio_data_out(bank->regbase), data_out);
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci	superio_exit(sio->addr);
41462306a36Sopenharmony_ci}
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_cistatic int f7188x_gpio_set_config(struct gpio_chip *chip, unsigned offset,
41762306a36Sopenharmony_ci				  unsigned long config)
41862306a36Sopenharmony_ci{
41962306a36Sopenharmony_ci	int err;
42062306a36Sopenharmony_ci	enum pin_config_param param = pinconf_to_config_param(config);
42162306a36Sopenharmony_ci	struct f7188x_gpio_bank *bank = gpiochip_get_data(chip);
42262306a36Sopenharmony_ci	struct f7188x_sio *sio = bank->data->sio;
42362306a36Sopenharmony_ci	u8 data;
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	if (param != PIN_CONFIG_DRIVE_OPEN_DRAIN &&
42662306a36Sopenharmony_ci	    param != PIN_CONFIG_DRIVE_PUSH_PULL)
42762306a36Sopenharmony_ci		return -ENOTSUPP;
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci	err = superio_enter(sio->addr);
43062306a36Sopenharmony_ci	if (err)
43162306a36Sopenharmony_ci		return err;
43262306a36Sopenharmony_ci	superio_select(sio->addr, sio->device);
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci	data = superio_inb(sio->addr, f7188x_gpio_out_mode(bank->regbase));
43562306a36Sopenharmony_ci	if (param == PIN_CONFIG_DRIVE_OPEN_DRAIN)
43662306a36Sopenharmony_ci		data &= ~BIT(offset);
43762306a36Sopenharmony_ci	else
43862306a36Sopenharmony_ci		data |= BIT(offset);
43962306a36Sopenharmony_ci	superio_outb(sio->addr, f7188x_gpio_out_mode(bank->regbase), data);
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci	superio_exit(sio->addr);
44262306a36Sopenharmony_ci	return 0;
44362306a36Sopenharmony_ci}
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci/*
44662306a36Sopenharmony_ci * Platform device and driver.
44762306a36Sopenharmony_ci */
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_cistatic int f7188x_gpio_probe(struct platform_device *pdev)
45062306a36Sopenharmony_ci{
45162306a36Sopenharmony_ci	int err;
45262306a36Sopenharmony_ci	int i;
45362306a36Sopenharmony_ci	struct f7188x_sio *sio = dev_get_platdata(&pdev->dev);
45462306a36Sopenharmony_ci	struct f7188x_gpio_data *data;
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
45762306a36Sopenharmony_ci	if (!data)
45862306a36Sopenharmony_ci		return -ENOMEM;
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci	switch (sio->type) {
46162306a36Sopenharmony_ci	case f71869:
46262306a36Sopenharmony_ci		data->nr_bank = ARRAY_SIZE(f71869_gpio_bank);
46362306a36Sopenharmony_ci		data->bank = f71869_gpio_bank;
46462306a36Sopenharmony_ci		break;
46562306a36Sopenharmony_ci	case f71869a:
46662306a36Sopenharmony_ci		data->nr_bank = ARRAY_SIZE(f71869a_gpio_bank);
46762306a36Sopenharmony_ci		data->bank = f71869a_gpio_bank;
46862306a36Sopenharmony_ci		break;
46962306a36Sopenharmony_ci	case f71882fg:
47062306a36Sopenharmony_ci		data->nr_bank = ARRAY_SIZE(f71882_gpio_bank);
47162306a36Sopenharmony_ci		data->bank = f71882_gpio_bank;
47262306a36Sopenharmony_ci		break;
47362306a36Sopenharmony_ci	case f71889a:
47462306a36Sopenharmony_ci		data->nr_bank = ARRAY_SIZE(f71889a_gpio_bank);
47562306a36Sopenharmony_ci		data->bank = f71889a_gpio_bank;
47662306a36Sopenharmony_ci		break;
47762306a36Sopenharmony_ci	case f71889f:
47862306a36Sopenharmony_ci		data->nr_bank = ARRAY_SIZE(f71889_gpio_bank);
47962306a36Sopenharmony_ci		data->bank = f71889_gpio_bank;
48062306a36Sopenharmony_ci		break;
48162306a36Sopenharmony_ci	case f81866:
48262306a36Sopenharmony_ci		data->nr_bank = ARRAY_SIZE(f81866_gpio_bank);
48362306a36Sopenharmony_ci		data->bank = f81866_gpio_bank;
48462306a36Sopenharmony_ci		break;
48562306a36Sopenharmony_ci	case  f81804:
48662306a36Sopenharmony_ci		data->nr_bank = ARRAY_SIZE(f81804_gpio_bank);
48762306a36Sopenharmony_ci		data->bank = f81804_gpio_bank;
48862306a36Sopenharmony_ci		break;
48962306a36Sopenharmony_ci	case f81865:
49062306a36Sopenharmony_ci		data->nr_bank = ARRAY_SIZE(f81865_gpio_bank);
49162306a36Sopenharmony_ci		data->bank = f81865_gpio_bank;
49262306a36Sopenharmony_ci		break;
49362306a36Sopenharmony_ci	case nct6126d:
49462306a36Sopenharmony_ci		data->nr_bank = ARRAY_SIZE(nct6126d_gpio_bank);
49562306a36Sopenharmony_ci		data->bank = nct6126d_gpio_bank;
49662306a36Sopenharmony_ci		break;
49762306a36Sopenharmony_ci	default:
49862306a36Sopenharmony_ci		return -ENODEV;
49962306a36Sopenharmony_ci	}
50062306a36Sopenharmony_ci	data->sio = sio;
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci	platform_set_drvdata(pdev, data);
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_ci	/* For each GPIO bank, register a GPIO chip. */
50562306a36Sopenharmony_ci	for (i = 0; i < data->nr_bank; i++) {
50662306a36Sopenharmony_ci		struct f7188x_gpio_bank *bank = &data->bank[i];
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_ci		bank->chip.parent = &pdev->dev;
50962306a36Sopenharmony_ci		bank->data = data;
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_ci		err = devm_gpiochip_add_data(&pdev->dev, &bank->chip, bank);
51262306a36Sopenharmony_ci		if (err) {
51362306a36Sopenharmony_ci			dev_err(&pdev->dev,
51462306a36Sopenharmony_ci				"Failed to register gpiochip %d: %d\n",
51562306a36Sopenharmony_ci				i, err);
51662306a36Sopenharmony_ci			return err;
51762306a36Sopenharmony_ci		}
51862306a36Sopenharmony_ci	}
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci	return 0;
52162306a36Sopenharmony_ci}
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_cistatic int __init f7188x_find(int addr, struct f7188x_sio *sio)
52462306a36Sopenharmony_ci{
52562306a36Sopenharmony_ci	int err;
52662306a36Sopenharmony_ci	u16 devid;
52762306a36Sopenharmony_ci	u16 manid;
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci	err = superio_enter(addr);
53062306a36Sopenharmony_ci	if (err)
53162306a36Sopenharmony_ci		return err;
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_ci	err = -ENODEV;
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ci	sio->device = SIO_LD_GPIO_FINTEK;
53662306a36Sopenharmony_ci	devid = superio_inw(addr, SIO_DEVID);
53762306a36Sopenharmony_ci	switch (devid) {
53862306a36Sopenharmony_ci	case SIO_F71869_ID:
53962306a36Sopenharmony_ci		sio->type = f71869;
54062306a36Sopenharmony_ci		break;
54162306a36Sopenharmony_ci	case SIO_F71869A_ID:
54262306a36Sopenharmony_ci		sio->type = f71869a;
54362306a36Sopenharmony_ci		break;
54462306a36Sopenharmony_ci	case SIO_F71882_ID:
54562306a36Sopenharmony_ci		sio->type = f71882fg;
54662306a36Sopenharmony_ci		break;
54762306a36Sopenharmony_ci	case SIO_F71889A_ID:
54862306a36Sopenharmony_ci		sio->type = f71889a;
54962306a36Sopenharmony_ci		break;
55062306a36Sopenharmony_ci	case SIO_F71889_ID:
55162306a36Sopenharmony_ci		sio->type = f71889f;
55262306a36Sopenharmony_ci		break;
55362306a36Sopenharmony_ci	case SIO_F81866_ID:
55462306a36Sopenharmony_ci		sio->type = f81866;
55562306a36Sopenharmony_ci		break;
55662306a36Sopenharmony_ci	case SIO_F81804_ID:
55762306a36Sopenharmony_ci		sio->type = f81804;
55862306a36Sopenharmony_ci		break;
55962306a36Sopenharmony_ci	case SIO_F81865_ID:
56062306a36Sopenharmony_ci		sio->type = f81865;
56162306a36Sopenharmony_ci		break;
56262306a36Sopenharmony_ci	case SIO_NCT6126D_ID:
56362306a36Sopenharmony_ci		sio->device = SIO_LD_GPIO_NUVOTON;
56462306a36Sopenharmony_ci		sio->type = nct6126d;
56562306a36Sopenharmony_ci		break;
56662306a36Sopenharmony_ci	default:
56762306a36Sopenharmony_ci		pr_info("Unsupported Fintek device 0x%04x\n", devid);
56862306a36Sopenharmony_ci		goto err;
56962306a36Sopenharmony_ci	}
57062306a36Sopenharmony_ci
57162306a36Sopenharmony_ci	/* double check manufacturer where possible */
57262306a36Sopenharmony_ci	if (sio->type != nct6126d) {
57362306a36Sopenharmony_ci		manid = superio_inw(addr, SIO_FINTEK_MANID);
57462306a36Sopenharmony_ci		if (manid != SIO_FINTEK_ID) {
57562306a36Sopenharmony_ci			pr_debug("Not a Fintek device at 0x%08x\n", addr);
57662306a36Sopenharmony_ci			goto err;
57762306a36Sopenharmony_ci		}
57862306a36Sopenharmony_ci	}
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ci	sio->addr = addr;
58162306a36Sopenharmony_ci	err = 0;
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_ci	pr_info("Found %s at %#x\n", f7188x_names[sio->type], (unsigned int)addr);
58462306a36Sopenharmony_ci	if (sio->type != nct6126d)
58562306a36Sopenharmony_ci		pr_info("   revision %d\n", superio_inb(addr, SIO_FINTEK_DEVREV));
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_cierr:
58862306a36Sopenharmony_ci	superio_exit(addr);
58962306a36Sopenharmony_ci	return err;
59062306a36Sopenharmony_ci}
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_cistatic struct platform_device *f7188x_gpio_pdev;
59362306a36Sopenharmony_ci
59462306a36Sopenharmony_cistatic int __init
59562306a36Sopenharmony_cif7188x_gpio_device_add(const struct f7188x_sio *sio)
59662306a36Sopenharmony_ci{
59762306a36Sopenharmony_ci	int err;
59862306a36Sopenharmony_ci
59962306a36Sopenharmony_ci	f7188x_gpio_pdev = platform_device_alloc(DRVNAME, -1);
60062306a36Sopenharmony_ci	if (!f7188x_gpio_pdev)
60162306a36Sopenharmony_ci		return -ENOMEM;
60262306a36Sopenharmony_ci
60362306a36Sopenharmony_ci	err = platform_device_add_data(f7188x_gpio_pdev,
60462306a36Sopenharmony_ci				       sio, sizeof(*sio));
60562306a36Sopenharmony_ci	if (err) {
60662306a36Sopenharmony_ci		pr_err("Platform data allocation failed\n");
60762306a36Sopenharmony_ci		goto err;
60862306a36Sopenharmony_ci	}
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci	err = platform_device_add(f7188x_gpio_pdev);
61162306a36Sopenharmony_ci	if (err) {
61262306a36Sopenharmony_ci		pr_err("Device addition failed\n");
61362306a36Sopenharmony_ci		goto err;
61462306a36Sopenharmony_ci	}
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_ci	return 0;
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_cierr:
61962306a36Sopenharmony_ci	platform_device_put(f7188x_gpio_pdev);
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_ci	return err;
62262306a36Sopenharmony_ci}
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_ci/*
62562306a36Sopenharmony_ci * Try to match a supported Fintek device by reading the (hard-wired)
62662306a36Sopenharmony_ci * configuration I/O ports. If available, then register both the platform
62762306a36Sopenharmony_ci * device and driver to support the GPIOs.
62862306a36Sopenharmony_ci */
62962306a36Sopenharmony_ci
63062306a36Sopenharmony_cistatic struct platform_driver f7188x_gpio_driver = {
63162306a36Sopenharmony_ci	.driver = {
63262306a36Sopenharmony_ci		.name	= DRVNAME,
63362306a36Sopenharmony_ci	},
63462306a36Sopenharmony_ci	.probe		= f7188x_gpio_probe,
63562306a36Sopenharmony_ci};
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_cistatic int __init f7188x_gpio_init(void)
63862306a36Sopenharmony_ci{
63962306a36Sopenharmony_ci	int err;
64062306a36Sopenharmony_ci	struct f7188x_sio sio;
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_ci	if (f7188x_find(0x2e, &sio) &&
64362306a36Sopenharmony_ci	    f7188x_find(0x4e, &sio))
64462306a36Sopenharmony_ci		return -ENODEV;
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci	err = platform_driver_register(&f7188x_gpio_driver);
64762306a36Sopenharmony_ci	if (!err) {
64862306a36Sopenharmony_ci		err = f7188x_gpio_device_add(&sio);
64962306a36Sopenharmony_ci		if (err)
65062306a36Sopenharmony_ci			platform_driver_unregister(&f7188x_gpio_driver);
65162306a36Sopenharmony_ci	}
65262306a36Sopenharmony_ci
65362306a36Sopenharmony_ci	return err;
65462306a36Sopenharmony_ci}
65562306a36Sopenharmony_cisubsys_initcall(f7188x_gpio_init);
65662306a36Sopenharmony_ci
65762306a36Sopenharmony_cistatic void __exit f7188x_gpio_exit(void)
65862306a36Sopenharmony_ci{
65962306a36Sopenharmony_ci	platform_device_unregister(f7188x_gpio_pdev);
66062306a36Sopenharmony_ci	platform_driver_unregister(&f7188x_gpio_driver);
66162306a36Sopenharmony_ci}
66262306a36Sopenharmony_cimodule_exit(f7188x_gpio_exit);
66362306a36Sopenharmony_ci
66462306a36Sopenharmony_ciMODULE_DESCRIPTION("GPIO driver for Super-I/O chips F71869, F71869A, F71882FG, F71889A, F71889F and F81866");
66562306a36Sopenharmony_ciMODULE_AUTHOR("Simon Guinot <simon.guinot@sequanux.org>");
66662306a36Sopenharmony_ciMODULE_LICENSE("GPL");
667