David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 1 | /* |
Peter Korsgaard | 752ad5e | 2011-07-15 10:25:32 +0200 | [diff] [blame] | 2 | * MCP23S08 SPI/GPIO gpio expander driver |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 3 | */ |
| 4 | |
| 5 | #include <linux/kernel.h> |
| 6 | #include <linux/device.h> |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 7 | #include <linux/mutex.h> |
Paul Gortmaker | bb207ef | 2011-07-03 13:38:09 -0400 | [diff] [blame] | 8 | #include <linux/module.h> |
H Hartley Sweeten | d120c17 | 2009-09-22 16:46:37 -0700 | [diff] [blame] | 9 | #include <linux/gpio.h> |
Peter Korsgaard | 752ad5e | 2011-07-15 10:25:32 +0200 | [diff] [blame] | 10 | #include <linux/i2c.h> |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 11 | #include <linux/spi/spi.h> |
| 12 | #include <linux/spi/mcp23s08.h> |
Tejun Heo | 5a0e3ad | 2010-03-24 17:04:11 +0900 | [diff] [blame] | 13 | #include <linux/slab.h> |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 14 | #include <asm/byteorder.h> |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 15 | #include <linux/of.h> |
| 16 | #include <linux/of_device.h> |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 17 | |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 18 | /** |
| 19 | * MCP types supported by driver |
| 20 | */ |
| 21 | #define MCP_TYPE_S08 0 |
| 22 | #define MCP_TYPE_S17 1 |
Peter Korsgaard | 752ad5e | 2011-07-15 10:25:32 +0200 | [diff] [blame] | 23 | #define MCP_TYPE_008 2 |
| 24 | #define MCP_TYPE_017 3 |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 25 | |
| 26 | /* Registers are all 8 bits wide. |
| 27 | * |
| 28 | * The mcp23s17 has twice as many bits, and can be configured to work |
| 29 | * with either 16 bit registers or with two adjacent 8 bit banks. |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 30 | */ |
| 31 | #define MCP_IODIR 0x00 /* init/reset: all ones */ |
| 32 | #define MCP_IPOL 0x01 |
| 33 | #define MCP_GPINTEN 0x02 |
| 34 | #define MCP_DEFVAL 0x03 |
| 35 | #define MCP_INTCON 0x04 |
| 36 | #define MCP_IOCON 0x05 |
| 37 | # define IOCON_SEQOP (1 << 5) |
| 38 | # define IOCON_HAEN (1 << 3) |
| 39 | # define IOCON_ODR (1 << 2) |
| 40 | # define IOCON_INTPOL (1 << 1) |
| 41 | #define MCP_GPPU 0x06 |
| 42 | #define MCP_INTF 0x07 |
| 43 | #define MCP_INTCAP 0x08 |
| 44 | #define MCP_GPIO 0x09 |
| 45 | #define MCP_OLAT 0x0a |
| 46 | |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 47 | struct mcp23s08; |
| 48 | |
| 49 | struct mcp23s08_ops { |
| 50 | int (*read)(struct mcp23s08 *mcp, unsigned reg); |
| 51 | int (*write)(struct mcp23s08 *mcp, unsigned reg, unsigned val); |
| 52 | int (*read_regs)(struct mcp23s08 *mcp, unsigned reg, |
| 53 | u16 *vals, unsigned n); |
| 54 | }; |
| 55 | |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 56 | struct mcp23s08 { |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 57 | u8 addr; |
| 58 | |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 59 | u16 cache[11]; |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 60 | /* lock protects the cached values */ |
| 61 | struct mutex lock; |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 62 | |
| 63 | struct gpio_chip chip; |
| 64 | |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 65 | const struct mcp23s08_ops *ops; |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 66 | void *data; /* ops specific data */ |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 67 | }; |
| 68 | |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 69 | /* A given spi_device can represent up to eight mcp23sxx chips |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 70 | * sharing the same chipselect but using different addresses |
| 71 | * (e.g. chips #0 and #3 might be populated, but not #1 or $2). |
| 72 | * Driver data holds all the per-chip data. |
| 73 | */ |
| 74 | struct mcp23s08_driver_data { |
| 75 | unsigned ngpio; |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 76 | struct mcp23s08 *mcp[8]; |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 77 | struct mcp23s08 chip[]; |
| 78 | }; |
| 79 | |
Peter Korsgaard | 752ad5e | 2011-07-15 10:25:32 +0200 | [diff] [blame] | 80 | /*----------------------------------------------------------------------*/ |
| 81 | |
Daniel M. Weeks | cbf24fa | 2012-11-06 23:51:05 -0500 | [diff] [blame] | 82 | #if IS_ENABLED(CONFIG_I2C) |
Peter Korsgaard | 752ad5e | 2011-07-15 10:25:32 +0200 | [diff] [blame] | 83 | |
| 84 | static int mcp23008_read(struct mcp23s08 *mcp, unsigned reg) |
| 85 | { |
| 86 | return i2c_smbus_read_byte_data(mcp->data, reg); |
| 87 | } |
| 88 | |
| 89 | static int mcp23008_write(struct mcp23s08 *mcp, unsigned reg, unsigned val) |
| 90 | { |
| 91 | return i2c_smbus_write_byte_data(mcp->data, reg, val); |
| 92 | } |
| 93 | |
| 94 | static int |
| 95 | mcp23008_read_regs(struct mcp23s08 *mcp, unsigned reg, u16 *vals, unsigned n) |
| 96 | { |
| 97 | while (n--) { |
| 98 | int ret = mcp23008_read(mcp, reg++); |
| 99 | if (ret < 0) |
| 100 | return ret; |
| 101 | *vals++ = ret; |
| 102 | } |
| 103 | |
| 104 | return 0; |
| 105 | } |
| 106 | |
| 107 | static int mcp23017_read(struct mcp23s08 *mcp, unsigned reg) |
| 108 | { |
| 109 | return i2c_smbus_read_word_data(mcp->data, reg << 1); |
| 110 | } |
| 111 | |
| 112 | static int mcp23017_write(struct mcp23s08 *mcp, unsigned reg, unsigned val) |
| 113 | { |
| 114 | return i2c_smbus_write_word_data(mcp->data, reg << 1, val); |
| 115 | } |
| 116 | |
| 117 | static int |
| 118 | mcp23017_read_regs(struct mcp23s08 *mcp, unsigned reg, u16 *vals, unsigned n) |
| 119 | { |
| 120 | while (n--) { |
| 121 | int ret = mcp23017_read(mcp, reg++); |
| 122 | if (ret < 0) |
| 123 | return ret; |
| 124 | *vals++ = ret; |
| 125 | } |
| 126 | |
| 127 | return 0; |
| 128 | } |
| 129 | |
| 130 | static const struct mcp23s08_ops mcp23008_ops = { |
| 131 | .read = mcp23008_read, |
| 132 | .write = mcp23008_write, |
| 133 | .read_regs = mcp23008_read_regs, |
| 134 | }; |
| 135 | |
| 136 | static const struct mcp23s08_ops mcp23017_ops = { |
| 137 | .read = mcp23017_read, |
| 138 | .write = mcp23017_write, |
| 139 | .read_regs = mcp23017_read_regs, |
| 140 | }; |
| 141 | |
| 142 | #endif /* CONFIG_I2C */ |
| 143 | |
| 144 | /*----------------------------------------------------------------------*/ |
| 145 | |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 146 | #ifdef CONFIG_SPI_MASTER |
| 147 | |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 148 | static int mcp23s08_read(struct mcp23s08 *mcp, unsigned reg) |
| 149 | { |
| 150 | u8 tx[2], rx[1]; |
| 151 | int status; |
| 152 | |
| 153 | tx[0] = mcp->addr | 0x01; |
| 154 | tx[1] = reg; |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 155 | status = spi_write_then_read(mcp->data, tx, sizeof tx, rx, sizeof rx); |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 156 | return (status < 0) ? status : rx[0]; |
| 157 | } |
| 158 | |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 159 | static int mcp23s08_write(struct mcp23s08 *mcp, unsigned reg, unsigned val) |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 160 | { |
| 161 | u8 tx[3]; |
| 162 | |
| 163 | tx[0] = mcp->addr; |
| 164 | tx[1] = reg; |
| 165 | tx[2] = val; |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 166 | return spi_write_then_read(mcp->data, tx, sizeof tx, NULL, 0); |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 167 | } |
| 168 | |
| 169 | static int |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 170 | mcp23s08_read_regs(struct mcp23s08 *mcp, unsigned reg, u16 *vals, unsigned n) |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 171 | { |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 172 | u8 tx[2], *tmp; |
| 173 | int status; |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 174 | |
| 175 | if ((n + reg) > sizeof mcp->cache) |
| 176 | return -EINVAL; |
| 177 | tx[0] = mcp->addr | 0x01; |
| 178 | tx[1] = reg; |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 179 | |
| 180 | tmp = (u8 *)vals; |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 181 | status = spi_write_then_read(mcp->data, tx, sizeof tx, tmp, n); |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 182 | if (status >= 0) { |
| 183 | while (n--) |
| 184 | vals[n] = tmp[n]; /* expand to 16bit */ |
| 185 | } |
| 186 | return status; |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 187 | } |
| 188 | |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 189 | static int mcp23s17_read(struct mcp23s08 *mcp, unsigned reg) |
| 190 | { |
| 191 | u8 tx[2], rx[2]; |
| 192 | int status; |
| 193 | |
| 194 | tx[0] = mcp->addr | 0x01; |
| 195 | tx[1] = reg << 1; |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 196 | status = spi_write_then_read(mcp->data, tx, sizeof tx, rx, sizeof rx); |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 197 | return (status < 0) ? status : (rx[0] | (rx[1] << 8)); |
| 198 | } |
| 199 | |
| 200 | static int mcp23s17_write(struct mcp23s08 *mcp, unsigned reg, unsigned val) |
| 201 | { |
| 202 | u8 tx[4]; |
| 203 | |
| 204 | tx[0] = mcp->addr; |
| 205 | tx[1] = reg << 1; |
| 206 | tx[2] = val; |
| 207 | tx[3] = val >> 8; |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 208 | return spi_write_then_read(mcp->data, tx, sizeof tx, NULL, 0); |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 209 | } |
| 210 | |
| 211 | static int |
| 212 | mcp23s17_read_regs(struct mcp23s08 *mcp, unsigned reg, u16 *vals, unsigned n) |
| 213 | { |
| 214 | u8 tx[2]; |
| 215 | int status; |
| 216 | |
| 217 | if ((n + reg) > sizeof mcp->cache) |
| 218 | return -EINVAL; |
| 219 | tx[0] = mcp->addr | 0x01; |
| 220 | tx[1] = reg << 1; |
| 221 | |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 222 | status = spi_write_then_read(mcp->data, tx, sizeof tx, |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 223 | (u8 *)vals, n * 2); |
| 224 | if (status >= 0) { |
| 225 | while (n--) |
| 226 | vals[n] = __le16_to_cpu((__le16)vals[n]); |
| 227 | } |
| 228 | |
| 229 | return status; |
| 230 | } |
| 231 | |
| 232 | static const struct mcp23s08_ops mcp23s08_ops = { |
| 233 | .read = mcp23s08_read, |
| 234 | .write = mcp23s08_write, |
| 235 | .read_regs = mcp23s08_read_regs, |
| 236 | }; |
| 237 | |
| 238 | static const struct mcp23s08_ops mcp23s17_ops = { |
| 239 | .read = mcp23s17_read, |
| 240 | .write = mcp23s17_write, |
| 241 | .read_regs = mcp23s17_read_regs, |
| 242 | }; |
| 243 | |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 244 | #endif /* CONFIG_SPI_MASTER */ |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 245 | |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 246 | /*----------------------------------------------------------------------*/ |
| 247 | |
| 248 | static int mcp23s08_direction_input(struct gpio_chip *chip, unsigned offset) |
| 249 | { |
| 250 | struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); |
| 251 | int status; |
| 252 | |
| 253 | mutex_lock(&mcp->lock); |
| 254 | mcp->cache[MCP_IODIR] |= (1 << offset); |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 255 | status = mcp->ops->write(mcp, MCP_IODIR, mcp->cache[MCP_IODIR]); |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 256 | mutex_unlock(&mcp->lock); |
| 257 | return status; |
| 258 | } |
| 259 | |
| 260 | static int mcp23s08_get(struct gpio_chip *chip, unsigned offset) |
| 261 | { |
| 262 | struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); |
| 263 | int status; |
| 264 | |
| 265 | mutex_lock(&mcp->lock); |
| 266 | |
| 267 | /* REVISIT reading this clears any IRQ ... */ |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 268 | status = mcp->ops->read(mcp, MCP_GPIO); |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 269 | if (status < 0) |
| 270 | status = 0; |
| 271 | else { |
| 272 | mcp->cache[MCP_GPIO] = status; |
| 273 | status = !!(status & (1 << offset)); |
| 274 | } |
| 275 | mutex_unlock(&mcp->lock); |
| 276 | return status; |
| 277 | } |
| 278 | |
| 279 | static int __mcp23s08_set(struct mcp23s08 *mcp, unsigned mask, int value) |
| 280 | { |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 281 | unsigned olat = mcp->cache[MCP_OLAT]; |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 282 | |
| 283 | if (value) |
| 284 | olat |= mask; |
| 285 | else |
| 286 | olat &= ~mask; |
| 287 | mcp->cache[MCP_OLAT] = olat; |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 288 | return mcp->ops->write(mcp, MCP_OLAT, olat); |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 289 | } |
| 290 | |
| 291 | static void mcp23s08_set(struct gpio_chip *chip, unsigned offset, int value) |
| 292 | { |
| 293 | struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 294 | unsigned mask = 1 << offset; |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 295 | |
| 296 | mutex_lock(&mcp->lock); |
| 297 | __mcp23s08_set(mcp, mask, value); |
| 298 | mutex_unlock(&mcp->lock); |
| 299 | } |
| 300 | |
| 301 | static int |
| 302 | mcp23s08_direction_output(struct gpio_chip *chip, unsigned offset, int value) |
| 303 | { |
| 304 | struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 305 | unsigned mask = 1 << offset; |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 306 | int status; |
| 307 | |
| 308 | mutex_lock(&mcp->lock); |
| 309 | status = __mcp23s08_set(mcp, mask, value); |
| 310 | if (status == 0) { |
| 311 | mcp->cache[MCP_IODIR] &= ~mask; |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 312 | status = mcp->ops->write(mcp, MCP_IODIR, mcp->cache[MCP_IODIR]); |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 313 | } |
| 314 | mutex_unlock(&mcp->lock); |
| 315 | return status; |
| 316 | } |
| 317 | |
| 318 | /*----------------------------------------------------------------------*/ |
| 319 | |
| 320 | #ifdef CONFIG_DEBUG_FS |
| 321 | |
| 322 | #include <linux/seq_file.h> |
| 323 | |
| 324 | /* |
| 325 | * This shows more info than the generic gpio dump code: |
| 326 | * pullups, deglitching, open drain drive. |
| 327 | */ |
| 328 | static void mcp23s08_dbg_show(struct seq_file *s, struct gpio_chip *chip) |
| 329 | { |
| 330 | struct mcp23s08 *mcp; |
| 331 | char bank; |
Roel Kluin | 1d1c1d9 | 2008-05-23 13:04:43 -0700 | [diff] [blame] | 332 | int t; |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 333 | unsigned mask; |
| 334 | |
| 335 | mcp = container_of(chip, struct mcp23s08, chip); |
| 336 | |
| 337 | /* NOTE: we only handle one bank for now ... */ |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 338 | bank = '0' + ((mcp->addr >> 1) & 0x7); |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 339 | |
| 340 | mutex_lock(&mcp->lock); |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 341 | t = mcp->ops->read_regs(mcp, 0, mcp->cache, ARRAY_SIZE(mcp->cache)); |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 342 | if (t < 0) { |
| 343 | seq_printf(s, " I/O ERROR %d\n", t); |
| 344 | goto done; |
| 345 | } |
| 346 | |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 347 | for (t = 0, mask = 1; t < chip->ngpio; t++, mask <<= 1) { |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 348 | const char *label; |
| 349 | |
| 350 | label = gpiochip_is_requested(chip, t); |
| 351 | if (!label) |
| 352 | continue; |
| 353 | |
| 354 | seq_printf(s, " gpio-%-3d P%c.%d (%-12s) %s %s %s", |
| 355 | chip->base + t, bank, t, label, |
| 356 | (mcp->cache[MCP_IODIR] & mask) ? "in " : "out", |
| 357 | (mcp->cache[MCP_GPIO] & mask) ? "hi" : "lo", |
Peter Korsgaard | eb1567f | 2012-04-25 11:51:53 +0200 | [diff] [blame] | 358 | (mcp->cache[MCP_GPPU] & mask) ? "up" : " "); |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 359 | /* NOTE: ignoring the irq-related registers */ |
| 360 | seq_printf(s, "\n"); |
| 361 | } |
| 362 | done: |
| 363 | mutex_unlock(&mcp->lock); |
| 364 | } |
| 365 | |
| 366 | #else |
| 367 | #define mcp23s08_dbg_show NULL |
| 368 | #endif |
| 369 | |
| 370 | /*----------------------------------------------------------------------*/ |
| 371 | |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 372 | static int mcp23s08_probe_one(struct mcp23s08 *mcp, struct device *dev, |
| 373 | void *data, unsigned addr, |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 374 | unsigned type, unsigned base, unsigned pullups) |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 375 | { |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 376 | int status; |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 377 | |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 378 | mutex_init(&mcp->lock); |
| 379 | |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 380 | mcp->data = data; |
| 381 | mcp->addr = addr; |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 382 | |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 383 | mcp->chip.direction_input = mcp23s08_direction_input; |
| 384 | mcp->chip.get = mcp23s08_get; |
| 385 | mcp->chip.direction_output = mcp23s08_direction_output; |
| 386 | mcp->chip.set = mcp23s08_set; |
| 387 | mcp->chip.dbg_show = mcp23s08_dbg_show; |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 388 | #ifdef CONFIG_OF |
| 389 | mcp->chip.of_gpio_n_cells = 2; |
| 390 | mcp->chip.of_node = dev->of_node; |
| 391 | #endif |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 392 | |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 393 | switch (type) { |
| 394 | #ifdef CONFIG_SPI_MASTER |
| 395 | case MCP_TYPE_S08: |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 396 | mcp->ops = &mcp23s08_ops; |
| 397 | mcp->chip.ngpio = 8; |
| 398 | mcp->chip.label = "mcp23s08"; |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 399 | break; |
| 400 | |
| 401 | case MCP_TYPE_S17: |
| 402 | mcp->ops = &mcp23s17_ops; |
| 403 | mcp->chip.ngpio = 16; |
| 404 | mcp->chip.label = "mcp23s17"; |
| 405 | break; |
| 406 | #endif /* CONFIG_SPI_MASTER */ |
| 407 | |
Daniel M. Weeks | cbf24fa | 2012-11-06 23:51:05 -0500 | [diff] [blame] | 408 | #if IS_ENABLED(CONFIG_I2C) |
Peter Korsgaard | 752ad5e | 2011-07-15 10:25:32 +0200 | [diff] [blame] | 409 | case MCP_TYPE_008: |
| 410 | mcp->ops = &mcp23008_ops; |
| 411 | mcp->chip.ngpio = 8; |
| 412 | mcp->chip.label = "mcp23008"; |
| 413 | break; |
| 414 | |
| 415 | case MCP_TYPE_017: |
| 416 | mcp->ops = &mcp23017_ops; |
| 417 | mcp->chip.ngpio = 16; |
| 418 | mcp->chip.label = "mcp23017"; |
| 419 | break; |
| 420 | #endif /* CONFIG_I2C */ |
| 421 | |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 422 | default: |
| 423 | dev_err(dev, "invalid device type (%d)\n", type); |
| 424 | return -EINVAL; |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 425 | } |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 426 | |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 427 | mcp->chip.base = base; |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 428 | mcp->chip.can_sleep = 1; |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 429 | mcp->chip.dev = dev; |
Guennadi Liakhovetski | d72cbed | 2008-04-28 02:14:45 -0700 | [diff] [blame] | 430 | mcp->chip.owner = THIS_MODULE; |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 431 | |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 432 | /* verify MCP_IOCON.SEQOP = 0, so sequential reads work, |
| 433 | * and MCP_IOCON.HAEN = 1, so we work with all chips. |
| 434 | */ |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 435 | status = mcp->ops->read(mcp, MCP_IOCON); |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 436 | if (status < 0) |
| 437 | goto fail; |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 438 | if ((status & IOCON_SEQOP) || !(status & IOCON_HAEN)) { |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 439 | /* mcp23s17 has IOCON twice, make sure they are in sync */ |
| 440 | status &= ~(IOCON_SEQOP | (IOCON_SEQOP << 8)); |
| 441 | status |= IOCON_HAEN | (IOCON_HAEN << 8); |
| 442 | status = mcp->ops->write(mcp, MCP_IOCON, status); |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 443 | if (status < 0) |
| 444 | goto fail; |
| 445 | } |
| 446 | |
| 447 | /* configure ~100K pullups */ |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 448 | status = mcp->ops->write(mcp, MCP_GPPU, pullups); |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 449 | if (status < 0) |
| 450 | goto fail; |
| 451 | |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 452 | status = mcp->ops->read_regs(mcp, 0, mcp->cache, ARRAY_SIZE(mcp->cache)); |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 453 | if (status < 0) |
| 454 | goto fail; |
| 455 | |
| 456 | /* disable inverter on input */ |
| 457 | if (mcp->cache[MCP_IPOL] != 0) { |
| 458 | mcp->cache[MCP_IPOL] = 0; |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 459 | status = mcp->ops->write(mcp, MCP_IPOL, 0); |
| 460 | if (status < 0) |
| 461 | goto fail; |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 462 | } |
| 463 | |
| 464 | /* disable irqs */ |
| 465 | if (mcp->cache[MCP_GPINTEN] != 0) { |
| 466 | mcp->cache[MCP_GPINTEN] = 0; |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 467 | status = mcp->ops->write(mcp, MCP_GPINTEN, 0); |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 468 | if (status < 0) |
| 469 | goto fail; |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 470 | } |
| 471 | |
| 472 | status = gpiochip_add(&mcp->chip); |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 473 | fail: |
| 474 | if (status < 0) |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 475 | dev_dbg(dev, "can't setup chip %d, --> %d\n", |
| 476 | addr, status); |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 477 | return status; |
| 478 | } |
| 479 | |
Peter Korsgaard | 752ad5e | 2011-07-15 10:25:32 +0200 | [diff] [blame] | 480 | /*----------------------------------------------------------------------*/ |
| 481 | |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 482 | #ifdef CONFIG_OF |
| 483 | #ifdef CONFIG_SPI_MASTER |
| 484 | static struct of_device_id mcp23s08_spi_of_match[] = { |
| 485 | { |
Lars Poeschel | 4597168 | 2013-08-28 10:38:50 +0200 | [diff] [blame^] | 486 | .compatible = "microchip,mcp23s08", |
| 487 | .data = (void *) MCP_TYPE_S08, |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 488 | }, |
| 489 | { |
Lars Poeschel | 4597168 | 2013-08-28 10:38:50 +0200 | [diff] [blame^] | 490 | .compatible = "microchip,mcp23s17", |
| 491 | .data = (void *) MCP_TYPE_S17, |
| 492 | }, |
| 493 | /* NOTE: The use of the mcp prefix is deprecated and will be removed. */ |
| 494 | { |
| 495 | .compatible = "mcp,mcp23s08", |
| 496 | .data = (void *) MCP_TYPE_S08, |
| 497 | }, |
| 498 | { |
| 499 | .compatible = "mcp,mcp23s17", |
| 500 | .data = (void *) MCP_TYPE_S17, |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 501 | }, |
| 502 | { }, |
| 503 | }; |
| 504 | MODULE_DEVICE_TABLE(of, mcp23s08_spi_of_match); |
| 505 | #endif |
| 506 | |
| 507 | #if IS_ENABLED(CONFIG_I2C) |
| 508 | static struct of_device_id mcp23s08_i2c_of_match[] = { |
| 509 | { |
Lars Poeschel | 4597168 | 2013-08-28 10:38:50 +0200 | [diff] [blame^] | 510 | .compatible = "microchip,mcp23008", |
| 511 | .data = (void *) MCP_TYPE_008, |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 512 | }, |
| 513 | { |
Lars Poeschel | 4597168 | 2013-08-28 10:38:50 +0200 | [diff] [blame^] | 514 | .compatible = "microchip,mcp23017", |
| 515 | .data = (void *) MCP_TYPE_017, |
| 516 | }, |
| 517 | /* NOTE: The use of the mcp prefix is deprecated and will be removed. */ |
| 518 | { |
| 519 | .compatible = "mcp,mcp23008", |
| 520 | .data = (void *) MCP_TYPE_008, |
| 521 | }, |
| 522 | { |
| 523 | .compatible = "mcp,mcp23017", |
| 524 | .data = (void *) MCP_TYPE_017, |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 525 | }, |
| 526 | { }, |
| 527 | }; |
| 528 | MODULE_DEVICE_TABLE(of, mcp23s08_i2c_of_match); |
| 529 | #endif |
| 530 | #endif /* CONFIG_OF */ |
| 531 | |
| 532 | |
Daniel M. Weeks | cbf24fa | 2012-11-06 23:51:05 -0500 | [diff] [blame] | 533 | #if IS_ENABLED(CONFIG_I2C) |
Peter Korsgaard | 752ad5e | 2011-07-15 10:25:32 +0200 | [diff] [blame] | 534 | |
Bill Pemberton | 3836309 | 2012-11-19 13:22:34 -0500 | [diff] [blame] | 535 | static int mcp230xx_probe(struct i2c_client *client, |
Peter Korsgaard | 752ad5e | 2011-07-15 10:25:32 +0200 | [diff] [blame] | 536 | const struct i2c_device_id *id) |
| 537 | { |
| 538 | struct mcp23s08_platform_data *pdata; |
| 539 | struct mcp23s08 *mcp; |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 540 | int status, base, pullups; |
| 541 | const struct of_device_id *match; |
Peter Korsgaard | 752ad5e | 2011-07-15 10:25:32 +0200 | [diff] [blame] | 542 | |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 543 | match = of_match_device(of_match_ptr(mcp23s08_i2c_of_match), |
| 544 | &client->dev); |
Jingoo Han | e56aee1 | 2013-07-30 17:08:05 +0900 | [diff] [blame] | 545 | pdata = dev_get_platdata(&client->dev); |
Daniel M. Weeks | 8a56406 | 2013-07-19 00:19:58 -0400 | [diff] [blame] | 546 | if (match || !pdata) { |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 547 | base = -1; |
| 548 | pullups = 0; |
| 549 | } else { |
Daniel M. Weeks | 8a56406 | 2013-07-19 00:19:58 -0400 | [diff] [blame] | 550 | if (!gpio_is_valid(pdata->base)) { |
| 551 | dev_dbg(&client->dev, "invalid platform data\n"); |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 552 | return -EINVAL; |
| 553 | } |
| 554 | base = pdata->base; |
| 555 | pullups = pdata->chip[0].pullups; |
Peter Korsgaard | 752ad5e | 2011-07-15 10:25:32 +0200 | [diff] [blame] | 556 | } |
| 557 | |
| 558 | mcp = kzalloc(sizeof *mcp, GFP_KERNEL); |
| 559 | if (!mcp) |
| 560 | return -ENOMEM; |
| 561 | |
| 562 | status = mcp23s08_probe_one(mcp, &client->dev, client, client->addr, |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 563 | id->driver_data, base, pullups); |
Peter Korsgaard | 752ad5e | 2011-07-15 10:25:32 +0200 | [diff] [blame] | 564 | if (status) |
| 565 | goto fail; |
| 566 | |
| 567 | i2c_set_clientdata(client, mcp); |
| 568 | |
| 569 | return 0; |
| 570 | |
| 571 | fail: |
| 572 | kfree(mcp); |
| 573 | |
| 574 | return status; |
| 575 | } |
| 576 | |
Bill Pemberton | 206210c | 2012-11-19 13:25:50 -0500 | [diff] [blame] | 577 | static int mcp230xx_remove(struct i2c_client *client) |
Peter Korsgaard | 752ad5e | 2011-07-15 10:25:32 +0200 | [diff] [blame] | 578 | { |
| 579 | struct mcp23s08 *mcp = i2c_get_clientdata(client); |
| 580 | int status; |
| 581 | |
| 582 | status = gpiochip_remove(&mcp->chip); |
| 583 | if (status == 0) |
| 584 | kfree(mcp); |
| 585 | |
| 586 | return status; |
| 587 | } |
| 588 | |
| 589 | static const struct i2c_device_id mcp230xx_id[] = { |
| 590 | { "mcp23008", MCP_TYPE_008 }, |
| 591 | { "mcp23017", MCP_TYPE_017 }, |
| 592 | { }, |
| 593 | }; |
| 594 | MODULE_DEVICE_TABLE(i2c, mcp230xx_id); |
| 595 | |
| 596 | static struct i2c_driver mcp230xx_driver = { |
| 597 | .driver = { |
| 598 | .name = "mcp230xx", |
| 599 | .owner = THIS_MODULE, |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 600 | .of_match_table = of_match_ptr(mcp23s08_i2c_of_match), |
Peter Korsgaard | 752ad5e | 2011-07-15 10:25:32 +0200 | [diff] [blame] | 601 | }, |
| 602 | .probe = mcp230xx_probe, |
Bill Pemberton | 8283c4f | 2012-11-19 13:20:08 -0500 | [diff] [blame] | 603 | .remove = mcp230xx_remove, |
Peter Korsgaard | 752ad5e | 2011-07-15 10:25:32 +0200 | [diff] [blame] | 604 | .id_table = mcp230xx_id, |
| 605 | }; |
| 606 | |
| 607 | static int __init mcp23s08_i2c_init(void) |
| 608 | { |
| 609 | return i2c_add_driver(&mcp230xx_driver); |
| 610 | } |
| 611 | |
| 612 | static void mcp23s08_i2c_exit(void) |
| 613 | { |
| 614 | i2c_del_driver(&mcp230xx_driver); |
| 615 | } |
| 616 | |
| 617 | #else |
| 618 | |
| 619 | static int __init mcp23s08_i2c_init(void) { return 0; } |
| 620 | static void mcp23s08_i2c_exit(void) { } |
| 621 | |
| 622 | #endif /* CONFIG_I2C */ |
| 623 | |
| 624 | /*----------------------------------------------------------------------*/ |
| 625 | |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 626 | #ifdef CONFIG_SPI_MASTER |
| 627 | |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 628 | static int mcp23s08_probe(struct spi_device *spi) |
| 629 | { |
| 630 | struct mcp23s08_platform_data *pdata; |
| 631 | unsigned addr; |
| 632 | unsigned chips = 0; |
| 633 | struct mcp23s08_driver_data *data; |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 634 | int status, type; |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 635 | unsigned base = -1, |
| 636 | ngpio = 0, |
| 637 | pullups[ARRAY_SIZE(pdata->chip)]; |
| 638 | const struct of_device_id *match; |
| 639 | u32 spi_present_mask = 0; |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 640 | |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 641 | match = of_match_device(of_match_ptr(mcp23s08_spi_of_match), &spi->dev); |
| 642 | if (match) { |
| 643 | type = (int)match->data; |
| 644 | status = of_property_read_u32(spi->dev.of_node, |
Lars Poeschel | 4597168 | 2013-08-28 10:38:50 +0200 | [diff] [blame^] | 645 | "microchip,spi-present-mask", &spi_present_mask); |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 646 | if (status) { |
Lars Poeschel | 4597168 | 2013-08-28 10:38:50 +0200 | [diff] [blame^] | 647 | status = of_property_read_u32(spi->dev.of_node, |
| 648 | "mcp,spi-present-mask", &spi_present_mask); |
| 649 | if (status) { |
| 650 | dev_err(&spi->dev, |
| 651 | "DT has no spi-present-mask\n"); |
| 652 | return -ENODEV; |
| 653 | } |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 654 | } |
| 655 | if ((spi_present_mask <= 0) || (spi_present_mask >= 256)) { |
| 656 | dev_err(&spi->dev, "invalid spi-present-mask\n"); |
| 657 | return -ENODEV; |
| 658 | } |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 659 | |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 660 | for (addr = 0; addr < ARRAY_SIZE(pdata->chip); addr++) |
| 661 | pullups[addr] = 0; |
| 662 | } else { |
| 663 | type = spi_get_device_id(spi)->driver_data; |
Jingoo Han | e56aee1 | 2013-07-30 17:08:05 +0900 | [diff] [blame] | 664 | pdata = dev_get_platdata(&spi->dev); |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 665 | if (!pdata || !gpio_is_valid(pdata->base)) { |
| 666 | dev_dbg(&spi->dev, |
| 667 | "invalid or missing platform data\n"); |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 668 | return -EINVAL; |
| 669 | } |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 670 | |
| 671 | for (addr = 0; addr < ARRAY_SIZE(pdata->chip); addr++) { |
| 672 | if (!pdata->chip[addr].is_present) |
| 673 | continue; |
| 674 | chips++; |
| 675 | if ((type == MCP_TYPE_S08) && (addr > 3)) { |
| 676 | dev_err(&spi->dev, |
| 677 | "mcp23s08 only supports address 0..3\n"); |
| 678 | return -EINVAL; |
| 679 | } |
| 680 | spi_present_mask |= 1 << addr; |
| 681 | pullups[addr] = pdata->chip[addr].pullups; |
| 682 | } |
| 683 | |
| 684 | if (!chips) |
| 685 | return -ENODEV; |
| 686 | |
| 687 | base = pdata->base; |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 688 | } |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 689 | |
| 690 | data = kzalloc(sizeof *data + chips * sizeof(struct mcp23s08), |
| 691 | GFP_KERNEL); |
| 692 | if (!data) |
| 693 | return -ENOMEM; |
| 694 | spi_set_drvdata(spi, data); |
| 695 | |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 696 | for (addr = 0; addr < ARRAY_SIZE(pdata->chip); addr++) { |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 697 | if (!(spi_present_mask & (1 << addr))) |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 698 | continue; |
| 699 | chips--; |
| 700 | data->mcp[addr] = &data->chip[chips]; |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 701 | status = mcp23s08_probe_one(data->mcp[addr], &spi->dev, spi, |
| 702 | 0x40 | (addr << 1), type, base, |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 703 | pullups[addr]); |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 704 | if (status < 0) |
| 705 | goto fail; |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 706 | |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 707 | if (base != -1) |
| 708 | base += (type == MCP_TYPE_S17) ? 16 : 8; |
| 709 | ngpio += (type == MCP_TYPE_S17) ? 16 : 8; |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 710 | } |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 711 | data->ngpio = ngpio; |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 712 | |
| 713 | /* NOTE: these chips have a relatively sane IRQ framework, with |
| 714 | * per-signal masking and level/edge triggering. It's not yet |
| 715 | * handled here... |
| 716 | */ |
| 717 | |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 718 | return 0; |
| 719 | |
| 720 | fail: |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 721 | for (addr = 0; addr < ARRAY_SIZE(data->mcp); addr++) { |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 722 | int tmp; |
| 723 | |
| 724 | if (!data->mcp[addr]) |
| 725 | continue; |
| 726 | tmp = gpiochip_remove(&data->mcp[addr]->chip); |
| 727 | if (tmp < 0) |
| 728 | dev_err(&spi->dev, "%s --> %d\n", "remove", tmp); |
| 729 | } |
| 730 | kfree(data); |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 731 | return status; |
| 732 | } |
| 733 | |
| 734 | static int mcp23s08_remove(struct spi_device *spi) |
| 735 | { |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 736 | struct mcp23s08_driver_data *data = spi_get_drvdata(spi); |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 737 | unsigned addr; |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 738 | int status = 0; |
| 739 | |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 740 | for (addr = 0; addr < ARRAY_SIZE(data->mcp); addr++) { |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 741 | int tmp; |
| 742 | |
| 743 | if (!data->mcp[addr]) |
| 744 | continue; |
| 745 | |
| 746 | tmp = gpiochip_remove(&data->mcp[addr]->chip); |
| 747 | if (tmp < 0) { |
| 748 | dev_err(&spi->dev, "%s --> %d\n", "remove", tmp); |
| 749 | status = tmp; |
| 750 | } |
| 751 | } |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 752 | if (status == 0) |
David Brownell | 8f1cc3b | 2008-07-25 01:46:09 -0700 | [diff] [blame] | 753 | kfree(data); |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 754 | return status; |
| 755 | } |
| 756 | |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 757 | static const struct spi_device_id mcp23s08_ids[] = { |
| 758 | { "mcp23s08", MCP_TYPE_S08 }, |
| 759 | { "mcp23s17", MCP_TYPE_S17 }, |
| 760 | { }, |
| 761 | }; |
| 762 | MODULE_DEVICE_TABLE(spi, mcp23s08_ids); |
| 763 | |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 764 | static struct spi_driver mcp23s08_driver = { |
| 765 | .probe = mcp23s08_probe, |
| 766 | .remove = mcp23s08_remove, |
Peter Korsgaard | 0b7bb77 | 2011-03-09 17:56:30 +0100 | [diff] [blame] | 767 | .id_table = mcp23s08_ids, |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 768 | .driver = { |
| 769 | .name = "mcp23s08", |
| 770 | .owner = THIS_MODULE, |
Lars Poeschel | 97ddb1c | 2013-04-04 12:02:02 +0200 | [diff] [blame] | 771 | .of_match_table = of_match_ptr(mcp23s08_spi_of_match), |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 772 | }, |
| 773 | }; |
| 774 | |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 775 | static int __init mcp23s08_spi_init(void) |
| 776 | { |
| 777 | return spi_register_driver(&mcp23s08_driver); |
| 778 | } |
| 779 | |
| 780 | static void mcp23s08_spi_exit(void) |
| 781 | { |
| 782 | spi_unregister_driver(&mcp23s08_driver); |
| 783 | } |
| 784 | |
| 785 | #else |
| 786 | |
| 787 | static int __init mcp23s08_spi_init(void) { return 0; } |
| 788 | static void mcp23s08_spi_exit(void) { } |
| 789 | |
| 790 | #endif /* CONFIG_SPI_MASTER */ |
| 791 | |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 792 | /*----------------------------------------------------------------------*/ |
| 793 | |
| 794 | static int __init mcp23s08_init(void) |
| 795 | { |
Peter Korsgaard | 752ad5e | 2011-07-15 10:25:32 +0200 | [diff] [blame] | 796 | int ret; |
| 797 | |
| 798 | ret = mcp23s08_spi_init(); |
| 799 | if (ret) |
| 800 | goto spi_fail; |
| 801 | |
| 802 | ret = mcp23s08_i2c_init(); |
| 803 | if (ret) |
| 804 | goto i2c_fail; |
| 805 | |
| 806 | return 0; |
| 807 | |
| 808 | i2c_fail: |
| 809 | mcp23s08_spi_exit(); |
| 810 | spi_fail: |
| 811 | return ret; |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 812 | } |
Peter Korsgaard | 752ad5e | 2011-07-15 10:25:32 +0200 | [diff] [blame] | 813 | /* register after spi/i2c postcore initcall and before |
David Brownell | 673c0c0 | 2008-10-15 22:02:46 -0700 | [diff] [blame] | 814 | * subsys initcalls that may rely on these GPIOs |
| 815 | */ |
| 816 | subsys_initcall(mcp23s08_init); |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 817 | |
| 818 | static void __exit mcp23s08_exit(void) |
| 819 | { |
Peter Korsgaard | d62b98f | 2011-07-15 10:25:31 +0200 | [diff] [blame] | 820 | mcp23s08_spi_exit(); |
Peter Korsgaard | 752ad5e | 2011-07-15 10:25:32 +0200 | [diff] [blame] | 821 | mcp23s08_i2c_exit(); |
David Brownell | e58b9e2 | 2008-02-04 22:28:25 -0800 | [diff] [blame] | 822 | } |
| 823 | module_exit(mcp23s08_exit); |
| 824 | |
| 825 | MODULE_LICENSE("GPL"); |