aboutsummaryrefslogtreecommitdiff
path: root/drivers/w1-eeprom/ds2502.c
blob: a67f5edd0febb010c4315a55eab9eb28fe5831fd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
// 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 <dm/device_compat.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_plat(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,
};

u8 family_supported[] = {
	W1_FAMILY_DS2502,
};

U_BOOT_W1_DEVICE(ds2502, family_supported);