268 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/* MCP23S08 SPI GPIO driver */3 4#include <linux/mod_devicetable.h>5#include <linux/module.h>6#include <linux/property.h>7#include <linux/regmap.h>8#include <linux/spi/spi.h>9 10#include "pinctrl-mcp23s08.h"11 12#define MCP_MAX_DEV_PER_CS 813 14/*15 * A given spi_device can represent up to eight mcp23sxx chips16 * sharing the same chipselect but using different addresses17 * (e.g. chips #0 and #3 might be populated, but not #1 or #2).18 * Driver data holds all the per-chip data.19 */20struct mcp23s08_driver_data {21 unsigned ngpio;22 struct mcp23s08 *mcp[8];23 struct mcp23s08 chip[];24};25 26static int mcp23sxx_spi_write(void *context, const void *data, size_t count)27{28 struct mcp23s08 *mcp = context;29 struct spi_device *spi = to_spi_device(mcp->dev);30 struct spi_message m;31 struct spi_transfer t[2] = { { .tx_buf = &mcp->addr, .len = 1, },32 { .tx_buf = data, .len = count, }, };33 34 spi_message_init(&m);35 spi_message_add_tail(&t[0], &m);36 spi_message_add_tail(&t[1], &m);37 38 return spi_sync(spi, &m);39}40 41static int mcp23sxx_spi_gather_write(void *context,42 const void *reg, size_t reg_size,43 const void *val, size_t val_size)44{45 struct mcp23s08 *mcp = context;46 struct spi_device *spi = to_spi_device(mcp->dev);47 struct spi_message m;48 struct spi_transfer t[3] = { { .tx_buf = &mcp->addr, .len = 1, },49 { .tx_buf = reg, .len = reg_size, },50 { .tx_buf = val, .len = val_size, }, };51 52 spi_message_init(&m);53 spi_message_add_tail(&t[0], &m);54 spi_message_add_tail(&t[1], &m);55 spi_message_add_tail(&t[2], &m);56 57 return spi_sync(spi, &m);58}59 60static int mcp23sxx_spi_read(void *context, const void *reg, size_t reg_size,61 void *val, size_t val_size)62{63 struct mcp23s08 *mcp = context;64 struct spi_device *spi = to_spi_device(mcp->dev);65 u8 tx[2];66 67 if (reg_size != 1)68 return -EINVAL;69 70 tx[0] = mcp->addr | 0x01;71 tx[1] = *((u8 *) reg);72 73 return spi_write_then_read(spi, tx, sizeof(tx), val, val_size);74}75 76static const struct regmap_bus mcp23sxx_spi_regmap = {77 .write = mcp23sxx_spi_write,78 .gather_write = mcp23sxx_spi_gather_write,79 .read = mcp23sxx_spi_read,80};81 82static int mcp23s08_spi_regmap_init(struct mcp23s08 *mcp, struct device *dev,83 unsigned int addr,84 const struct mcp23s08_info *info)85{86 struct regmap_config *copy;87 const char *name;88 89 switch (info->type) {90 case MCP_TYPE_S08:91 mcp->chip.label = devm_kasprintf(dev, GFP_KERNEL, "mcp23s08.%d", addr);92 if (!mcp->chip.label)93 return -ENOMEM;94 95 name = devm_kasprintf(dev, GFP_KERNEL, "%d", addr);96 if (!name)97 return -ENOMEM;98 99 break;100 101 case MCP_TYPE_S17:102 mcp->chip.label = devm_kasprintf(dev, GFP_KERNEL, "mcp23s17.%d", addr);103 if (!mcp->chip.label)104 return -ENOMEM;105 106 name = devm_kasprintf(dev, GFP_KERNEL, "%d", addr);107 if (!name)108 return -ENOMEM;109 110 break;111 112 case MCP_TYPE_S18:113 mcp->chip.label = info->label;114 name = info->regmap->name;115 break;116 117 default:118 dev_err(dev, "invalid device type (%d)\n", info->type);119 return -EINVAL;120 }121 122 mcp->reg_shift = info->reg_shift;123 mcp->chip.ngpio = info->ngpio;124 copy = devm_kmemdup(dev, info->regmap, sizeof(*info->regmap), GFP_KERNEL);125 if (!copy)126 return -ENOMEM;127 128 copy->name = name;129 130 mcp->regmap = devm_regmap_init(dev, &mcp23sxx_spi_regmap, mcp, copy);131 if (IS_ERR(mcp->regmap))132 dev_err(dev, "regmap init failed for %s\n", mcp->chip.label);133 return PTR_ERR_OR_ZERO(mcp->regmap);134}135 136static int mcp23s08_probe(struct spi_device *spi)137{138 struct mcp23s08_driver_data *data;139 const struct mcp23s08_info *info;140 struct device *dev = &spi->dev;141 unsigned long spi_present_mask;142 unsigned int ngpio = 0;143 unsigned int addr;144 int chips;145 int ret;146 u32 v;147 148 info = spi_get_device_match_data(spi);149 150 ret = device_property_read_u32(dev, "microchip,spi-present-mask", &v);151 if (ret) {152 ret = device_property_read_u32(dev, "mcp,spi-present-mask", &v);153 if (ret) {154 dev_err(dev, "missing spi-present-mask");155 return ret;156 }157 }158 spi_present_mask = v;159 160 if (!spi_present_mask || spi_present_mask >= BIT(MCP_MAX_DEV_PER_CS)) {161 dev_err(dev, "invalid spi-present-mask");162 return -ENODEV;163 }164 165 chips = hweight_long(spi_present_mask);166 167 data = devm_kzalloc(dev, struct_size(data, chip, chips), GFP_KERNEL);168 if (!data)169 return -ENOMEM;170 171 spi_set_drvdata(spi, data);172 173 for_each_set_bit(addr, &spi_present_mask, MCP_MAX_DEV_PER_CS) {174 data->mcp[addr] = &data->chip[--chips];175 data->mcp[addr]->irq = spi->irq;176 177 ret = mcp23s08_spi_regmap_init(data->mcp[addr], dev, addr, info);178 if (ret)179 return ret;180 181 data->mcp[addr]->pinctrl_desc.name = devm_kasprintf(dev, GFP_KERNEL,182 "mcp23xxx-pinctrl.%d",183 addr);184 if (!data->mcp[addr]->pinctrl_desc.name)185 return -ENOMEM;186 187 ret = mcp23s08_probe_one(data->mcp[addr], dev, 0x40 | (addr << 1),188 info->type, -1);189 if (ret < 0)190 return ret;191 192 ngpio += data->mcp[addr]->chip.ngpio;193 }194 data->ngpio = ngpio;195 196 return 0;197}198 199static const struct mcp23s08_info mcp23s08_spi = {200 .regmap = &mcp23x08_regmap,201 .type = MCP_TYPE_S08,202 .ngpio = 8,203 .reg_shift = 0,204};205 206static const struct mcp23s08_info mcp23s17_spi = {207 .regmap = &mcp23x17_regmap,208 .type = MCP_TYPE_S17,209 .ngpio = 16,210 .reg_shift = 1,211};212 213static const struct mcp23s08_info mcp23s18_spi = {214 .regmap = &mcp23x17_regmap,215 .label = "mcp23s18",216 .type = MCP_TYPE_S18,217 .ngpio = 16,218 .reg_shift = 1,219};220 221static const struct spi_device_id mcp23s08_ids[] = {222 { "mcp23s08", (kernel_ulong_t)&mcp23s08_spi },223 { "mcp23s17", (kernel_ulong_t)&mcp23s17_spi },224 { "mcp23s18", (kernel_ulong_t)&mcp23s18_spi },225 { }226};227MODULE_DEVICE_TABLE(spi, mcp23s08_ids);228 229static const struct of_device_id mcp23s08_spi_of_match[] = {230 { .compatible = "microchip,mcp23s08", .data = &mcp23s08_spi },231 { .compatible = "microchip,mcp23s17", .data = &mcp23s17_spi },232 { .compatible = "microchip,mcp23s18", .data = &mcp23s18_spi },233/* NOTE: The use of the mcp prefix is deprecated and will be removed. */234 { .compatible = "mcp,mcp23s08", .data = &mcp23s08_spi },235 { .compatible = "mcp,mcp23s17", .data = &mcp23s17_spi },236 { }237};238MODULE_DEVICE_TABLE(of, mcp23s08_spi_of_match);239 240static struct spi_driver mcp23s08_driver = {241 .probe = mcp23s08_probe,242 .id_table = mcp23s08_ids,243 .driver = {244 .name = "mcp23s08",245 .of_match_table = mcp23s08_spi_of_match,246 },247};248 249static int __init mcp23s08_spi_init(void)250{251 return spi_register_driver(&mcp23s08_driver);252}253 254/*255 * Register after SPI postcore initcall and before256 * subsys initcalls that may rely on these GPIOs.257 */258subsys_initcall(mcp23s08_spi_init);259 260static void mcp23s08_spi_exit(void)261{262 spi_unregister_driver(&mcp23s08_driver);263}264module_exit(mcp23s08_spi_exit);265 266MODULE_DESCRIPTION("MCP23S08 SPI GPIO driver");267MODULE_LICENSE("GPL");268