]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/commitdiff
w1: add DS2501, DS2502, DS2505 EPROM device driver
authorThomas Bogendoerfer <tbogendoerfer@suse.de>
Sat, 31 Aug 2019 08:26:22 +0000 (10:26 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 4 Sep 2019 12:34:31 +0000 (14:34 +0200)
Add a 1-Wire slave driver to support DS250x EPROM deivces. This
slave driver attaches the devices to the NVMEM subsystem for
an easy in-kernel usage.

Signed-off-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
Link: https://lore.kernel.org/r/20190831082623.15627-3-tbogendoerfer@suse.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/w1/slaves/Kconfig
drivers/w1/slaves/Makefile
drivers/w1/slaves/w1_ds250x.c [new file with mode: 0644]

index 37aaad26b37336d136418d410e8b31ac4fa26195..ebed495b9e692cfbce260201397a8546c3d0f553 100644 (file)
@@ -101,6 +101,12 @@ config W1_SLAVE_DS2438
          Say Y here if you want to use a 1-wire
          DS2438 Smart Battery Monitor device support
 
+config W1_SLAVE_DS250X
+       tristate "512b/1kb/16kb EPROM family support"
+       help
+         Say Y here if you want to use a 1-wire
+         512b/1kb/16kb EPROM family device (DS250x).
+
 config W1_SLAVE_DS2780
        tristate "Dallas 2780 battery monitor chip"
        help
index eab29f151413743e21bd23ac23d9348b107df6a0..8e9655eaa4789adf87a97ef4c1f02abe1f5d6762 100644 (file)
@@ -14,6 +14,7 @@ obj-$(CONFIG_W1_SLAVE_DS2431) += w1_ds2431.o
 obj-$(CONFIG_W1_SLAVE_DS2805)  += w1_ds2805.o
 obj-$(CONFIG_W1_SLAVE_DS2433)  += w1_ds2433.o
 obj-$(CONFIG_W1_SLAVE_DS2438)  += w1_ds2438.o
+obj-$(CONFIG_W1_SLAVE_DS250X)  += w1_ds250x.o
 obj-$(CONFIG_W1_SLAVE_DS2780)  += w1_ds2780.o
 obj-$(CONFIG_W1_SLAVE_DS2781)  += w1_ds2781.o
 obj-$(CONFIG_W1_SLAVE_DS28E04) += w1_ds28e04.o
diff --git a/drivers/w1/slaves/w1_ds250x.c b/drivers/w1/slaves/w1_ds250x.c
new file mode 100644 (file)
index 0000000..e507117
--- /dev/null
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * w1_ds250x.c - w1 family 09/0b/89/91 (DS250x) driver
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/crc16.h>
+
+#include <linux/w1.h>
+#include <linux/nvmem-provider.h>
+
+#define W1_DS2501_UNW_FAMILY    0x91
+#define W1_DS2501_SIZE          64
+
+#define W1_DS2502_FAMILY        0x09
+#define W1_DS2502_UNW_FAMILY    0x89
+#define W1_DS2502_SIZE          128
+
+#define W1_DS2505_FAMILY       0x0b
+#define W1_DS2505_SIZE         2048
+
+#define W1_PAGE_SIZE           32
+
+#define W1_EXT_READ_MEMORY     0xA5
+#define W1_READ_DATA_CRC        0xC3
+
+#define OFF2PG(off)    ((off) / W1_PAGE_SIZE)
+
+#define CRC16_INIT             0
+#define CRC16_VALID            0xb001
+
+struct w1_eprom_data {
+       size_t size;
+       int (*read)(struct w1_slave *sl, int pageno);
+       u8 eprom[W1_DS2505_SIZE];
+       DECLARE_BITMAP(page_present, W1_DS2505_SIZE / W1_PAGE_SIZE);
+       char nvmem_name[64];
+};
+
+static int w1_ds2502_read_page(struct w1_slave *sl, int pageno)
+{
+       struct w1_eprom_data *data = sl->family_data;
+       int pgoff = pageno * W1_PAGE_SIZE;
+       int ret = -EIO;
+       u8 buf[3];
+       u8 crc8;
+
+       if (test_bit(pageno, data->page_present))
+               return 0; /* page already present */
+
+       mutex_lock(&sl->master->bus_mutex);
+
+       if (w1_reset_select_slave(sl))
+               goto err;
+
+       buf[0] = W1_READ_DATA_CRC;
+       buf[1] = pgoff & 0xff;
+       buf[2] = pgoff >> 8;
+       w1_write_block(sl->master, buf, 3);
+
+       crc8 = w1_read_8(sl->master);
+       if (w1_calc_crc8(buf, 3) != crc8)
+               goto err;
+
+       w1_read_block(sl->master, &data->eprom[pgoff], W1_PAGE_SIZE);
+
+       crc8 = w1_read_8(sl->master);
+       if (w1_calc_crc8(&data->eprom[pgoff], W1_PAGE_SIZE) != crc8)
+               goto err;
+
+       set_bit(pageno, data->page_present); /* mark page present */
+       ret = 0;
+err:
+       mutex_unlock(&sl->master->bus_mutex);
+       return ret;
+}
+
+static int w1_ds2505_read_page(struct w1_slave *sl, int pageno)
+{
+       struct w1_eprom_data *data = sl->family_data;
+       int redir_retries = 16;
+       int pgoff, epoff;
+       int ret = -EIO;
+       u8 buf[6];
+       u8 redir;
+       u16 crc;
+
+       if (test_bit(pageno, data->page_present))
+               return 0; /* page already present */
+
+       epoff = pgoff = pageno * W1_PAGE_SIZE;
+       mutex_lock(&sl->master->bus_mutex);
+
+retry:
+       if (w1_reset_select_slave(sl))
+               goto err;
+
+       buf[0] = W1_EXT_READ_MEMORY;
+       buf[1] = pgoff & 0xff;
+       buf[2] = pgoff >> 8;
+       w1_write_block(sl->master, buf, 3);
+       w1_read_block(sl->master, buf + 3, 3); /* redir, crc16 */
+       redir = buf[3];
+       crc = crc16(CRC16_INIT, buf, 6);
+
+       if (crc != CRC16_VALID)
+               goto err;
+
+
+       if (redir != 0xff) {
+               redir_retries--;
+               if (redir_retries < 0)
+                       goto err;
+
+               pgoff = (redir ^ 0xff) * W1_PAGE_SIZE;
+               goto retry;
+       }
+
+       w1_read_block(sl->master, &data->eprom[epoff], W1_PAGE_SIZE);
+       w1_read_block(sl->master, buf, 2); /* crc16 */
+       crc = crc16(CRC16_INIT, &data->eprom[epoff], W1_PAGE_SIZE);
+       crc = crc16(crc, buf, 2);
+
+       if (crc != CRC16_VALID)
+               goto err;
+
+       set_bit(pageno, data->page_present);
+       ret = 0;
+err:
+       mutex_unlock(&sl->master->bus_mutex);
+       return ret;
+}
+
+static int w1_nvmem_read(void *priv, unsigned int off, void *buf, size_t count)
+{
+       struct w1_slave *sl = priv;
+       struct w1_eprom_data *data = sl->family_data;
+       size_t eprom_size = data->size;
+       int ret;
+       int i;
+
+       if (off > eprom_size)
+               return -EINVAL;
+
+       if ((off + count) > eprom_size)
+               count = eprom_size - off;
+
+       i = OFF2PG(off);
+       do {
+               ret = data->read(sl, i++);
+               if (ret < 0)
+                       return ret;
+       } while (i < OFF2PG(off + count));
+
+       memcpy(buf, &data->eprom[off], count);
+       return 0;
+}
+
+static int w1_eprom_add_slave(struct w1_slave *sl)
+{
+       struct w1_eprom_data *data;
+       struct nvmem_device *nvmem;
+       struct nvmem_config nvmem_cfg = {
+               .dev = &sl->dev,
+               .reg_read = w1_nvmem_read,
+               .type = NVMEM_TYPE_OTP,
+               .read_only = true,
+               .word_size = 1,
+               .priv = sl,
+               .id = -1
+       };
+
+       data = devm_kzalloc(&sl->dev, sizeof(struct w1_eprom_data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       sl->family_data = data;
+       switch (sl->family->fid) {
+       case W1_DS2501_UNW_FAMILY:
+               data->size = W1_DS2501_SIZE;
+               data->read = w1_ds2502_read_page;
+               break;
+       case W1_DS2502_FAMILY:
+       case W1_DS2502_UNW_FAMILY:
+               data->size = W1_DS2502_SIZE;
+               data->read = w1_ds2502_read_page;
+               break;
+       case W1_DS2505_FAMILY:
+               data->size = W1_DS2505_SIZE;
+               data->read = w1_ds2505_read_page;
+               break;
+       }
+
+       if (sl->master->bus_master->dev_id)
+               snprintf(data->nvmem_name, sizeof(data->nvmem_name),
+                        "%s-%02x-%012llx",
+                        sl->master->bus_master->dev_id, sl->reg_num.family,
+                        (unsigned long long)sl->reg_num.id);
+       else
+               snprintf(data->nvmem_name, sizeof(data->nvmem_name),
+                        "%02x-%012llx",
+                        sl->reg_num.family,
+                        (unsigned long long)sl->reg_num.id);
+
+       nvmem_cfg.name = data->nvmem_name;
+       nvmem_cfg.size = data->size;
+
+       nvmem = devm_nvmem_register(&sl->dev, &nvmem_cfg);
+       return PTR_ERR_OR_ZERO(nvmem);
+}
+
+static struct w1_family_ops w1_eprom_fops = {
+       .add_slave      = w1_eprom_add_slave,
+};
+
+static struct w1_family w1_family_09 = {
+       .fid = W1_DS2502_FAMILY,
+       .fops = &w1_eprom_fops,
+};
+
+static struct w1_family w1_family_0b = {
+       .fid = W1_DS2505_FAMILY,
+       .fops = &w1_eprom_fops,
+};
+
+static struct w1_family w1_family_89 = {
+       .fid = W1_DS2502_UNW_FAMILY,
+       .fops = &w1_eprom_fops,
+};
+
+static struct w1_family w1_family_91 = {
+       .fid = W1_DS2501_UNW_FAMILY,
+       .fops = &w1_eprom_fops,
+};
+
+static int __init w1_ds250x_init(void)
+{
+       int err;
+
+       err = w1_register_family(&w1_family_09);
+       if (err)
+               return err;
+
+       err = w1_register_family(&w1_family_0b);
+       if (err)
+               goto err_0b;
+
+       err = w1_register_family(&w1_family_89);
+       if (err)
+               goto err_89;
+
+       err = w1_register_family(&w1_family_91);
+       if (err)
+               goto err_91;
+
+       return 0;
+
+err_91:
+       w1_unregister_family(&w1_family_89);
+err_89:
+       w1_unregister_family(&w1_family_0b);
+err_0b:
+       w1_unregister_family(&w1_family_09);
+       return err;
+}
+
+static void __exit w1_ds250x_exit(void)
+{
+       w1_unregister_family(&w1_family_09);
+       w1_unregister_family(&w1_family_0b);
+       w1_unregister_family(&w1_family_89);
+       w1_unregister_family(&w1_family_91);
+}
+
+module_init(w1_ds250x_init);
+module_exit(w1_ds250x_exit);
+
+MODULE_AUTHOR("Thomas Bogendoerfer <tbogendoerfe@suse.de>");
+MODULE_DESCRIPTION("w1 family driver for DS250x Add Only Memory");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("w1-family-" __stringify(W1_DS2502_FAMILY));
+MODULE_ALIAS("w1-family-" __stringify(W1_DS2505_FAMILY));
+MODULE_ALIAS("w1-family-" __stringify(W1_DS2501_UNW_FAMILY));
+MODULE_ALIAS("w1-family-" __stringify(W1_DS2502_UNW_FAMILY));