18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/**
38c2ecf20Sopenharmony_ci * Copyright (C) 2006 Juergen Beisert, Pengutronix
48c2ecf20Sopenharmony_ci * Copyright (C) 2008 Guennadi Liakhovetski, Pengutronix
58c2ecf20Sopenharmony_ci * Copyright (C) 2009 Wolfram Sang, Pengutronix
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * The Maxim MAX7300/1 device is an I2C/SPI driven GPIO expander. There are
88c2ecf20Sopenharmony_ci * 28 GPIOs. 8 of them can trigger an interrupt. See datasheet for more
98c2ecf20Sopenharmony_ci * details
108c2ecf20Sopenharmony_ci * Note:
118c2ecf20Sopenharmony_ci * - DIN must be stable at the rising edge of clock.
128c2ecf20Sopenharmony_ci * - when writing:
138c2ecf20Sopenharmony_ci *   - always clock in 16 clocks at once
148c2ecf20Sopenharmony_ci *   - at DIN: D15 first, D0 last
158c2ecf20Sopenharmony_ci *   - D0..D7 = databyte, D8..D14 = commandbyte
168c2ecf20Sopenharmony_ci *   - D15 = low -> write command
178c2ecf20Sopenharmony_ci * - when reading
188c2ecf20Sopenharmony_ci *   - always clock in 16 clocks at once
198c2ecf20Sopenharmony_ci *   - at DIN: D15 first, D0 last
208c2ecf20Sopenharmony_ci *   - D0..D7 = dummy, D8..D14 = register address
218c2ecf20Sopenharmony_ci *   - D15 = high -> read command
228c2ecf20Sopenharmony_ci *   - raise CS and assert it again
238c2ecf20Sopenharmony_ci *   - always clock in 16 clocks at once
248c2ecf20Sopenharmony_ci *   - at DOUT: D15 first, D0 last
258c2ecf20Sopenharmony_ci *   - D0..D7 contains the data from the first cycle
268c2ecf20Sopenharmony_ci *
278c2ecf20Sopenharmony_ci * The driver exports a standard gpiochip interface
288c2ecf20Sopenharmony_ci */
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci#include <linux/module.h>
318c2ecf20Sopenharmony_ci#include <linux/init.h>
328c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
338c2ecf20Sopenharmony_ci#include <linux/mutex.h>
348c2ecf20Sopenharmony_ci#include <linux/spi/max7301.h>
358c2ecf20Sopenharmony_ci#include <linux/gpio/driver.h>
368c2ecf20Sopenharmony_ci#include <linux/slab.h>
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci/*
398c2ecf20Sopenharmony_ci * Pin configurations, see MAX7301 datasheet page 6
408c2ecf20Sopenharmony_ci */
418c2ecf20Sopenharmony_ci#define PIN_CONFIG_MASK 0x03
428c2ecf20Sopenharmony_ci#define PIN_CONFIG_IN_PULLUP 0x03
438c2ecf20Sopenharmony_ci#define PIN_CONFIG_IN_WO_PULLUP 0x02
448c2ecf20Sopenharmony_ci#define PIN_CONFIG_OUT 0x01
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci#define PIN_NUMBER 28
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_cistatic int max7301_direction_input(struct gpio_chip *chip, unsigned offset)
498c2ecf20Sopenharmony_ci{
508c2ecf20Sopenharmony_ci	struct max7301 *ts = container_of(chip, struct max7301, chip);
518c2ecf20Sopenharmony_ci	u8 *config;
528c2ecf20Sopenharmony_ci	u8 offset_bits, pin_config;
538c2ecf20Sopenharmony_ci	int ret;
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	/* First 4 pins are unused in the controller */
568c2ecf20Sopenharmony_ci	offset += 4;
578c2ecf20Sopenharmony_ci	offset_bits = (offset & 3) << 1;
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci	config = &ts->port_config[offset >> 2];
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	if (ts->input_pullup_active & BIT(offset))
628c2ecf20Sopenharmony_ci		pin_config = PIN_CONFIG_IN_PULLUP;
638c2ecf20Sopenharmony_ci	else
648c2ecf20Sopenharmony_ci		pin_config = PIN_CONFIG_IN_WO_PULLUP;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	mutex_lock(&ts->lock);
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	*config = (*config & ~(PIN_CONFIG_MASK << offset_bits))
698c2ecf20Sopenharmony_ci			   | (pin_config << offset_bits);
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	ret = ts->write(ts->dev, 0x08 + (offset >> 2), *config);
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	mutex_unlock(&ts->lock);
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	return ret;
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_cistatic int __max7301_set(struct max7301 *ts, unsigned offset, int value)
798c2ecf20Sopenharmony_ci{
808c2ecf20Sopenharmony_ci	if (value) {
818c2ecf20Sopenharmony_ci		ts->out_level |= 1 << offset;
828c2ecf20Sopenharmony_ci		return ts->write(ts->dev, 0x20 + offset, 0x01);
838c2ecf20Sopenharmony_ci	} else {
848c2ecf20Sopenharmony_ci		ts->out_level &= ~(1 << offset);
858c2ecf20Sopenharmony_ci		return ts->write(ts->dev, 0x20 + offset, 0x00);
868c2ecf20Sopenharmony_ci	}
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_cistatic int max7301_direction_output(struct gpio_chip *chip, unsigned offset,
908c2ecf20Sopenharmony_ci				    int value)
918c2ecf20Sopenharmony_ci{
928c2ecf20Sopenharmony_ci	struct max7301 *ts = container_of(chip, struct max7301, chip);
938c2ecf20Sopenharmony_ci	u8 *config;
948c2ecf20Sopenharmony_ci	u8 offset_bits;
958c2ecf20Sopenharmony_ci	int ret;
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	/* First 4 pins are unused in the controller */
988c2ecf20Sopenharmony_ci	offset += 4;
998c2ecf20Sopenharmony_ci	offset_bits = (offset & 3) << 1;
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	config = &ts->port_config[offset >> 2];
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	mutex_lock(&ts->lock);
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	*config = (*config & ~(PIN_CONFIG_MASK << offset_bits))
1068c2ecf20Sopenharmony_ci			   | (PIN_CONFIG_OUT << offset_bits);
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	ret = __max7301_set(ts, offset, value);
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	if (!ret)
1118c2ecf20Sopenharmony_ci		ret = ts->write(ts->dev, 0x08 + (offset >> 2), *config);
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	mutex_unlock(&ts->lock);
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	return ret;
1168c2ecf20Sopenharmony_ci}
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_cistatic int max7301_get(struct gpio_chip *chip, unsigned offset)
1198c2ecf20Sopenharmony_ci{
1208c2ecf20Sopenharmony_ci	struct max7301 *ts = gpiochip_get_data(chip);
1218c2ecf20Sopenharmony_ci	int config, level = -EINVAL;
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	/* First 4 pins are unused in the controller */
1248c2ecf20Sopenharmony_ci	offset += 4;
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	mutex_lock(&ts->lock);
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	config = (ts->port_config[offset >> 2] >> ((offset & 3) << 1))
1298c2ecf20Sopenharmony_ci			& PIN_CONFIG_MASK;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	switch (config) {
1328c2ecf20Sopenharmony_ci	case PIN_CONFIG_OUT:
1338c2ecf20Sopenharmony_ci		/* Output: return cached level */
1348c2ecf20Sopenharmony_ci		level =  !!(ts->out_level & (1 << offset));
1358c2ecf20Sopenharmony_ci		break;
1368c2ecf20Sopenharmony_ci	case PIN_CONFIG_IN_WO_PULLUP:
1378c2ecf20Sopenharmony_ci	case PIN_CONFIG_IN_PULLUP:
1388c2ecf20Sopenharmony_ci		/* Input: read out */
1398c2ecf20Sopenharmony_ci		level = ts->read(ts->dev, 0x20 + offset) & 0x01;
1408c2ecf20Sopenharmony_ci	}
1418c2ecf20Sopenharmony_ci	mutex_unlock(&ts->lock);
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	return level;
1448c2ecf20Sopenharmony_ci}
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_cistatic void max7301_set(struct gpio_chip *chip, unsigned offset, int value)
1478c2ecf20Sopenharmony_ci{
1488c2ecf20Sopenharmony_ci	struct max7301 *ts = gpiochip_get_data(chip);
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	/* First 4 pins are unused in the controller */
1518c2ecf20Sopenharmony_ci	offset += 4;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	mutex_lock(&ts->lock);
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	__max7301_set(ts, offset, value);
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	mutex_unlock(&ts->lock);
1588c2ecf20Sopenharmony_ci}
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ciint __max730x_probe(struct max7301 *ts)
1618c2ecf20Sopenharmony_ci{
1628c2ecf20Sopenharmony_ci	struct device *dev = ts->dev;
1638c2ecf20Sopenharmony_ci	struct max7301_platform_data *pdata;
1648c2ecf20Sopenharmony_ci	int i, ret;
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	pdata = dev_get_platdata(dev);
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	mutex_init(&ts->lock);
1698c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, ts);
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	/* Power up the chip and disable IRQ output */
1728c2ecf20Sopenharmony_ci	ts->write(dev, 0x04, 0x01);
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	if (pdata) {
1758c2ecf20Sopenharmony_ci		ts->input_pullup_active = pdata->input_pullup_active;
1768c2ecf20Sopenharmony_ci		ts->chip.base = pdata->base;
1778c2ecf20Sopenharmony_ci	} else {
1788c2ecf20Sopenharmony_ci		ts->chip.base = -1;
1798c2ecf20Sopenharmony_ci	}
1808c2ecf20Sopenharmony_ci	ts->chip.label = dev->driver->name;
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	ts->chip.direction_input = max7301_direction_input;
1838c2ecf20Sopenharmony_ci	ts->chip.get = max7301_get;
1848c2ecf20Sopenharmony_ci	ts->chip.direction_output = max7301_direction_output;
1858c2ecf20Sopenharmony_ci	ts->chip.set = max7301_set;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	ts->chip.ngpio = PIN_NUMBER;
1888c2ecf20Sopenharmony_ci	ts->chip.can_sleep = true;
1898c2ecf20Sopenharmony_ci	ts->chip.parent = dev;
1908c2ecf20Sopenharmony_ci	ts->chip.owner = THIS_MODULE;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	/*
1938c2ecf20Sopenharmony_ci	 * initialize pullups according to platform data and cache the
1948c2ecf20Sopenharmony_ci	 * register values for later use.
1958c2ecf20Sopenharmony_ci	 */
1968c2ecf20Sopenharmony_ci	for (i = 1; i < 8; i++) {
1978c2ecf20Sopenharmony_ci		int j;
1988c2ecf20Sopenharmony_ci		/*
1998c2ecf20Sopenharmony_ci		 * initialize port_config with "0xAA", which means
2008c2ecf20Sopenharmony_ci		 * input with internal pullup disabled. This is needed
2018c2ecf20Sopenharmony_ci		 * to avoid writing zeros (in the inner for loop),
2028c2ecf20Sopenharmony_ci		 * which is not allowed according to the datasheet.
2038c2ecf20Sopenharmony_ci		 */
2048c2ecf20Sopenharmony_ci		ts->port_config[i] = 0xAA;
2058c2ecf20Sopenharmony_ci		for (j = 0; j < 4; j++) {
2068c2ecf20Sopenharmony_ci			int offset = (i - 1) * 4 + j;
2078c2ecf20Sopenharmony_ci			ret = max7301_direction_input(&ts->chip, offset);
2088c2ecf20Sopenharmony_ci			if (ret)
2098c2ecf20Sopenharmony_ci				goto exit_destroy;
2108c2ecf20Sopenharmony_ci		}
2118c2ecf20Sopenharmony_ci	}
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	ret = gpiochip_add_data(&ts->chip, ts);
2148c2ecf20Sopenharmony_ci	if (!ret)
2158c2ecf20Sopenharmony_ci		return ret;
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ciexit_destroy:
2188c2ecf20Sopenharmony_ci	mutex_destroy(&ts->lock);
2198c2ecf20Sopenharmony_ci	return ret;
2208c2ecf20Sopenharmony_ci}
2218c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(__max730x_probe);
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ciint __max730x_remove(struct device *dev)
2248c2ecf20Sopenharmony_ci{
2258c2ecf20Sopenharmony_ci	struct max7301 *ts = dev_get_drvdata(dev);
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	if (ts == NULL)
2288c2ecf20Sopenharmony_ci		return -ENODEV;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	/* Power down the chip and disable IRQ output */
2318c2ecf20Sopenharmony_ci	ts->write(dev, 0x04, 0x00);
2328c2ecf20Sopenharmony_ci	gpiochip_remove(&ts->chip);
2338c2ecf20Sopenharmony_ci	mutex_destroy(&ts->lock);
2348c2ecf20Sopenharmony_ci	return 0;
2358c2ecf20Sopenharmony_ci}
2368c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(__max730x_remove);
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ciMODULE_AUTHOR("Juergen Beisert, Wolfram Sang");
2398c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
2408c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("MAX730x GPIO-Expanders, generic parts");
241