162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/** 362306a36Sopenharmony_ci * Copyright (C) 2006 Juergen Beisert, Pengutronix 462306a36Sopenharmony_ci * Copyright (C) 2008 Guennadi Liakhovetski, Pengutronix 562306a36Sopenharmony_ci * Copyright (C) 2009 Wolfram Sang, Pengutronix 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * The Maxim MAX7300/1 device is an I2C/SPI driven GPIO expander. There are 862306a36Sopenharmony_ci * 28 GPIOs. 8 of them can trigger an interrupt. See datasheet for more 962306a36Sopenharmony_ci * details 1062306a36Sopenharmony_ci * Note: 1162306a36Sopenharmony_ci * - DIN must be stable at the rising edge of clock. 1262306a36Sopenharmony_ci * - when writing: 1362306a36Sopenharmony_ci * - always clock in 16 clocks at once 1462306a36Sopenharmony_ci * - at DIN: D15 first, D0 last 1562306a36Sopenharmony_ci * - D0..D7 = databyte, D8..D14 = commandbyte 1662306a36Sopenharmony_ci * - D15 = low -> write command 1762306a36Sopenharmony_ci * - when reading 1862306a36Sopenharmony_ci * - always clock in 16 clocks at once 1962306a36Sopenharmony_ci * - at DIN: D15 first, D0 last 2062306a36Sopenharmony_ci * - D0..D7 = dummy, D8..D14 = register address 2162306a36Sopenharmony_ci * - D15 = high -> read command 2262306a36Sopenharmony_ci * - raise CS and assert it again 2362306a36Sopenharmony_ci * - always clock in 16 clocks at once 2462306a36Sopenharmony_ci * - at DOUT: D15 first, D0 last 2562306a36Sopenharmony_ci * - D0..D7 contains the data from the first cycle 2662306a36Sopenharmony_ci * 2762306a36Sopenharmony_ci * The driver exports a standard gpiochip interface 2862306a36Sopenharmony_ci */ 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#include <linux/module.h> 3162306a36Sopenharmony_ci#include <linux/init.h> 3262306a36Sopenharmony_ci#include <linux/platform_device.h> 3362306a36Sopenharmony_ci#include <linux/mutex.h> 3462306a36Sopenharmony_ci#include <linux/spi/max7301.h> 3562306a36Sopenharmony_ci#include <linux/gpio/driver.h> 3662306a36Sopenharmony_ci#include <linux/slab.h> 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci/* 3962306a36Sopenharmony_ci * Pin configurations, see MAX7301 datasheet page 6 4062306a36Sopenharmony_ci */ 4162306a36Sopenharmony_ci#define PIN_CONFIG_MASK 0x03 4262306a36Sopenharmony_ci#define PIN_CONFIG_IN_PULLUP 0x03 4362306a36Sopenharmony_ci#define PIN_CONFIG_IN_WO_PULLUP 0x02 4462306a36Sopenharmony_ci#define PIN_CONFIG_OUT 0x01 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci#define PIN_NUMBER 28 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic int max7301_direction_input(struct gpio_chip *chip, unsigned offset) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci struct max7301 *ts = container_of(chip, struct max7301, chip); 5162306a36Sopenharmony_ci u8 *config; 5262306a36Sopenharmony_ci u8 offset_bits, pin_config; 5362306a36Sopenharmony_ci int ret; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci /* First 4 pins are unused in the controller */ 5662306a36Sopenharmony_ci offset += 4; 5762306a36Sopenharmony_ci offset_bits = (offset & 3) << 1; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci config = &ts->port_config[offset >> 2]; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci if (ts->input_pullup_active & BIT(offset)) 6262306a36Sopenharmony_ci pin_config = PIN_CONFIG_IN_PULLUP; 6362306a36Sopenharmony_ci else 6462306a36Sopenharmony_ci pin_config = PIN_CONFIG_IN_WO_PULLUP; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci mutex_lock(&ts->lock); 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci *config = (*config & ~(PIN_CONFIG_MASK << offset_bits)) 6962306a36Sopenharmony_ci | (pin_config << offset_bits); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci ret = ts->write(ts->dev, 0x08 + (offset >> 2), *config); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci mutex_unlock(&ts->lock); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci return ret; 7662306a36Sopenharmony_ci} 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistatic int __max7301_set(struct max7301 *ts, unsigned offset, int value) 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci if (value) { 8162306a36Sopenharmony_ci ts->out_level |= 1 << offset; 8262306a36Sopenharmony_ci return ts->write(ts->dev, 0x20 + offset, 0x01); 8362306a36Sopenharmony_ci } else { 8462306a36Sopenharmony_ci ts->out_level &= ~(1 << offset); 8562306a36Sopenharmony_ci return ts->write(ts->dev, 0x20 + offset, 0x00); 8662306a36Sopenharmony_ci } 8762306a36Sopenharmony_ci} 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic int max7301_direction_output(struct gpio_chip *chip, unsigned offset, 9062306a36Sopenharmony_ci int value) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci struct max7301 *ts = container_of(chip, struct max7301, chip); 9362306a36Sopenharmony_ci u8 *config; 9462306a36Sopenharmony_ci u8 offset_bits; 9562306a36Sopenharmony_ci int ret; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci /* First 4 pins are unused in the controller */ 9862306a36Sopenharmony_ci offset += 4; 9962306a36Sopenharmony_ci offset_bits = (offset & 3) << 1; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci config = &ts->port_config[offset >> 2]; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci mutex_lock(&ts->lock); 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci *config = (*config & ~(PIN_CONFIG_MASK << offset_bits)) 10662306a36Sopenharmony_ci | (PIN_CONFIG_OUT << offset_bits); 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci ret = __max7301_set(ts, offset, value); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci if (!ret) 11162306a36Sopenharmony_ci ret = ts->write(ts->dev, 0x08 + (offset >> 2), *config); 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci mutex_unlock(&ts->lock); 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci return ret; 11662306a36Sopenharmony_ci} 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_cistatic int max7301_get(struct gpio_chip *chip, unsigned offset) 11962306a36Sopenharmony_ci{ 12062306a36Sopenharmony_ci struct max7301 *ts = gpiochip_get_data(chip); 12162306a36Sopenharmony_ci int config, level = -EINVAL; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci /* First 4 pins are unused in the controller */ 12462306a36Sopenharmony_ci offset += 4; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci mutex_lock(&ts->lock); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci config = (ts->port_config[offset >> 2] >> ((offset & 3) << 1)) 12962306a36Sopenharmony_ci & PIN_CONFIG_MASK; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci switch (config) { 13262306a36Sopenharmony_ci case PIN_CONFIG_OUT: 13362306a36Sopenharmony_ci /* Output: return cached level */ 13462306a36Sopenharmony_ci level = !!(ts->out_level & (1 << offset)); 13562306a36Sopenharmony_ci break; 13662306a36Sopenharmony_ci case PIN_CONFIG_IN_WO_PULLUP: 13762306a36Sopenharmony_ci case PIN_CONFIG_IN_PULLUP: 13862306a36Sopenharmony_ci /* Input: read out */ 13962306a36Sopenharmony_ci level = ts->read(ts->dev, 0x20 + offset) & 0x01; 14062306a36Sopenharmony_ci } 14162306a36Sopenharmony_ci mutex_unlock(&ts->lock); 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci return level; 14462306a36Sopenharmony_ci} 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_cistatic void max7301_set(struct gpio_chip *chip, unsigned offset, int value) 14762306a36Sopenharmony_ci{ 14862306a36Sopenharmony_ci struct max7301 *ts = gpiochip_get_data(chip); 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci /* First 4 pins are unused in the controller */ 15162306a36Sopenharmony_ci offset += 4; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci mutex_lock(&ts->lock); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci __max7301_set(ts, offset, value); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci mutex_unlock(&ts->lock); 15862306a36Sopenharmony_ci} 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ciint __max730x_probe(struct max7301 *ts) 16162306a36Sopenharmony_ci{ 16262306a36Sopenharmony_ci struct device *dev = ts->dev; 16362306a36Sopenharmony_ci struct max7301_platform_data *pdata; 16462306a36Sopenharmony_ci int i, ret; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci pdata = dev_get_platdata(dev); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci mutex_init(&ts->lock); 16962306a36Sopenharmony_ci dev_set_drvdata(dev, ts); 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci /* Power up the chip and disable IRQ output */ 17262306a36Sopenharmony_ci ts->write(dev, 0x04, 0x01); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci if (pdata) { 17562306a36Sopenharmony_ci ts->input_pullup_active = pdata->input_pullup_active; 17662306a36Sopenharmony_ci ts->chip.base = pdata->base; 17762306a36Sopenharmony_ci } else { 17862306a36Sopenharmony_ci ts->chip.base = -1; 17962306a36Sopenharmony_ci } 18062306a36Sopenharmony_ci ts->chip.label = dev->driver->name; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci ts->chip.direction_input = max7301_direction_input; 18362306a36Sopenharmony_ci ts->chip.get = max7301_get; 18462306a36Sopenharmony_ci ts->chip.direction_output = max7301_direction_output; 18562306a36Sopenharmony_ci ts->chip.set = max7301_set; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci ts->chip.ngpio = PIN_NUMBER; 18862306a36Sopenharmony_ci ts->chip.can_sleep = true; 18962306a36Sopenharmony_ci ts->chip.parent = dev; 19062306a36Sopenharmony_ci ts->chip.owner = THIS_MODULE; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci /* 19362306a36Sopenharmony_ci * initialize pullups according to platform data and cache the 19462306a36Sopenharmony_ci * register values for later use. 19562306a36Sopenharmony_ci */ 19662306a36Sopenharmony_ci for (i = 1; i < 8; i++) { 19762306a36Sopenharmony_ci int j; 19862306a36Sopenharmony_ci /* 19962306a36Sopenharmony_ci * initialize port_config with "0xAA", which means 20062306a36Sopenharmony_ci * input with internal pullup disabled. This is needed 20162306a36Sopenharmony_ci * to avoid writing zeros (in the inner for loop), 20262306a36Sopenharmony_ci * which is not allowed according to the datasheet. 20362306a36Sopenharmony_ci */ 20462306a36Sopenharmony_ci ts->port_config[i] = 0xAA; 20562306a36Sopenharmony_ci for (j = 0; j < 4; j++) { 20662306a36Sopenharmony_ci int offset = (i - 1) * 4 + j; 20762306a36Sopenharmony_ci ret = max7301_direction_input(&ts->chip, offset); 20862306a36Sopenharmony_ci if (ret) 20962306a36Sopenharmony_ci goto exit_destroy; 21062306a36Sopenharmony_ci } 21162306a36Sopenharmony_ci } 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci ret = gpiochip_add_data(&ts->chip, ts); 21462306a36Sopenharmony_ci if (!ret) 21562306a36Sopenharmony_ci return ret; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ciexit_destroy: 21862306a36Sopenharmony_ci mutex_destroy(&ts->lock); 21962306a36Sopenharmony_ci return ret; 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(__max730x_probe); 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_civoid __max730x_remove(struct device *dev) 22462306a36Sopenharmony_ci{ 22562306a36Sopenharmony_ci struct max7301 *ts = dev_get_drvdata(dev); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci /* Power down the chip and disable IRQ output */ 22862306a36Sopenharmony_ci ts->write(dev, 0x04, 0x00); 22962306a36Sopenharmony_ci gpiochip_remove(&ts->chip); 23062306a36Sopenharmony_ci mutex_destroy(&ts->lock); 23162306a36Sopenharmony_ci} 23262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(__max730x_remove); 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ciMODULE_AUTHOR("Juergen Beisert, Wolfram Sang"); 23562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 23662306a36Sopenharmony_ciMODULE_DESCRIPTION("MAX730x GPIO-Expanders, generic parts"); 237