18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * GPIO driver for the SMSC SCH311x Super-I/O chips
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2013 Bruno Randolf <br1@einfach.org>
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * SuperIO functions and chip detection:
88c2ecf20Sopenharmony_ci * (c) Copyright 2008 Wim Van Sebroeck <wim@iguana.be>.
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/ioport.h>
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci#include <linux/kernel.h>
148c2ecf20Sopenharmony_ci#include <linux/init.h>
158c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
168c2ecf20Sopenharmony_ci#include <linux/gpio/driver.h>
178c2ecf20Sopenharmony_ci#include <linux/bitops.h>
188c2ecf20Sopenharmony_ci#include <linux/io.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#define DRV_NAME			"gpio-sch311x"
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci#define SCH311X_GPIO_CONF_DIR		BIT(0)
238c2ecf20Sopenharmony_ci#define SCH311X_GPIO_CONF_INVERT	BIT(1)
248c2ecf20Sopenharmony_ci#define SCH311X_GPIO_CONF_OPEN_DRAIN	BIT(7)
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci#define SIO_CONFIG_KEY_ENTER		0x55
278c2ecf20Sopenharmony_ci#define SIO_CONFIG_KEY_EXIT		0xaa
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#define GP1				0x4b
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistatic int sch311x_ioports[] = { 0x2e, 0x4e, 0x162e, 0x164e };
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_cistatic struct platform_device *sch311x_gpio_pdev;
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistruct sch311x_pdev_data {		/* platform device data */
368c2ecf20Sopenharmony_ci	unsigned short runtime_reg;	/* runtime register base address */
378c2ecf20Sopenharmony_ci};
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistruct sch311x_gpio_block {		/* one GPIO block runtime data */
408c2ecf20Sopenharmony_ci	struct gpio_chip chip;
418c2ecf20Sopenharmony_ci	unsigned short data_reg;	/* from definition below */
428c2ecf20Sopenharmony_ci	unsigned short *config_regs;	/* pointer to definition below */
438c2ecf20Sopenharmony_ci	unsigned short runtime_reg;	/* runtime register */
448c2ecf20Sopenharmony_ci	spinlock_t lock;		/* lock for this GPIO block */
458c2ecf20Sopenharmony_ci};
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_cistruct sch311x_gpio_priv {		/* driver private data */
488c2ecf20Sopenharmony_ci	struct sch311x_gpio_block blocks[6];
498c2ecf20Sopenharmony_ci};
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_cistruct sch311x_gpio_block_def {		/* register address definitions */
528c2ecf20Sopenharmony_ci	unsigned short data_reg;
538c2ecf20Sopenharmony_ci	unsigned short config_regs[8];
548c2ecf20Sopenharmony_ci	unsigned short base;
558c2ecf20Sopenharmony_ci};
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci/* Note: some GPIOs are not available, these are marked with 0x00 */
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_cistatic struct sch311x_gpio_block_def sch311x_gpio_blocks[] = {
608c2ecf20Sopenharmony_ci	{
618c2ecf20Sopenharmony_ci		.data_reg = 0x4b,	/* GP1 */
628c2ecf20Sopenharmony_ci		.config_regs = {0x23, 0x24, 0x25, 0x26, 0x27, 0x29, 0x2a, 0x2b},
638c2ecf20Sopenharmony_ci		.base = 10,
648c2ecf20Sopenharmony_ci	},
658c2ecf20Sopenharmony_ci	{
668c2ecf20Sopenharmony_ci		.data_reg = 0x4c,	/* GP2 */
678c2ecf20Sopenharmony_ci		.config_regs = {0x00, 0x2c, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x32},
688c2ecf20Sopenharmony_ci		.base = 20,
698c2ecf20Sopenharmony_ci	},
708c2ecf20Sopenharmony_ci	{
718c2ecf20Sopenharmony_ci		.data_reg = 0x4d,	/* GP3 */
728c2ecf20Sopenharmony_ci		.config_regs = {0x33, 0x34, 0x35, 0x36, 0x37, 0x00, 0x39, 0x3a},
738c2ecf20Sopenharmony_ci		.base = 30,
748c2ecf20Sopenharmony_ci	},
758c2ecf20Sopenharmony_ci	{
768c2ecf20Sopenharmony_ci		.data_reg = 0x4e,	/* GP4 */
778c2ecf20Sopenharmony_ci		.config_regs = {0x3b, 0x00, 0x3d, 0x00, 0x6e, 0x6f, 0x72, 0x73},
788c2ecf20Sopenharmony_ci		.base = 40,
798c2ecf20Sopenharmony_ci	},
808c2ecf20Sopenharmony_ci	{
818c2ecf20Sopenharmony_ci		.data_reg = 0x4f,	/* GP5 */
828c2ecf20Sopenharmony_ci		.config_regs = {0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46},
838c2ecf20Sopenharmony_ci		.base = 50,
848c2ecf20Sopenharmony_ci	},
858c2ecf20Sopenharmony_ci	{
868c2ecf20Sopenharmony_ci		.data_reg = 0x50,	/* GP6 */
878c2ecf20Sopenharmony_ci		.config_regs = {0x47, 0x48, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59},
888c2ecf20Sopenharmony_ci		.base = 60,
898c2ecf20Sopenharmony_ci	},
908c2ecf20Sopenharmony_ci};
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci/*
938c2ecf20Sopenharmony_ci *	Super-IO functions
948c2ecf20Sopenharmony_ci */
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_cistatic inline int sch311x_sio_enter(int sio_config_port)
978c2ecf20Sopenharmony_ci{
988c2ecf20Sopenharmony_ci	/* Don't step on other drivers' I/O space by accident. */
998c2ecf20Sopenharmony_ci	if (!request_muxed_region(sio_config_port, 2, DRV_NAME)) {
1008c2ecf20Sopenharmony_ci		pr_err(DRV_NAME "I/O address 0x%04x already in use\n",
1018c2ecf20Sopenharmony_ci		       sio_config_port);
1028c2ecf20Sopenharmony_ci		return -EBUSY;
1038c2ecf20Sopenharmony_ci	}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	outb(SIO_CONFIG_KEY_ENTER, sio_config_port);
1068c2ecf20Sopenharmony_ci	return 0;
1078c2ecf20Sopenharmony_ci}
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_cistatic inline void sch311x_sio_exit(int sio_config_port)
1108c2ecf20Sopenharmony_ci{
1118c2ecf20Sopenharmony_ci	outb(SIO_CONFIG_KEY_EXIT, sio_config_port);
1128c2ecf20Sopenharmony_ci	release_region(sio_config_port, 2);
1138c2ecf20Sopenharmony_ci}
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_cistatic inline int sch311x_sio_inb(int sio_config_port, int reg)
1168c2ecf20Sopenharmony_ci{
1178c2ecf20Sopenharmony_ci	outb(reg, sio_config_port);
1188c2ecf20Sopenharmony_ci	return inb(sio_config_port + 1);
1198c2ecf20Sopenharmony_ci}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_cistatic inline void sch311x_sio_outb(int sio_config_port, int reg, int val)
1228c2ecf20Sopenharmony_ci{
1238c2ecf20Sopenharmony_ci	outb(reg, sio_config_port);
1248c2ecf20Sopenharmony_ci	outb(val, sio_config_port + 1);
1258c2ecf20Sopenharmony_ci}
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci/*
1298c2ecf20Sopenharmony_ci *	GPIO functions
1308c2ecf20Sopenharmony_ci */
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_cistatic int sch311x_gpio_request(struct gpio_chip *chip, unsigned offset)
1338c2ecf20Sopenharmony_ci{
1348c2ecf20Sopenharmony_ci	struct sch311x_gpio_block *block = gpiochip_get_data(chip);
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	if (block->config_regs[offset] == 0) /* GPIO is not available */
1378c2ecf20Sopenharmony_ci		return -ENODEV;
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	if (!request_region(block->runtime_reg + block->config_regs[offset],
1408c2ecf20Sopenharmony_ci			    1, DRV_NAME)) {
1418c2ecf20Sopenharmony_ci		dev_err(chip->parent, "Failed to request region 0x%04x.\n",
1428c2ecf20Sopenharmony_ci			block->runtime_reg + block->config_regs[offset]);
1438c2ecf20Sopenharmony_ci		return -EBUSY;
1448c2ecf20Sopenharmony_ci	}
1458c2ecf20Sopenharmony_ci	return 0;
1468c2ecf20Sopenharmony_ci}
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_cistatic void sch311x_gpio_free(struct gpio_chip *chip, unsigned offset)
1498c2ecf20Sopenharmony_ci{
1508c2ecf20Sopenharmony_ci	struct sch311x_gpio_block *block = gpiochip_get_data(chip);
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	if (block->config_regs[offset] == 0) /* GPIO is not available */
1538c2ecf20Sopenharmony_ci		return;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	release_region(block->runtime_reg + block->config_regs[offset], 1);
1568c2ecf20Sopenharmony_ci}
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_cistatic int sch311x_gpio_get(struct gpio_chip *chip, unsigned offset)
1598c2ecf20Sopenharmony_ci{
1608c2ecf20Sopenharmony_ci	struct sch311x_gpio_block *block = gpiochip_get_data(chip);
1618c2ecf20Sopenharmony_ci	u8 data;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	spin_lock(&block->lock);
1648c2ecf20Sopenharmony_ci	data = inb(block->runtime_reg + block->data_reg);
1658c2ecf20Sopenharmony_ci	spin_unlock(&block->lock);
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	return !!(data & BIT(offset));
1688c2ecf20Sopenharmony_ci}
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_cistatic void __sch311x_gpio_set(struct sch311x_gpio_block *block,
1718c2ecf20Sopenharmony_ci			       unsigned offset, int value)
1728c2ecf20Sopenharmony_ci{
1738c2ecf20Sopenharmony_ci	u8 data = inb(block->runtime_reg + block->data_reg);
1748c2ecf20Sopenharmony_ci	if (value)
1758c2ecf20Sopenharmony_ci		data |= BIT(offset);
1768c2ecf20Sopenharmony_ci	else
1778c2ecf20Sopenharmony_ci		data &= ~BIT(offset);
1788c2ecf20Sopenharmony_ci	outb(data, block->runtime_reg + block->data_reg);
1798c2ecf20Sopenharmony_ci}
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_cistatic void sch311x_gpio_set(struct gpio_chip *chip, unsigned offset,
1828c2ecf20Sopenharmony_ci			     int value)
1838c2ecf20Sopenharmony_ci{
1848c2ecf20Sopenharmony_ci	struct sch311x_gpio_block *block = gpiochip_get_data(chip);
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	spin_lock(&block->lock);
1878c2ecf20Sopenharmony_ci	__sch311x_gpio_set(block, offset, value);
1888c2ecf20Sopenharmony_ci	spin_unlock(&block->lock);
1898c2ecf20Sopenharmony_ci}
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_cistatic int sch311x_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
1928c2ecf20Sopenharmony_ci{
1938c2ecf20Sopenharmony_ci	struct sch311x_gpio_block *block = gpiochip_get_data(chip);
1948c2ecf20Sopenharmony_ci	u8 data;
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	spin_lock(&block->lock);
1978c2ecf20Sopenharmony_ci	data = inb(block->runtime_reg + block->config_regs[offset]);
1988c2ecf20Sopenharmony_ci	data |= SCH311X_GPIO_CONF_DIR;
1998c2ecf20Sopenharmony_ci	outb(data, block->runtime_reg + block->config_regs[offset]);
2008c2ecf20Sopenharmony_ci	spin_unlock(&block->lock);
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	return 0;
2038c2ecf20Sopenharmony_ci}
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_cistatic int sch311x_gpio_direction_out(struct gpio_chip *chip, unsigned offset,
2068c2ecf20Sopenharmony_ci				      int value)
2078c2ecf20Sopenharmony_ci{
2088c2ecf20Sopenharmony_ci	struct sch311x_gpio_block *block = gpiochip_get_data(chip);
2098c2ecf20Sopenharmony_ci	u8 data;
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	spin_lock(&block->lock);
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	data = inb(block->runtime_reg + block->config_regs[offset]);
2148c2ecf20Sopenharmony_ci	data &= ~SCH311X_GPIO_CONF_DIR;
2158c2ecf20Sopenharmony_ci	outb(data, block->runtime_reg + block->config_regs[offset]);
2168c2ecf20Sopenharmony_ci	__sch311x_gpio_set(block, offset, value);
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	spin_unlock(&block->lock);
2198c2ecf20Sopenharmony_ci	return 0;
2208c2ecf20Sopenharmony_ci}
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_cistatic int sch311x_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
2238c2ecf20Sopenharmony_ci{
2248c2ecf20Sopenharmony_ci	struct sch311x_gpio_block *block = gpiochip_get_data(chip);
2258c2ecf20Sopenharmony_ci	u8 data;
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	spin_lock(&block->lock);
2288c2ecf20Sopenharmony_ci	data = inb(block->runtime_reg + block->config_regs[offset]);
2298c2ecf20Sopenharmony_ci	spin_unlock(&block->lock);
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	if (data & SCH311X_GPIO_CONF_DIR)
2328c2ecf20Sopenharmony_ci		return GPIO_LINE_DIRECTION_IN;
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	return GPIO_LINE_DIRECTION_OUT;
2358c2ecf20Sopenharmony_ci}
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_cistatic int sch311x_gpio_set_config(struct gpio_chip *chip, unsigned offset,
2388c2ecf20Sopenharmony_ci				   unsigned long config)
2398c2ecf20Sopenharmony_ci{
2408c2ecf20Sopenharmony_ci	struct sch311x_gpio_block *block = gpiochip_get_data(chip);
2418c2ecf20Sopenharmony_ci	enum pin_config_param param = pinconf_to_config_param(config);
2428c2ecf20Sopenharmony_ci	u8 data;
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	switch (param) {
2458c2ecf20Sopenharmony_ci	case PIN_CONFIG_DRIVE_OPEN_DRAIN:
2468c2ecf20Sopenharmony_ci		spin_lock(&block->lock);
2478c2ecf20Sopenharmony_ci		data = inb(block->runtime_reg + block->config_regs[offset]);
2488c2ecf20Sopenharmony_ci		data |= SCH311X_GPIO_CONF_OPEN_DRAIN;
2498c2ecf20Sopenharmony_ci		outb(data, block->runtime_reg + block->config_regs[offset]);
2508c2ecf20Sopenharmony_ci		spin_unlock(&block->lock);
2518c2ecf20Sopenharmony_ci		return 0;
2528c2ecf20Sopenharmony_ci	case PIN_CONFIG_DRIVE_PUSH_PULL:
2538c2ecf20Sopenharmony_ci		spin_lock(&block->lock);
2548c2ecf20Sopenharmony_ci		data = inb(block->runtime_reg + block->config_regs[offset]);
2558c2ecf20Sopenharmony_ci		data &= ~SCH311X_GPIO_CONF_OPEN_DRAIN;
2568c2ecf20Sopenharmony_ci		outb(data, block->runtime_reg + block->config_regs[offset]);
2578c2ecf20Sopenharmony_ci		spin_unlock(&block->lock);
2588c2ecf20Sopenharmony_ci		return 0;
2598c2ecf20Sopenharmony_ci	default:
2608c2ecf20Sopenharmony_ci		break;
2618c2ecf20Sopenharmony_ci	}
2628c2ecf20Sopenharmony_ci	return -ENOTSUPP;
2638c2ecf20Sopenharmony_ci}
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_cistatic int sch311x_gpio_probe(struct platform_device *pdev)
2668c2ecf20Sopenharmony_ci{
2678c2ecf20Sopenharmony_ci	struct sch311x_pdev_data *pdata = dev_get_platdata(&pdev->dev);
2688c2ecf20Sopenharmony_ci	struct sch311x_gpio_priv *priv;
2698c2ecf20Sopenharmony_ci	struct sch311x_gpio_block *block;
2708c2ecf20Sopenharmony_ci	int err, i;
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci	/* we can register all GPIO data registers at once */
2738c2ecf20Sopenharmony_ci	if (!devm_request_region(&pdev->dev, pdata->runtime_reg + GP1, 6,
2748c2ecf20Sopenharmony_ci		DRV_NAME)) {
2758c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Failed to request region 0x%04x-0x%04x.\n",
2768c2ecf20Sopenharmony_ci			pdata->runtime_reg + GP1, pdata->runtime_reg + GP1 + 5);
2778c2ecf20Sopenharmony_ci		return -EBUSY;
2788c2ecf20Sopenharmony_ci	}
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
2818c2ecf20Sopenharmony_ci	if (!priv)
2828c2ecf20Sopenharmony_ci		return -ENOMEM;
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, priv);
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(priv->blocks); i++) {
2878c2ecf20Sopenharmony_ci		block = &priv->blocks[i];
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_ci		spin_lock_init(&block->lock);
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_ci		block->chip.label = DRV_NAME;
2928c2ecf20Sopenharmony_ci		block->chip.owner = THIS_MODULE;
2938c2ecf20Sopenharmony_ci		block->chip.request = sch311x_gpio_request;
2948c2ecf20Sopenharmony_ci		block->chip.free = sch311x_gpio_free;
2958c2ecf20Sopenharmony_ci		block->chip.direction_input = sch311x_gpio_direction_in;
2968c2ecf20Sopenharmony_ci		block->chip.direction_output = sch311x_gpio_direction_out;
2978c2ecf20Sopenharmony_ci		block->chip.get_direction = sch311x_gpio_get_direction;
2988c2ecf20Sopenharmony_ci		block->chip.set_config = sch311x_gpio_set_config;
2998c2ecf20Sopenharmony_ci		block->chip.get = sch311x_gpio_get;
3008c2ecf20Sopenharmony_ci		block->chip.set = sch311x_gpio_set;
3018c2ecf20Sopenharmony_ci		block->chip.ngpio = 8;
3028c2ecf20Sopenharmony_ci		block->chip.parent = &pdev->dev;
3038c2ecf20Sopenharmony_ci		block->chip.base = sch311x_gpio_blocks[i].base;
3048c2ecf20Sopenharmony_ci		block->config_regs = sch311x_gpio_blocks[i].config_regs;
3058c2ecf20Sopenharmony_ci		block->data_reg = sch311x_gpio_blocks[i].data_reg;
3068c2ecf20Sopenharmony_ci		block->runtime_reg = pdata->runtime_reg;
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci		err = gpiochip_add_data(&block->chip, block);
3098c2ecf20Sopenharmony_ci		if (err < 0) {
3108c2ecf20Sopenharmony_ci			dev_err(&pdev->dev,
3118c2ecf20Sopenharmony_ci				"Could not register gpiochip, %d\n", err);
3128c2ecf20Sopenharmony_ci			goto exit_err;
3138c2ecf20Sopenharmony_ci		}
3148c2ecf20Sopenharmony_ci		dev_info(&pdev->dev,
3158c2ecf20Sopenharmony_ci			 "SMSC SCH311x GPIO block %d registered.\n", i);
3168c2ecf20Sopenharmony_ci	}
3178c2ecf20Sopenharmony_ci
3188c2ecf20Sopenharmony_ci	return 0;
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ciexit_err:
3218c2ecf20Sopenharmony_ci	/* release already registered chips */
3228c2ecf20Sopenharmony_ci	for (--i; i >= 0; i--)
3238c2ecf20Sopenharmony_ci		gpiochip_remove(&priv->blocks[i].chip);
3248c2ecf20Sopenharmony_ci	return err;
3258c2ecf20Sopenharmony_ci}
3268c2ecf20Sopenharmony_ci
3278c2ecf20Sopenharmony_cistatic int sch311x_gpio_remove(struct platform_device *pdev)
3288c2ecf20Sopenharmony_ci{
3298c2ecf20Sopenharmony_ci	struct sch311x_gpio_priv *priv = platform_get_drvdata(pdev);
3308c2ecf20Sopenharmony_ci	int i;
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(priv->blocks); i++) {
3338c2ecf20Sopenharmony_ci		gpiochip_remove(&priv->blocks[i].chip);
3348c2ecf20Sopenharmony_ci		dev_info(&pdev->dev,
3358c2ecf20Sopenharmony_ci			 "SMSC SCH311x GPIO block %d unregistered.\n", i);
3368c2ecf20Sopenharmony_ci	}
3378c2ecf20Sopenharmony_ci	return 0;
3388c2ecf20Sopenharmony_ci}
3398c2ecf20Sopenharmony_ci
3408c2ecf20Sopenharmony_cistatic struct platform_driver sch311x_gpio_driver = {
3418c2ecf20Sopenharmony_ci	.driver.name	= DRV_NAME,
3428c2ecf20Sopenharmony_ci	.probe		= sch311x_gpio_probe,
3438c2ecf20Sopenharmony_ci	.remove		= sch311x_gpio_remove,
3448c2ecf20Sopenharmony_ci};
3458c2ecf20Sopenharmony_ci
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_ci/*
3488c2ecf20Sopenharmony_ci *	Init & exit routines
3498c2ecf20Sopenharmony_ci */
3508c2ecf20Sopenharmony_ci
3518c2ecf20Sopenharmony_cistatic int __init sch311x_detect(int sio_config_port, unsigned short *addr)
3528c2ecf20Sopenharmony_ci{
3538c2ecf20Sopenharmony_ci	int err = 0, reg;
3548c2ecf20Sopenharmony_ci	unsigned short base_addr;
3558c2ecf20Sopenharmony_ci	u8 dev_id;
3568c2ecf20Sopenharmony_ci
3578c2ecf20Sopenharmony_ci	err = sch311x_sio_enter(sio_config_port);
3588c2ecf20Sopenharmony_ci	if (err)
3598c2ecf20Sopenharmony_ci		return err;
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	/* Check device ID. */
3628c2ecf20Sopenharmony_ci	reg = sch311x_sio_inb(sio_config_port, 0x20);
3638c2ecf20Sopenharmony_ci	switch (reg) {
3648c2ecf20Sopenharmony_ci	case 0x7c: /* SCH3112 */
3658c2ecf20Sopenharmony_ci		dev_id = 2;
3668c2ecf20Sopenharmony_ci		break;
3678c2ecf20Sopenharmony_ci	case 0x7d: /* SCH3114 */
3688c2ecf20Sopenharmony_ci		dev_id = 4;
3698c2ecf20Sopenharmony_ci		break;
3708c2ecf20Sopenharmony_ci	case 0x7f: /* SCH3116 */
3718c2ecf20Sopenharmony_ci		dev_id = 6;
3728c2ecf20Sopenharmony_ci		break;
3738c2ecf20Sopenharmony_ci	default:
3748c2ecf20Sopenharmony_ci		err = -ENODEV;
3758c2ecf20Sopenharmony_ci		goto exit;
3768c2ecf20Sopenharmony_ci	}
3778c2ecf20Sopenharmony_ci
3788c2ecf20Sopenharmony_ci	/* Select logical device A (runtime registers) */
3798c2ecf20Sopenharmony_ci	sch311x_sio_outb(sio_config_port, 0x07, 0x0a);
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_ci	/* Check if Logical Device Register is currently active */
3828c2ecf20Sopenharmony_ci	if ((sch311x_sio_inb(sio_config_port, 0x30) & 0x01) == 0)
3838c2ecf20Sopenharmony_ci		pr_info("Seems that LDN 0x0a is not active...\n");
3848c2ecf20Sopenharmony_ci
3858c2ecf20Sopenharmony_ci	/* Get the base address of the runtime registers */
3868c2ecf20Sopenharmony_ci	base_addr = (sch311x_sio_inb(sio_config_port, 0x60) << 8) |
3878c2ecf20Sopenharmony_ci			   sch311x_sio_inb(sio_config_port, 0x61);
3888c2ecf20Sopenharmony_ci	if (!base_addr) {
3898c2ecf20Sopenharmony_ci		pr_err("Base address not set\n");
3908c2ecf20Sopenharmony_ci		err = -ENODEV;
3918c2ecf20Sopenharmony_ci		goto exit;
3928c2ecf20Sopenharmony_ci	}
3938c2ecf20Sopenharmony_ci	*addr = base_addr;
3948c2ecf20Sopenharmony_ci
3958c2ecf20Sopenharmony_ci	pr_info("Found an SMSC SCH311%d chip at 0x%04x\n", dev_id, base_addr);
3968c2ecf20Sopenharmony_ci
3978c2ecf20Sopenharmony_ciexit:
3988c2ecf20Sopenharmony_ci	sch311x_sio_exit(sio_config_port);
3998c2ecf20Sopenharmony_ci	return err;
4008c2ecf20Sopenharmony_ci}
4018c2ecf20Sopenharmony_ci
4028c2ecf20Sopenharmony_cistatic int __init sch311x_gpio_pdev_add(const unsigned short addr)
4038c2ecf20Sopenharmony_ci{
4048c2ecf20Sopenharmony_ci	struct sch311x_pdev_data pdata;
4058c2ecf20Sopenharmony_ci	int err;
4068c2ecf20Sopenharmony_ci
4078c2ecf20Sopenharmony_ci	pdata.runtime_reg = addr;
4088c2ecf20Sopenharmony_ci
4098c2ecf20Sopenharmony_ci	sch311x_gpio_pdev = platform_device_alloc(DRV_NAME, -1);
4108c2ecf20Sopenharmony_ci	if (!sch311x_gpio_pdev)
4118c2ecf20Sopenharmony_ci		return -ENOMEM;
4128c2ecf20Sopenharmony_ci
4138c2ecf20Sopenharmony_ci	err = platform_device_add_data(sch311x_gpio_pdev,
4148c2ecf20Sopenharmony_ci				       &pdata, sizeof(pdata));
4158c2ecf20Sopenharmony_ci	if (err) {
4168c2ecf20Sopenharmony_ci		pr_err(DRV_NAME "Platform data allocation failed\n");
4178c2ecf20Sopenharmony_ci		goto err;
4188c2ecf20Sopenharmony_ci	}
4198c2ecf20Sopenharmony_ci
4208c2ecf20Sopenharmony_ci	err = platform_device_add(sch311x_gpio_pdev);
4218c2ecf20Sopenharmony_ci	if (err) {
4228c2ecf20Sopenharmony_ci		pr_err(DRV_NAME "Device addition failed\n");
4238c2ecf20Sopenharmony_ci		goto err;
4248c2ecf20Sopenharmony_ci	}
4258c2ecf20Sopenharmony_ci	return 0;
4268c2ecf20Sopenharmony_ci
4278c2ecf20Sopenharmony_cierr:
4288c2ecf20Sopenharmony_ci	platform_device_put(sch311x_gpio_pdev);
4298c2ecf20Sopenharmony_ci	return err;
4308c2ecf20Sopenharmony_ci}
4318c2ecf20Sopenharmony_ci
4328c2ecf20Sopenharmony_cistatic int __init sch311x_gpio_init(void)
4338c2ecf20Sopenharmony_ci{
4348c2ecf20Sopenharmony_ci	int err, i;
4358c2ecf20Sopenharmony_ci	unsigned short addr = 0;
4368c2ecf20Sopenharmony_ci
4378c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(sch311x_ioports); i++)
4388c2ecf20Sopenharmony_ci		if (sch311x_detect(sch311x_ioports[i], &addr) == 0)
4398c2ecf20Sopenharmony_ci			break;
4408c2ecf20Sopenharmony_ci
4418c2ecf20Sopenharmony_ci	if (!addr)
4428c2ecf20Sopenharmony_ci		return -ENODEV;
4438c2ecf20Sopenharmony_ci
4448c2ecf20Sopenharmony_ci	err = platform_driver_register(&sch311x_gpio_driver);
4458c2ecf20Sopenharmony_ci	if (err)
4468c2ecf20Sopenharmony_ci		return err;
4478c2ecf20Sopenharmony_ci
4488c2ecf20Sopenharmony_ci	err = sch311x_gpio_pdev_add(addr);
4498c2ecf20Sopenharmony_ci	if (err)
4508c2ecf20Sopenharmony_ci		goto unreg_platform_driver;
4518c2ecf20Sopenharmony_ci
4528c2ecf20Sopenharmony_ci	return 0;
4538c2ecf20Sopenharmony_ci
4548c2ecf20Sopenharmony_ciunreg_platform_driver:
4558c2ecf20Sopenharmony_ci	platform_driver_unregister(&sch311x_gpio_driver);
4568c2ecf20Sopenharmony_ci	return err;
4578c2ecf20Sopenharmony_ci}
4588c2ecf20Sopenharmony_ci
4598c2ecf20Sopenharmony_cistatic void __exit sch311x_gpio_exit(void)
4608c2ecf20Sopenharmony_ci{
4618c2ecf20Sopenharmony_ci	platform_device_unregister(sch311x_gpio_pdev);
4628c2ecf20Sopenharmony_ci	platform_driver_unregister(&sch311x_gpio_driver);
4638c2ecf20Sopenharmony_ci}
4648c2ecf20Sopenharmony_ci
4658c2ecf20Sopenharmony_cimodule_init(sch311x_gpio_init);
4668c2ecf20Sopenharmony_cimodule_exit(sch311x_gpio_exit);
4678c2ecf20Sopenharmony_ci
4688c2ecf20Sopenharmony_ciMODULE_AUTHOR("Bruno Randolf <br1@einfach.org>");
4698c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("SMSC SCH311x GPIO Driver");
4708c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
4718c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:gpio-sch311x");
472