diff options
Diffstat (limited to 'drivers/misc/eeprom/eeprom_93xx46.c')
-rw-r--r-- | drivers/misc/eeprom/eeprom_93xx46.c | 332 |
1 files changed, 269 insertions, 63 deletions
diff --git a/drivers/misc/eeprom/eeprom_93xx46.c b/drivers/misc/eeprom/eeprom_93xx46.c index ff63f05edc76..426fe2fd5238 100644 --- a/drivers/misc/eeprom/eeprom_93xx46.c +++ b/drivers/misc/eeprom/eeprom_93xx46.c @@ -10,12 +10,17 @@ #include <linux/delay.h> #include <linux/device.h> +#include <linux/gpio/consumer.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> #include <linux/slab.h> #include <linux/spi/spi.h> -#include <linux/sysfs.h> +#include <linux/nvmem-provider.h> +#include <linux/regmap.h> #include <linux/eeprom_93xx46.h> #define OP_START 0x4 @@ -25,73 +30,111 @@ #define ADDR_ERAL 0x20 #define ADDR_EWEN 0x30 +struct eeprom_93xx46_devtype_data { + unsigned int quirks; +}; + +static const struct eeprom_93xx46_devtype_data atmel_at93c46d_data = { + .quirks = EEPROM_93XX46_QUIRK_SINGLE_WORD_READ | + EEPROM_93XX46_QUIRK_INSTRUCTION_LENGTH, +}; + struct eeprom_93xx46_dev { struct spi_device *spi; struct eeprom_93xx46_platform_data *pdata; - struct bin_attribute bin; struct mutex lock; + struct regmap_config regmap_config; + struct nvmem_config nvmem_config; + struct nvmem_device *nvmem; int addrlen; + int size; }; +static inline bool has_quirk_single_word_read(struct eeprom_93xx46_dev *edev) +{ + return edev->pdata->quirks & EEPROM_93XX46_QUIRK_SINGLE_WORD_READ; +} + +static inline bool has_quirk_instruction_length(struct eeprom_93xx46_dev *edev) +{ + return edev->pdata->quirks & EEPROM_93XX46_QUIRK_INSTRUCTION_LENGTH; +} + static ssize_t -eeprom_93xx46_bin_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) +eeprom_93xx46_read(struct eeprom_93xx46_dev *edev, char *buf, + unsigned off, size_t count) { - struct eeprom_93xx46_dev *edev; - struct device *dev; - struct spi_message m; - struct spi_transfer t[2]; - int bits, ret; - u16 cmd_addr; + ssize_t ret = 0; - dev = container_of(kobj, struct device, kobj); - edev = dev_get_drvdata(dev); + if (unlikely(off >= edev->size)) + return 0; + if ((off + count) > edev->size) + count = edev->size - off; + if (unlikely(!count)) + return count; - cmd_addr = OP_READ << edev->addrlen; + mutex_lock(&edev->lock); - if (edev->addrlen == 7) { - cmd_addr |= off & 0x7f; - bits = 10; - } else { - cmd_addr |= off & 0x3f; - bits = 9; - } + if (edev->pdata->prepare) + edev->pdata->prepare(edev); - dev_dbg(&edev->spi->dev, "read cmd 0x%x, %d Hz\n", - cmd_addr, edev->spi->max_speed_hz); + while (count) { + struct spi_message m; + struct spi_transfer t[2] = { { 0 } }; + u16 cmd_addr = OP_READ << edev->addrlen; + size_t nbytes = count; + int bits; + int err; + + if (edev->addrlen == 7) { + cmd_addr |= off & 0x7f; + bits = 10; + if (has_quirk_single_word_read(edev)) + nbytes = 1; + } else { + cmd_addr |= (off >> 1) & 0x3f; + bits = 9; + if (has_quirk_single_word_read(edev)) + nbytes = 2; + } - spi_message_init(&m); - memset(t, 0, sizeof(t)); + dev_dbg(&edev->spi->dev, "read cmd 0x%x, %d Hz\n", + cmd_addr, edev->spi->max_speed_hz); - t[0].tx_buf = (char *)&cmd_addr; - t[0].len = 2; - t[0].bits_per_word = bits; - spi_message_add_tail(&t[0], &m); + spi_message_init(&m); - t[1].rx_buf = buf; - t[1].len = count; - t[1].bits_per_word = 8; - spi_message_add_tail(&t[1], &m); + t[0].tx_buf = (char *)&cmd_addr; + t[0].len = 2; + t[0].bits_per_word = bits; + spi_message_add_tail(&t[0], &m); - mutex_lock(&edev->lock); + t[1].rx_buf = buf; + t[1].len = count; + t[1].bits_per_word = 8; + spi_message_add_tail(&t[1], &m); - if (edev->pdata->prepare) - edev->pdata->prepare(edev); + err = spi_sync(edev->spi, &m); + /* have to wait at least Tcsl ns */ + ndelay(250); - ret = spi_sync(edev->spi, &m); - /* have to wait at least Tcsl ns */ - ndelay(250); - if (ret) { - dev_err(&edev->spi->dev, "read %zu bytes at %d: err. %d\n", - count, (int)off, ret); + if (err) { + dev_err(&edev->spi->dev, "read %zu bytes at %d: err. %d\n", + nbytes, (int)off, err); + ret = err; + break; + } + + buf += nbytes; + off += nbytes; + count -= nbytes; + ret += nbytes; } if (edev->pdata->finish) edev->pdata->finish(edev); mutex_unlock(&edev->lock); - return ret ? : count; + return ret; } static int eeprom_93xx46_ew(struct eeprom_93xx46_dev *edev, int is_on) @@ -110,7 +153,13 @@ static int eeprom_93xx46_ew(struct eeprom_93xx46_dev *edev, int is_on) bits = 9; } - dev_dbg(&edev->spi->dev, "ew cmd 0x%04x\n", cmd_addr); + if (has_quirk_instruction_length(edev)) { + cmd_addr <<= 2; + bits += 2; + } + + dev_dbg(&edev->spi->dev, "ew%s cmd 0x%04x, %d bits\n", + is_on ? "en" : "ds", cmd_addr, bits); spi_message_init(&m); memset(&t, 0, sizeof(t)); @@ -155,7 +204,7 @@ eeprom_93xx46_write_word(struct eeprom_93xx46_dev *edev, bits = 10; data_len = 1; } else { - cmd_addr |= off & 0x3f; + cmd_addr |= (off >> 1) & 0x3f; bits = 9; data_len = 2; } @@ -182,16 +231,17 @@ eeprom_93xx46_write_word(struct eeprom_93xx46_dev *edev, } static ssize_t -eeprom_93xx46_bin_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) +eeprom_93xx46_write(struct eeprom_93xx46_dev *edev, const char *buf, + loff_t off, size_t count) { - struct eeprom_93xx46_dev *edev; - struct device *dev; int i, ret, step = 1; - dev = container_of(kobj, struct device, kobj); - edev = dev_get_drvdata(dev); + if (unlikely(off >= edev->size)) + return -EFBIG; + if ((off + count) > edev->size) + count = edev->size - off; + if (unlikely(!count)) + return count; /* only write even number of bytes on 16-bit devices */ if (edev->addrlen == 6) { @@ -228,6 +278,49 @@ eeprom_93xx46_bin_write(struct file *filp, struct kobject *kobj, return ret ? : count; } +/* + * Provide a regmap interface, which is registered with the NVMEM + * framework +*/ +static int eeprom_93xx46_regmap_read(void *context, const void *reg, + size_t reg_size, void *val, + size_t val_size) +{ + struct eeprom_93xx46_dev *eeprom_93xx46 = context; + off_t offset = *(u32 *)reg; + int err; + + err = eeprom_93xx46_read(eeprom_93xx46, val, offset, val_size); + if (err) + return err; + return 0; +} + +static int eeprom_93xx46_regmap_write(void *context, const void *data, + size_t count) +{ + struct eeprom_93xx46_dev *eeprom_93xx46 = context; + const char *buf; + u32 offset; + size_t len; + int err; + + memcpy(&offset, data, sizeof(offset)); + buf = (const char *)data + sizeof(offset); + len = count - sizeof(offset); + + err = eeprom_93xx46_write(eeprom_93xx46, buf, offset, len); + if (err) + return err; + return 0; +} + +static const struct regmap_bus eeprom_93xx46_regmap_bus = { + .read = eeprom_93xx46_regmap_read, + .write = eeprom_93xx46_regmap_write, + .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, +}; + static int eeprom_93xx46_eral(struct eeprom_93xx46_dev *edev) { struct eeprom_93xx46_platform_data *pd = edev->pdata; @@ -245,6 +338,13 @@ static int eeprom_93xx46_eral(struct eeprom_93xx46_dev *edev) bits = 9; } + if (has_quirk_instruction_length(edev)) { + cmd_addr <<= 2; + bits += 2; + } + + dev_dbg(&edev->spi->dev, "eral cmd 0x%04x, %d bits\n", cmd_addr, bits); + spi_message_init(&m); memset(&t, 0, sizeof(t)); @@ -294,12 +394,101 @@ static ssize_t eeprom_93xx46_store_erase(struct device *dev, } static DEVICE_ATTR(erase, S_IWUSR, NULL, eeprom_93xx46_store_erase); +static void select_assert(void *context) +{ + struct eeprom_93xx46_dev *edev = context; + + gpiod_set_value_cansleep(edev->pdata->select, 1); +} + +static void select_deassert(void *context) +{ + struct eeprom_93xx46_dev *edev = context; + + gpiod_set_value_cansleep(edev->pdata->select, 0); +} + +static const struct of_device_id eeprom_93xx46_of_table[] = { + { .compatible = "eeprom-93xx46", }, + { .compatible = "atmel,at93c46d", .data = &atmel_at93c46d_data, }, + {} +}; +MODULE_DEVICE_TABLE(of, eeprom_93xx46_of_table); + +static int eeprom_93xx46_probe_dt(struct spi_device *spi) +{ + const struct of_device_id *of_id = + of_match_device(eeprom_93xx46_of_table, &spi->dev); + struct device_node *np = spi->dev.of_node; + struct eeprom_93xx46_platform_data *pd; + u32 tmp; + int gpio; + enum of_gpio_flags of_flags; + int ret; + + pd = devm_kzalloc(&spi->dev, sizeof(*pd), GFP_KERNEL); + if (!pd) + return -ENOMEM; + + ret = of_property_read_u32(np, "data-size", &tmp); + if (ret < 0) { + dev_err(&spi->dev, "data-size property not found\n"); + return ret; + } + + if (tmp == 8) { + pd->flags |= EE_ADDR8; + } else if (tmp == 16) { + pd->flags |= EE_ADDR16; + } else { + dev_err(&spi->dev, "invalid data-size (%d)\n", tmp); + return -EINVAL; + } + + if (of_property_read_bool(np, "read-only")) + pd->flags |= EE_READONLY; + + gpio = of_get_named_gpio_flags(np, "select-gpios", 0, &of_flags); + if (gpio_is_valid(gpio)) { + unsigned long flags = + of_flags == OF_GPIO_ACTIVE_LOW ? GPIOF_ACTIVE_LOW : 0; + + ret = devm_gpio_request_one(&spi->dev, gpio, flags, + "eeprom_93xx46_select"); + if (ret) + return ret; + + pd->select = gpio_to_desc(gpio); + pd->prepare = select_assert; + pd->finish = select_deassert; + + gpiod_direction_output(pd->select, 0); + } + + if (of_id->data) { + const struct eeprom_93xx46_devtype_data *data = of_id->data; + + pd->quirks = data->quirks; + } + + spi->dev.platform_data = pd; + + return 0; +} + static int eeprom_93xx46_probe(struct spi_device *spi) { struct eeprom_93xx46_platform_data *pd; struct eeprom_93xx46_dev *edev; + struct regmap *regmap; int err; + if (spi->dev.of_node) { + err = eeprom_93xx46_probe_dt(spi); + if (err < 0) + return err; + } + pd = spi->dev.platform_data; if (!pd) { dev_err(&spi->dev, "missing platform data\n"); @@ -325,19 +514,34 @@ static int eeprom_93xx46_probe(struct spi_device *spi) edev->spi = spi_dev_get(spi); edev->pdata = pd; - sysfs_bin_attr_init(&edev->bin); - edev->bin.attr.name = "eeprom"; - edev->bin.attr.mode = S_IRUSR; - edev->bin.read = eeprom_93xx46_bin_read; - edev->bin.size = 128; - if (!(pd->flags & EE_READONLY)) { - edev->bin.write = eeprom_93xx46_bin_write; - edev->bin.attr.mode |= S_IWUSR; + edev->size = 128; + + edev->regmap_config.reg_bits = 32; + edev->regmap_config.val_bits = 8; + edev->regmap_config.reg_stride = 1; + edev->regmap_config.max_register = edev->size - 1; + + regmap = devm_regmap_init(&spi->dev, &eeprom_93xx46_regmap_bus, edev, + &edev->regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "regmap init failed\n"); + err = PTR_ERR(regmap); + goto fail; } - err = sysfs_create_bin_file(&spi->dev.kobj, &edev->bin); - if (err) + edev->nvmem_config.name = dev_name(&spi->dev); + edev->nvmem_config.dev = &spi->dev; + edev->nvmem_config.read_only = pd->flags & EE_READONLY; + edev->nvmem_config.root_only = true; + edev->nvmem_config.owner = THIS_MODULE; + edev->nvmem_config.compat = true; + edev->nvmem_config.base_dev = &spi->dev; + + edev->nvmem = nvmem_register(&edev->nvmem_config); + if (IS_ERR(edev->nvmem)) { + err = PTR_ERR(edev->nvmem); goto fail; + } dev_info(&spi->dev, "%d-bit eeprom %s\n", (pd->flags & EE_ADDR8) ? 8 : 16, @@ -359,10 +563,11 @@ static int eeprom_93xx46_remove(struct spi_device *spi) { struct eeprom_93xx46_dev *edev = spi_get_drvdata(spi); + nvmem_unregister(edev->nvmem); + if (!(edev->pdata->flags & EE_READONLY)) device_remove_file(&spi->dev, &dev_attr_erase); - sysfs_remove_bin_file(&spi->dev.kobj, &edev->bin); kfree(edev); return 0; } @@ -370,6 +575,7 @@ static int eeprom_93xx46_remove(struct spi_device *spi) static struct spi_driver eeprom_93xx46_driver = { .driver = { .name = "93xx46", + .of_match_table = of_match_ptr(eeprom_93xx46_of_table), }, .probe = eeprom_93xx46_probe, .remove = eeprom_93xx46_remove, |