18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * w1_ds250x.c - w1 family 09/0b/89/91 (DS250x) driver
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci#include <linux/kernel.h>
78c2ecf20Sopenharmony_ci#include <linux/module.h>
88c2ecf20Sopenharmony_ci#include <linux/moduleparam.h>
98c2ecf20Sopenharmony_ci#include <linux/device.h>
108c2ecf20Sopenharmony_ci#include <linux/types.h>
118c2ecf20Sopenharmony_ci#include <linux/delay.h>
128c2ecf20Sopenharmony_ci#include <linux/slab.h>
138c2ecf20Sopenharmony_ci#include <linux/crc16.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include <linux/w1.h>
168c2ecf20Sopenharmony_ci#include <linux/nvmem-provider.h>
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci#define W1_DS2501_UNW_FAMILY    0x91
198c2ecf20Sopenharmony_ci#define W1_DS2501_SIZE          64
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#define W1_DS2502_FAMILY        0x09
228c2ecf20Sopenharmony_ci#define W1_DS2502_UNW_FAMILY    0x89
238c2ecf20Sopenharmony_ci#define W1_DS2502_SIZE          128
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci#define W1_DS2505_FAMILY	0x0b
268c2ecf20Sopenharmony_ci#define W1_DS2505_SIZE		2048
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci#define W1_PAGE_SIZE		32
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci#define W1_EXT_READ_MEMORY	0xA5
318c2ecf20Sopenharmony_ci#define W1_READ_DATA_CRC        0xC3
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#define OFF2PG(off)	((off) / W1_PAGE_SIZE)
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci#define CRC16_INIT		0
368c2ecf20Sopenharmony_ci#define CRC16_VALID		0xb001
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_cistruct w1_eprom_data {
398c2ecf20Sopenharmony_ci	size_t size;
408c2ecf20Sopenharmony_ci	int (*read)(struct w1_slave *sl, int pageno);
418c2ecf20Sopenharmony_ci	u8 eprom[W1_DS2505_SIZE];
428c2ecf20Sopenharmony_ci	DECLARE_BITMAP(page_present, W1_DS2505_SIZE / W1_PAGE_SIZE);
438c2ecf20Sopenharmony_ci	char nvmem_name[64];
448c2ecf20Sopenharmony_ci};
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_cistatic int w1_ds2502_read_page(struct w1_slave *sl, int pageno)
478c2ecf20Sopenharmony_ci{
488c2ecf20Sopenharmony_ci	struct w1_eprom_data *data = sl->family_data;
498c2ecf20Sopenharmony_ci	int pgoff = pageno * W1_PAGE_SIZE;
508c2ecf20Sopenharmony_ci	int ret = -EIO;
518c2ecf20Sopenharmony_ci	u8 buf[3];
528c2ecf20Sopenharmony_ci	u8 crc8;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	if (test_bit(pageno, data->page_present))
558c2ecf20Sopenharmony_ci		return 0; /* page already present */
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	mutex_lock(&sl->master->bus_mutex);
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci	if (w1_reset_select_slave(sl))
608c2ecf20Sopenharmony_ci		goto err;
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	buf[0] = W1_READ_DATA_CRC;
638c2ecf20Sopenharmony_ci	buf[1] = pgoff & 0xff;
648c2ecf20Sopenharmony_ci	buf[2] = pgoff >> 8;
658c2ecf20Sopenharmony_ci	w1_write_block(sl->master, buf, 3);
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	crc8 = w1_read_8(sl->master);
688c2ecf20Sopenharmony_ci	if (w1_calc_crc8(buf, 3) != crc8)
698c2ecf20Sopenharmony_ci		goto err;
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	w1_read_block(sl->master, &data->eprom[pgoff], W1_PAGE_SIZE);
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	crc8 = w1_read_8(sl->master);
748c2ecf20Sopenharmony_ci	if (w1_calc_crc8(&data->eprom[pgoff], W1_PAGE_SIZE) != crc8)
758c2ecf20Sopenharmony_ci		goto err;
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	set_bit(pageno, data->page_present); /* mark page present */
788c2ecf20Sopenharmony_ci	ret = 0;
798c2ecf20Sopenharmony_cierr:
808c2ecf20Sopenharmony_ci	mutex_unlock(&sl->master->bus_mutex);
818c2ecf20Sopenharmony_ci	return ret;
828c2ecf20Sopenharmony_ci}
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_cistatic int w1_ds2505_read_page(struct w1_slave *sl, int pageno)
858c2ecf20Sopenharmony_ci{
868c2ecf20Sopenharmony_ci	struct w1_eprom_data *data = sl->family_data;
878c2ecf20Sopenharmony_ci	int redir_retries = 16;
888c2ecf20Sopenharmony_ci	int pgoff, epoff;
898c2ecf20Sopenharmony_ci	int ret = -EIO;
908c2ecf20Sopenharmony_ci	u8 buf[6];
918c2ecf20Sopenharmony_ci	u8 redir;
928c2ecf20Sopenharmony_ci	u16 crc;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	if (test_bit(pageno, data->page_present))
958c2ecf20Sopenharmony_ci		return 0; /* page already present */
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	epoff = pgoff = pageno * W1_PAGE_SIZE;
988c2ecf20Sopenharmony_ci	mutex_lock(&sl->master->bus_mutex);
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ciretry:
1018c2ecf20Sopenharmony_ci	if (w1_reset_select_slave(sl))
1028c2ecf20Sopenharmony_ci		goto err;
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	buf[0] = W1_EXT_READ_MEMORY;
1058c2ecf20Sopenharmony_ci	buf[1] = pgoff & 0xff;
1068c2ecf20Sopenharmony_ci	buf[2] = pgoff >> 8;
1078c2ecf20Sopenharmony_ci	w1_write_block(sl->master, buf, 3);
1088c2ecf20Sopenharmony_ci	w1_read_block(sl->master, buf + 3, 3); /* redir, crc16 */
1098c2ecf20Sopenharmony_ci	redir = buf[3];
1108c2ecf20Sopenharmony_ci	crc = crc16(CRC16_INIT, buf, 6);
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	if (crc != CRC16_VALID)
1138c2ecf20Sopenharmony_ci		goto err;
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	if (redir != 0xff) {
1178c2ecf20Sopenharmony_ci		redir_retries--;
1188c2ecf20Sopenharmony_ci		if (redir_retries < 0)
1198c2ecf20Sopenharmony_ci			goto err;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci		pgoff = (redir ^ 0xff) * W1_PAGE_SIZE;
1228c2ecf20Sopenharmony_ci		goto retry;
1238c2ecf20Sopenharmony_ci	}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	w1_read_block(sl->master, &data->eprom[epoff], W1_PAGE_SIZE);
1268c2ecf20Sopenharmony_ci	w1_read_block(sl->master, buf, 2); /* crc16 */
1278c2ecf20Sopenharmony_ci	crc = crc16(CRC16_INIT, &data->eprom[epoff], W1_PAGE_SIZE);
1288c2ecf20Sopenharmony_ci	crc = crc16(crc, buf, 2);
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	if (crc != CRC16_VALID)
1318c2ecf20Sopenharmony_ci		goto err;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	set_bit(pageno, data->page_present);
1348c2ecf20Sopenharmony_ci	ret = 0;
1358c2ecf20Sopenharmony_cierr:
1368c2ecf20Sopenharmony_ci	mutex_unlock(&sl->master->bus_mutex);
1378c2ecf20Sopenharmony_ci	return ret;
1388c2ecf20Sopenharmony_ci}
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_cistatic int w1_nvmem_read(void *priv, unsigned int off, void *buf, size_t count)
1418c2ecf20Sopenharmony_ci{
1428c2ecf20Sopenharmony_ci	struct w1_slave *sl = priv;
1438c2ecf20Sopenharmony_ci	struct w1_eprom_data *data = sl->family_data;
1448c2ecf20Sopenharmony_ci	size_t eprom_size = data->size;
1458c2ecf20Sopenharmony_ci	int ret;
1468c2ecf20Sopenharmony_ci	int i;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	if (off > eprom_size)
1498c2ecf20Sopenharmony_ci		return -EINVAL;
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	if ((off + count) > eprom_size)
1528c2ecf20Sopenharmony_ci		count = eprom_size - off;
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	i = OFF2PG(off);
1558c2ecf20Sopenharmony_ci	do {
1568c2ecf20Sopenharmony_ci		ret = data->read(sl, i++);
1578c2ecf20Sopenharmony_ci		if (ret < 0)
1588c2ecf20Sopenharmony_ci			return ret;
1598c2ecf20Sopenharmony_ci	} while (i < OFF2PG(off + count));
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	memcpy(buf, &data->eprom[off], count);
1628c2ecf20Sopenharmony_ci	return 0;
1638c2ecf20Sopenharmony_ci}
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_cistatic int w1_eprom_add_slave(struct w1_slave *sl)
1668c2ecf20Sopenharmony_ci{
1678c2ecf20Sopenharmony_ci	struct w1_eprom_data *data;
1688c2ecf20Sopenharmony_ci	struct nvmem_device *nvmem;
1698c2ecf20Sopenharmony_ci	struct nvmem_config nvmem_cfg = {
1708c2ecf20Sopenharmony_ci		.dev = &sl->dev,
1718c2ecf20Sopenharmony_ci		.reg_read = w1_nvmem_read,
1728c2ecf20Sopenharmony_ci		.type = NVMEM_TYPE_OTP,
1738c2ecf20Sopenharmony_ci		.read_only = true,
1748c2ecf20Sopenharmony_ci		.word_size = 1,
1758c2ecf20Sopenharmony_ci		.priv = sl,
1768c2ecf20Sopenharmony_ci		.id = -1
1778c2ecf20Sopenharmony_ci	};
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	data = devm_kzalloc(&sl->dev, sizeof(struct w1_eprom_data), GFP_KERNEL);
1808c2ecf20Sopenharmony_ci	if (!data)
1818c2ecf20Sopenharmony_ci		return -ENOMEM;
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	sl->family_data = data;
1848c2ecf20Sopenharmony_ci	switch (sl->family->fid) {
1858c2ecf20Sopenharmony_ci	case W1_DS2501_UNW_FAMILY:
1868c2ecf20Sopenharmony_ci		data->size = W1_DS2501_SIZE;
1878c2ecf20Sopenharmony_ci		data->read = w1_ds2502_read_page;
1888c2ecf20Sopenharmony_ci		break;
1898c2ecf20Sopenharmony_ci	case W1_DS2502_FAMILY:
1908c2ecf20Sopenharmony_ci	case W1_DS2502_UNW_FAMILY:
1918c2ecf20Sopenharmony_ci		data->size = W1_DS2502_SIZE;
1928c2ecf20Sopenharmony_ci		data->read = w1_ds2502_read_page;
1938c2ecf20Sopenharmony_ci		break;
1948c2ecf20Sopenharmony_ci	case W1_DS2505_FAMILY:
1958c2ecf20Sopenharmony_ci		data->size = W1_DS2505_SIZE;
1968c2ecf20Sopenharmony_ci		data->read = w1_ds2505_read_page;
1978c2ecf20Sopenharmony_ci		break;
1988c2ecf20Sopenharmony_ci	}
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	if (sl->master->bus_master->dev_id)
2018c2ecf20Sopenharmony_ci		snprintf(data->nvmem_name, sizeof(data->nvmem_name),
2028c2ecf20Sopenharmony_ci			 "%s-%02x-%012llx",
2038c2ecf20Sopenharmony_ci			 sl->master->bus_master->dev_id, sl->reg_num.family,
2048c2ecf20Sopenharmony_ci			 (unsigned long long)sl->reg_num.id);
2058c2ecf20Sopenharmony_ci	else
2068c2ecf20Sopenharmony_ci		snprintf(data->nvmem_name, sizeof(data->nvmem_name),
2078c2ecf20Sopenharmony_ci			 "%02x-%012llx",
2088c2ecf20Sopenharmony_ci			 sl->reg_num.family,
2098c2ecf20Sopenharmony_ci			 (unsigned long long)sl->reg_num.id);
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	nvmem_cfg.name = data->nvmem_name;
2128c2ecf20Sopenharmony_ci	nvmem_cfg.size = data->size;
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	nvmem = devm_nvmem_register(&sl->dev, &nvmem_cfg);
2158c2ecf20Sopenharmony_ci	return PTR_ERR_OR_ZERO(nvmem);
2168c2ecf20Sopenharmony_ci}
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_cistatic const struct w1_family_ops w1_eprom_fops = {
2198c2ecf20Sopenharmony_ci	.add_slave	= w1_eprom_add_slave,
2208c2ecf20Sopenharmony_ci};
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_cistatic struct w1_family w1_family_09 = {
2238c2ecf20Sopenharmony_ci	.fid = W1_DS2502_FAMILY,
2248c2ecf20Sopenharmony_ci	.fops = &w1_eprom_fops,
2258c2ecf20Sopenharmony_ci};
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_cistatic struct w1_family w1_family_0b = {
2288c2ecf20Sopenharmony_ci	.fid = W1_DS2505_FAMILY,
2298c2ecf20Sopenharmony_ci	.fops = &w1_eprom_fops,
2308c2ecf20Sopenharmony_ci};
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_cistatic struct w1_family w1_family_89 = {
2338c2ecf20Sopenharmony_ci	.fid = W1_DS2502_UNW_FAMILY,
2348c2ecf20Sopenharmony_ci	.fops = &w1_eprom_fops,
2358c2ecf20Sopenharmony_ci};
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_cistatic struct w1_family w1_family_91 = {
2388c2ecf20Sopenharmony_ci	.fid = W1_DS2501_UNW_FAMILY,
2398c2ecf20Sopenharmony_ci	.fops = &w1_eprom_fops,
2408c2ecf20Sopenharmony_ci};
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_cistatic int __init w1_ds250x_init(void)
2438c2ecf20Sopenharmony_ci{
2448c2ecf20Sopenharmony_ci	int err;
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	err = w1_register_family(&w1_family_09);
2478c2ecf20Sopenharmony_ci	if (err)
2488c2ecf20Sopenharmony_ci		return err;
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_ci	err = w1_register_family(&w1_family_0b);
2518c2ecf20Sopenharmony_ci	if (err)
2528c2ecf20Sopenharmony_ci		goto err_0b;
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_ci	err = w1_register_family(&w1_family_89);
2558c2ecf20Sopenharmony_ci	if (err)
2568c2ecf20Sopenharmony_ci		goto err_89;
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	err = w1_register_family(&w1_family_91);
2598c2ecf20Sopenharmony_ci	if (err)
2608c2ecf20Sopenharmony_ci		goto err_91;
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	return 0;
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_cierr_91:
2658c2ecf20Sopenharmony_ci	w1_unregister_family(&w1_family_89);
2668c2ecf20Sopenharmony_cierr_89:
2678c2ecf20Sopenharmony_ci	w1_unregister_family(&w1_family_0b);
2688c2ecf20Sopenharmony_cierr_0b:
2698c2ecf20Sopenharmony_ci	w1_unregister_family(&w1_family_09);
2708c2ecf20Sopenharmony_ci	return err;
2718c2ecf20Sopenharmony_ci}
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_cistatic void __exit w1_ds250x_exit(void)
2748c2ecf20Sopenharmony_ci{
2758c2ecf20Sopenharmony_ci	w1_unregister_family(&w1_family_09);
2768c2ecf20Sopenharmony_ci	w1_unregister_family(&w1_family_0b);
2778c2ecf20Sopenharmony_ci	w1_unregister_family(&w1_family_89);
2788c2ecf20Sopenharmony_ci	w1_unregister_family(&w1_family_91);
2798c2ecf20Sopenharmony_ci}
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_cimodule_init(w1_ds250x_init);
2828c2ecf20Sopenharmony_cimodule_exit(w1_ds250x_exit);
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ciMODULE_AUTHOR("Thomas Bogendoerfer <tbogendoerfe@suse.de>");
2858c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("w1 family driver for DS250x Add Only Memory");
2868c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
2878c2ecf20Sopenharmony_ciMODULE_ALIAS("w1-family-" __stringify(W1_DS2502_FAMILY));
2888c2ecf20Sopenharmony_ciMODULE_ALIAS("w1-family-" __stringify(W1_DS2505_FAMILY));
2898c2ecf20Sopenharmony_ciMODULE_ALIAS("w1-family-" __stringify(W1_DS2501_UNW_FAMILY));
2908c2ecf20Sopenharmony_ciMODULE_ALIAS("w1-family-" __stringify(W1_DS2502_UNW_FAMILY));
291