w1: add DS2501, DS2502, DS2505 EPROM device driver
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>
This commit is contained in:
parent
74ded38a8e
commit
25ec8710d9
@ -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
|
||||
|
@ -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
|
||||
|
290
drivers/w1/slaves/w1_ds250x.c
Normal file
290
drivers/w1/slaves/w1_ds250x.c
Normal file
@ -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));
|
Loading…
Reference in New Issue
Block a user