-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
gpio: gpio driver for max7301 SPI GPIO expander
Maxim's MAX7301 is an SPI GPIO expander with 28 GPIOs. Note: MAX7301's interrupt feature is not supported yet. [akpm@linux-foundation.org: coding-style fixes] [g.liakhovetski@pengutronix.de: Fix inaccuracies in comments, check spi_setup() return code, mask off high byte in max7301_read()] Signed-off-by: Juergen Beisert <j.beisert@pengutronix.de> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@pengutronix.de> Cc: Russell King <rmk@arm.linux.org.uk> Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
- Loading branch information
Juergen Beisert
authored and
Linus Torvalds
committed
Jul 22, 2008
1 parent
6519108
commit 0c36ec3
Showing
4 changed files
with
355 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,339 @@ | ||
/** | ||
* drivers/gpio/max7301.c | ||
* | ||
* Copyright (C) 2006 Juergen Beisert, Pengutronix | ||
* Copyright (C) 2008 Guennadi Liakhovetski, Pengutronix | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 2 as | ||
* published by the Free Software Foundation. | ||
* | ||
* The Maxim's MAX7301 device is an SPI driven GPIO expander. There are | ||
* 28 GPIOs. 8 of them can trigger an interrupt. See datasheet for more | ||
* details | ||
* Note: | ||
* - DIN must be stable at the rising edge of clock. | ||
* - when writing: | ||
* - always clock in 16 clocks at once | ||
* - at DIN: D15 first, D0 last | ||
* - D0..D7 = databyte, D8..D14 = commandbyte | ||
* - D15 = low -> write command | ||
* - when reading | ||
* - always clock in 16 clocks at once | ||
* - at DIN: D15 first, D0 last | ||
* - D0..D7 = dummy, D8..D14 = register address | ||
* - D15 = high -> read command | ||
* - raise CS and assert it again | ||
* - always clock in 16 clocks at once | ||
* - at DOUT: D15 first, D0 last | ||
* - D0..D7 contains the data from the first cycle | ||
* | ||
* The driver exports a standard gpiochip interface | ||
*/ | ||
|
||
#include <linux/init.h> | ||
#include <linux/platform_device.h> | ||
#include <linux/mutex.h> | ||
#include <linux/spi/spi.h> | ||
#include <linux/spi/max7301.h> | ||
#include <linux/gpio.h> | ||
|
||
#define DRIVER_NAME "max7301" | ||
|
||
/* | ||
* Pin configurations, see MAX7301 datasheet page 6 | ||
*/ | ||
#define PIN_CONFIG_MASK 0x03 | ||
#define PIN_CONFIG_IN_PULLUP 0x03 | ||
#define PIN_CONFIG_IN_WO_PULLUP 0x02 | ||
#define PIN_CONFIG_OUT 0x01 | ||
|
||
#define PIN_NUMBER 28 | ||
|
||
|
||
/* | ||
* Some registers must be read back to modify. | ||
* To save time we cache them here in memory | ||
*/ | ||
struct max7301 { | ||
struct mutex lock; | ||
u8 port_config[8]; /* field 0 is unused */ | ||
u32 out_level; /* cached output levels */ | ||
struct gpio_chip chip; | ||
struct spi_device *spi; | ||
}; | ||
|
||
/** | ||
* max7301_write - Write a new register content | ||
* @spi: The SPI device | ||
* @reg: Register offset | ||
* @val: Value to write | ||
* | ||
* A write to the MAX7301 means one message with one transfer | ||
* | ||
* Returns 0 if successful or a negative value on error | ||
*/ | ||
static int max7301_write(struct spi_device *spi, unsigned int reg, unsigned int val) | ||
{ | ||
u16 word = ((reg & 0x7F) << 8) | (val & 0xFF); | ||
return spi_write(spi, (const u8 *)&word, sizeof(word)); | ||
} | ||
|
||
/** | ||
* max7301_read - Read back register content | ||
* @spi: The SPI device | ||
* @reg: Register offset | ||
* | ||
* A read from the MAX7301 means two transfers; here, one message each | ||
* | ||
* Returns positive 8 bit value from device if successful or a | ||
* negative value on error | ||
*/ | ||
static int max7301_read(struct spi_device *spi, unsigned int reg) | ||
{ | ||
int ret; | ||
u16 word; | ||
|
||
word = 0x8000 | (reg << 8); | ||
ret = spi_write(spi, (const u8 *)&word, sizeof(word)); | ||
if (ret) | ||
return ret; | ||
/* | ||
* This relies on the fact, that a transfer with NULL tx_buf shifts out | ||
* zero bytes (=NOOP for MAX7301) | ||
*/ | ||
ret = spi_read(spi, (u8 *)&word, sizeof(word)); | ||
if (ret) | ||
return ret; | ||
return word & 0xff; | ||
} | ||
|
||
static int max7301_direction_input(struct gpio_chip *chip, unsigned offset) | ||
{ | ||
struct max7301 *ts = container_of(chip, struct max7301, chip); | ||
u8 *config; | ||
int ret; | ||
|
||
/* First 4 pins are unused in the controller */ | ||
offset += 4; | ||
|
||
config = &ts->port_config[offset >> 2]; | ||
|
||
mutex_lock(&ts->lock); | ||
|
||
/* Standard GPIO API doesn't support pull-ups, has to be extended. | ||
* Hard-coding no pollup for now. */ | ||
*config = (*config & ~(3 << (offset & 3))) | (1 << (offset & 3)); | ||
|
||
ret = max7301_write(ts->spi, 0x08 + (offset >> 2), *config); | ||
|
||
mutex_unlock(&ts->lock); | ||
|
||
return ret; | ||
} | ||
|
||
static int __max7301_set(struct max7301 *ts, unsigned offset, int value) | ||
{ | ||
if (value) { | ||
ts->out_level |= 1 << offset; | ||
return max7301_write(ts->spi, 0x20 + offset, 0x01); | ||
} else { | ||
ts->out_level &= ~(1 << offset); | ||
return max7301_write(ts->spi, 0x20 + offset, 0x00); | ||
} | ||
} | ||
|
||
static int max7301_direction_output(struct gpio_chip *chip, unsigned offset, | ||
int value) | ||
{ | ||
struct max7301 *ts = container_of(chip, struct max7301, chip); | ||
u8 *config; | ||
int ret; | ||
|
||
/* First 4 pins are unused in the controller */ | ||
offset += 4; | ||
|
||
config = &ts->port_config[offset >> 2]; | ||
|
||
mutex_lock(&ts->lock); | ||
|
||
*config = (*config & ~(3 << (offset & 3))) | (1 << (offset & 3)); | ||
|
||
ret = __max7301_set(ts, offset, value); | ||
|
||
if (!ret) | ||
ret = max7301_write(ts->spi, 0x08 + (offset >> 2), *config); | ||
|
||
mutex_unlock(&ts->lock); | ||
|
||
return ret; | ||
} | ||
|
||
static int max7301_get(struct gpio_chip *chip, unsigned offset) | ||
{ | ||
struct max7301 *ts = container_of(chip, struct max7301, chip); | ||
int config, level = -EINVAL; | ||
|
||
/* First 4 pins are unused in the controller */ | ||
offset += 4; | ||
|
||
mutex_lock(&ts->lock); | ||
|
||
config = (ts->port_config[offset >> 2] >> ((offset & 3) * 2)) & 3; | ||
|
||
switch (config) { | ||
case 1: | ||
/* Output: return cached level */ | ||
level = !!(ts->out_level & (1 << offset)); | ||
break; | ||
case 2: | ||
case 3: | ||
/* Input: read out */ | ||
level = max7301_read(ts->spi, 0x20 + offset) & 0x01; | ||
} | ||
mutex_unlock(&ts->lock); | ||
|
||
return level; | ||
} | ||
|
||
static void max7301_set(struct gpio_chip *chip, unsigned offset, int value) | ||
{ | ||
struct max7301 *ts = container_of(chip, struct max7301, chip); | ||
|
||
/* First 4 pins are unused in the controller */ | ||
offset += 4; | ||
|
||
mutex_lock(&ts->lock); | ||
|
||
__max7301_set(ts, offset, value); | ||
|
||
mutex_unlock(&ts->lock); | ||
} | ||
|
||
static int __devinit max7301_probe(struct spi_device *spi) | ||
{ | ||
struct max7301 *ts; | ||
struct max7301_platform_data *pdata; | ||
int i, ret; | ||
|
||
pdata = spi->dev.platform_data; | ||
if (!pdata || !pdata->base) | ||
return -ENODEV; | ||
|
||
/* | ||
* bits_per_word cannot be configured in platform data | ||
*/ | ||
spi->bits_per_word = 16; | ||
|
||
ret = spi_setup(spi); | ||
if (ret < 0) | ||
return ret; | ||
|
||
ts = kzalloc(sizeof(struct max7301), GFP_KERNEL); | ||
if (!ts) | ||
return -ENOMEM; | ||
|
||
mutex_init(&ts->lock); | ||
|
||
dev_set_drvdata(&spi->dev, ts); | ||
|
||
/* Power up the chip and disable IRQ output */ | ||
max7301_write(spi, 0x04, 0x01); | ||
|
||
ts->spi = spi; | ||
|
||
ts->chip.label = DRIVER_NAME, | ||
|
||
ts->chip.direction_input = max7301_direction_input; | ||
ts->chip.get = max7301_get; | ||
ts->chip.direction_output = max7301_direction_output; | ||
ts->chip.set = max7301_set; | ||
|
||
ts->chip.base = pdata->base; | ||
ts->chip.ngpio = PIN_NUMBER; | ||
ts->chip.can_sleep = 1; | ||
ts->chip.dev = &spi->dev; | ||
ts->chip.owner = THIS_MODULE; | ||
|
||
ret = gpiochip_add(&ts->chip); | ||
if (ret) | ||
goto exit_destroy; | ||
|
||
/* | ||
* tristate all pins in hardware and cache the | ||
* register values for later use. | ||
*/ | ||
for (i = 1; i < 8; i++) { | ||
int j; | ||
/* 0xAA means input with internal pullup disabled */ | ||
max7301_write(spi, 0x08 + i, 0xAA); | ||
ts->port_config[i] = 0xAA; | ||
for (j = 0; j < 4; j++) { | ||
int idx = ts->chip.base + (i - 1) * 4 + j; | ||
ret = gpio_direction_input(idx); | ||
if (ret) | ||
goto exit_remove; | ||
gpio_free(idx); | ||
} | ||
} | ||
return ret; | ||
|
||
exit_remove: | ||
gpiochip_remove(&ts->chip); | ||
exit_destroy: | ||
dev_set_drvdata(&spi->dev, NULL); | ||
mutex_destroy(&ts->lock); | ||
kfree(ts); | ||
return ret; | ||
} | ||
|
||
static int max7301_remove(struct spi_device *spi) | ||
{ | ||
struct max7301 *ts; | ||
int ret; | ||
|
||
ts = dev_get_drvdata(&spi->dev); | ||
if (ts == NULL) | ||
return -ENODEV; | ||
|
||
dev_set_drvdata(&spi->dev, NULL); | ||
|
||
/* Power down the chip and disable IRQ output */ | ||
max7301_write(spi, 0x04, 0x00); | ||
|
||
ret = gpiochip_remove(&ts->chip); | ||
if (!ret) { | ||
mutex_destroy(&ts->lock); | ||
kfree(ts); | ||
} else | ||
dev_err(&spi->dev, "Failed to remove the GPIO controller: %d\n", | ||
ret); | ||
|
||
return ret; | ||
} | ||
|
||
static struct spi_driver max7301_driver = { | ||
.driver = { | ||
.name = DRIVER_NAME, | ||
.owner = THIS_MODULE, | ||
}, | ||
.probe = max7301_probe, | ||
.remove = __devexit_p(max7301_remove), | ||
}; | ||
|
||
static int __init max7301_init(void) | ||
{ | ||
return spi_register_driver(&max7301_driver); | ||
} | ||
|
||
static void __exit max7301_exit(void) | ||
{ | ||
spi_unregister_driver(&max7301_driver); | ||
} | ||
|
||
module_init(max7301_init); | ||
module_exit(max7301_exit); | ||
|
||
MODULE_AUTHOR("Juergen Beisert"); | ||
MODULE_LICENSE("GPL v2"); | ||
MODULE_DESCRIPTION("MAX7301 SPI based GPIO-Expander"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#ifndef LINUX_SPI_MAX7301_H | ||
#define LINUX_SPI_MAX7301_H | ||
|
||
struct max7301_platform_data { | ||
/* number assigned to the first GPIO */ | ||
unsigned base; | ||
}; | ||
|
||
#endif |