w1-eeprom: Add support for Maxim DS2502 add only memory

Signed-off-by: Martin Fuzzey <martin.fuzzey@flowbird.group>
diff --git a/drivers/w1-eeprom/Kconfig b/drivers/w1-eeprom/Kconfig
index 4b7f3c4..34aca4b 100644
--- a/drivers/w1-eeprom/Kconfig
+++ b/drivers/w1-eeprom/Kconfig
@@ -18,6 +18,19 @@
 	help
 	  Maxim DS24 EEPROMs 1-Wire EEPROM support
 
+config W1_EEPROM_DS2502
+	bool "Enable Maxim DS2502 Add-Only Memory support"
+	depends on W1
+	help
+	  Maxim DS2502 1-Wire add-only memory support.
+	  This device has 128 bytes of data memory, organized as 4 pages of
+	  32 bytes and 8 out of band status bytes that may be used to redirect
+	  pages, allowing data to be modified up to 4 times (by external
+	  programming).
+
+	  The device may be seen as a 32 byte memory, using the page redirection
+	  or as a 128 byte memory, ignoring the page redirection.
+
 config W1_EEPROM_SANDBOX
 	bool "Enable sandbox onewire EEPROM driver"
 	depends on W1
diff --git a/drivers/w1-eeprom/Makefile b/drivers/w1-eeprom/Makefile
index 03cc4c8..83f4008 100644
--- a/drivers/w1-eeprom/Makefile
+++ b/drivers/w1-eeprom/Makefile
@@ -1,5 +1,6 @@
 obj-$(CONFIG_W1_EEPROM) += w1-eeprom-uclass.o
 
 obj-$(CONFIG_W1_EEPROM_DS24XXX) += ds24xxx.o
+obj-$(CONFIG_W1_EEPROM_DS2502) += ds2502.o
 
 obj-$(CONFIG_W1_EEPROM_SANDBOX) += eep_sandbox.o
diff --git a/drivers/w1-eeprom/ds2502.c b/drivers/w1-eeprom/ds2502.c
new file mode 100644
index 0000000..76ca460
--- /dev/null
+++ b/drivers/w1-eeprom/ds2502.c
@@ -0,0 +1,244 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for DS-2502 One wire "Add only Memory".
+ *
+ * The chip has 4 pages of 32 bytes.
+ * In addition it has 8 out of band status bytes that are used, by software,
+ * as page redirection bytes by an algorithm described in the data sheet.
+ * This is useful since data cannot be erased once written but it can be
+ * "patched" up to four times by switching pages.
+ *
+ * So, when a read request is entirely in the first page automatically
+ * apply the page redirection bytes (which allows the device to be seen as
+ * a 32 byte PROM, writable 4 times).
+ *
+ * If the read request is outside of or larger than the first page then read
+ * the raw data (which allows the device to be seen as a 128 byte PROM,
+ * writable once).
+ *
+ * Copyright (c) 2018 Flowbird
+ * Martin Fuzzey <martin.fuzzey@flowbird.group>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <linux/err.h>
+#include <w1-eeprom.h>
+#include <w1.h>
+
+#define DS2502_PAGE_SIZE	32
+#define DS2502_PAGE_COUNT	4
+#define DS2502_STATUS_SIZE	8
+
+#define DS2502_CMD_READ_STATUS	0xAA
+#define DS2502_CMD_READ_GEN_CRC	0xC3
+
+/* u-boot crc8() is CCITT CRC8, we need x^8 + x^5 + x^4 + 1 LSB first */
+static unsigned int ds2502_crc8(const u8 *buf, int len)
+{
+	static const u8 poly = 0x8C;  /* (1 + x^4 + x^5) + x^8 */
+	u8 crc = 0;
+	int i;
+
+	for (i = 0; i < len; i++) {
+		u8 data = buf[i];
+		int j;
+
+		for (j = 0; j < 8; j++) {
+			u8 mix = (crc ^ data) & 1;
+
+			crc >>= 1;
+			if (mix)
+				crc ^= poly;
+			data >>= 1;
+		}
+	}
+	return crc;
+}
+
+static int ds2502_read(struct udevice *dev, u8 cmd,
+		       int bytes_in_page, int pos,
+		       u8 *buf, int bytes_for_user)
+{
+	int retry;
+	int ret = 0;
+
+	for (retry = 0; retry < 3; retry++) {
+		u8 pagebuf[DS2502_PAGE_SIZE + 1]; /* 1 byte for CRC8 */
+		u8 crc;
+		int i;
+
+		ret = w1_reset_select(dev);
+		if (ret)
+			return ret;
+
+		/* send read to end of page and generate CRC command */
+		pagebuf[0] = cmd;
+		pagebuf[1] = pos & 0xff;
+		pagebuf[2] = pos >> 8;
+		crc = ds2502_crc8(pagebuf, 3);
+		for (i = 0; i < 3; i++)
+			w1_write_byte(dev, pagebuf[i]);
+
+		/* Check command CRC */
+		ret = w1_read_byte(dev);
+		if (ret < 0) {
+			dev_dbg(dev, "Error %d reading command CRC\n", ret);
+			continue;
+		}
+
+		if (ret != crc) {
+			dev_dbg(dev,
+				"bad CRC8 for cmd %02x got=%02X exp=%02X\n",
+				cmd, ret, crc);
+			ret = -EIO;
+			continue;
+		}
+
+		/* read data and check CRC */
+		ret = w1_read_buf(dev, pagebuf, bytes_in_page + 1);
+		if (ret < 0) {
+			dev_dbg(dev, "Error %d reading data\n", ret);
+			continue;
+		}
+
+		crc = ds2502_crc8(pagebuf, bytes_in_page);
+		if (crc == pagebuf[bytes_in_page]) {
+			memcpy(buf, pagebuf, bytes_for_user);
+			ret = 0;
+			break;
+		}
+		dev_dbg(dev, "Bad CRC8 got=%02X exp=%02X pos=%04X\n",
+			pagebuf[bytes_in_page], crc, pos);
+		ret = -EIO;
+	}
+
+	return ret;
+}
+
+static inline int ds2502_read_status_bytes(struct udevice *dev, u8 *buf)
+{
+	return ds2502_read(dev, DS2502_CMD_READ_STATUS,
+				DS2502_STATUS_SIZE, 0,
+				buf, DS2502_STATUS_SIZE);
+}
+
+/*
+ * Status bytes (from index 1) contain 1's complement page indirection
+ * So for N writes:
+ * N=1: ff ff ff ff ff ff ff 00
+ * N=2: ff fe ff ff ff ff ff 00
+ * N=3: ff fe fd ff ff ff ff 00
+ * N=4: ff fe fd fc ff ff ff 00
+ */
+static int ds2502_indirect_page(struct udevice *dev, u8 *status, int page)
+{
+	int page_seen = 0;
+
+	do {
+		u8 sb = status[page + 1];
+
+		if (sb == 0xff)
+			break;
+
+		page = ~sb & 0xff;
+
+		if (page >= DS2502_PAGE_COUNT) {
+			dev_err(dev,
+				"Illegal page redirection status byte %02x\n",
+				sb);
+			return -EINVAL;
+		}
+
+		if (page_seen & (1 << page)) {
+			dev_err(dev, "Infinite loop in page redirection\n");
+			return -EINVAL;
+		}
+
+		page_seen |= (1 << page);
+	} while (1);
+
+	return page;
+}
+
+static int ds2502_read_buf(struct udevice *dev, unsigned int offset,
+			   u8 *buf, unsigned int count)
+{
+	unsigned int min_page = offset / DS2502_PAGE_SIZE;
+	unsigned int max_page = (offset + count - 1) / DS2502_PAGE_SIZE;
+	int xfered = 0;
+	u8 status_bytes[DS2502_STATUS_SIZE];
+	int i;
+	int ret;
+
+	if (min_page >= DS2502_PAGE_COUNT || max_page >= DS2502_PAGE_COUNT)
+		return -EINVAL;
+
+	if (min_page == 0 && max_page == 0) {
+		ret = ds2502_read_status_bytes(dev, status_bytes);
+		if (ret)
+			return ret;
+	} else {
+		/* Dummy one to one page redirection */
+		memset(status_bytes, 0xff, sizeof(status_bytes));
+	}
+
+	for (i = min_page; i <= max_page; i++) {
+		int page;
+		int pos;
+		int bytes_in_page;
+		int bytes_for_user;
+
+		page = ds2502_indirect_page(dev, status_bytes, i);
+		if (page < 0)
+			return page;
+		dev_dbg(dev, "page logical %d => physical %d\n", i, page);
+
+		pos = page * DS2502_PAGE_SIZE;
+		if (i == min_page)
+			pos += offset % DS2502_PAGE_SIZE;
+
+		bytes_in_page = DS2502_PAGE_SIZE - (pos % DS2502_PAGE_SIZE);
+
+		if (i == max_page)
+			bytes_for_user = count - xfered;
+		else
+			bytes_for_user = bytes_in_page;
+
+		ret = ds2502_read(dev, DS2502_CMD_READ_GEN_CRC,
+				  bytes_in_page, pos,
+				  &buf[xfered], bytes_for_user);
+		if (ret < 0)
+			return ret;
+
+		xfered += bytes_for_user;
+	}
+
+	return 0;
+}
+
+static int ds2502_probe(struct udevice *dev)
+{
+	struct w1_device *w1;
+
+	w1 = dev_get_parent_platdata(dev);
+	w1->id = 0;
+	return 0;
+}
+
+static const struct w1_eeprom_ops ds2502_ops = {
+	.read_buf	= ds2502_read_buf,
+};
+
+static const struct udevice_id ds2502_id[] = {
+	{ .compatible = "maxim,ds2502", .data = W1_FAMILY_DS2502 },
+	{ },
+};
+
+U_BOOT_DRIVER(ds2502) = {
+	.name		= "ds2502",
+	.id		= UCLASS_W1_EEPROM,
+	.of_match	= ds2502_id,
+	.ops		= &ds2502_ops,
+	.probe		= ds2502_probe,
+};