Skip to content

Commit f0a9b0b

Browse files
committed
at24: SMBus fallback read/write for 16-bit addressed EEPROMs on PIIX4
AMD PIIX4 SMBus adapters (found on AMD SP5/EPYC platforms including Cisco 8000 series) support BYTE_DATA and WORD_DATA but lack I2C_FUNC_I2C and I2C_FUNC_SMBUS_I2C_BLOCK. For AT24_FLAG_ADDR16 EEPROMs this causes regmap_get_i2c_bus() to return -ENOTSUPP, making the EEPROM inaccessible. Add a smbus_addr16_fallback detection and two new helpers: - at24_smbus_read(): write_byte_data sets 16-bit pointer, repeated read_byte() fetches bytes (EEPROM auto-increments) - at24_smbus_write(): write_word_data encodes address+data in one WORD Force regmap_config.reg_bits=8 when fallback is active so regmap init succeeds; all actual I/O is handled by the SMBus helpers. Fixes EEPROM access on Cisco 8000-series with AMD SP5 on-die PIIX4 SMBus. Signed-off-by: Nishanth Sampath Kumar <nissampa@cisco.com>
1 parent c125a95 commit f0a9b0b

1 file changed

Lines changed: 230 additions & 0 deletions

File tree

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
From 07e943ccf6b98e941a39cd1f74db250e862151f0 Mon Sep 17 00:00:00 2001
2+
From: Nishanth Sampath Kumar <nissampa@cisco.com>
3+
Date: Fri, 27 Mar 2026 14:48:36 -0700
4+
Subject: [PATCH] at24: add SMBus fallback read/write for 16-bit addressed
5+
EEPROMs on PIIX4 adapters
6+
7+
AMD PIIX4 SMBus adapters expose I2C_FUNC_SMBUS_BYTE_DATA and
8+
I2C_FUNC_SMBUS_WORD_DATA but do NOT support I2C_FUNC_I2C or
9+
I2C_FUNC_SMBUS_I2C_BLOCK. The existing at24 driver relies on
10+
regmap_get_i2c_bus() which requires one of those two capabilities;
11+
for reg_bits=16 (AT24_FLAG_ADDR16 EEPROMs) it therefore returns
12+
-ENOTSUPP and the EEPROM is inaccessible on these adapters.
13+
14+
This patch detects the PIIX4 case (has BYTE_DATA + WORD_DATA but
15+
lacks I2C and BLOCK) and enables a lightweight SMBus-only fallback
16+
path:
17+
18+
READ: i2c_smbus_write_byte_data(addr_hi, addr_lo) sets the
19+
16-bit address pointer; repeated i2c_smbus_read_byte()
20+
calls fetch successive bytes (EEPROM auto-increments).
21+
22+
WRITE: i2c_smbus_write_word_data(addr_hi,
23+
cpu_to_le16((data<<8)|addr_lo)) encodes the 16-bit address
24+
and data byte in one SMBus WORD write.
25+
26+
When the fallback is active, regmap_config.reg_bits is forced to 8
27+
so that regmap_get_i2c_bus() can succeed (regmap_smbus_byte is
28+
selected); all actual I/O bypasses regmap and goes through the two
29+
new helpers at24_smbus_read() / at24_smbus_write().
30+
31+
This fixes EEPROM access for Cisco 8000-series platforms that use
32+
the AMD SP5 SoC on-die PIIX4 SMBus controller to reach platform
33+
EEPROMs with 16-bit (2-byte) addressing.
34+
35+
Signed-off-by: Nishanth Sampath Kumar <nissampa@cisco.com>
36+
---
37+
drivers/misc/eeprom/at24.c | 134 ++++++++++++++++++++++++++++++++++++++++++++-
38+
1 file changed, 133 insertions(+), 1 deletion(-)
39+
40+
--- a/drivers/misc/eeprom/at24.c 2026-03-19 15:09:21.518103630 -0700
41+
+++ b/drivers/misc/eeprom/at24.c 2026-03-19 15:10:18.279047438 -0700
42+
@@ -88,6 +88,10 @@
43+
struct regulator *vcc_reg;
44+
void (*read_post)(unsigned int off, char *buf, size_t count);
45+
46+
+ /* SMBus-only fallback for 16-bit addressed EEPROMs on PIIX4 adapters */
47+
+ bool smbus_addr16_fallback;
48+
+ struct i2c_client *smbus_client;
49+
+
50+
/*
51+
* Some chips tie up multiple I2C addresses; dummy devices reserve
52+
* them for us.
53+
@@ -343,6 +347,101 @@
54+
return count;
55+
}
56+
57+
+/* forward declaration needed by at24_smbus_write below */
58+
+static size_t at24_adjust_write_count(struct at24_data *at24,
59+
+ unsigned int offset, size_t count);
60+
+
61+
+/*
62+
+ * SMBus primitive fallback for 16-bit addressed EEPROMs.
63+
+ *
64+
+ * PIIX4 SMBus supports BYTE_DATA and WORD_DATA but NOT I2C_FUNC_I2C or
65+
+ * I2C_FUNC_SMBUS_I2C_BLOCK, so regmap_get_i2c_bus() returns -ENOTSUPP
66+
+ * for reg_bits=16. We bypass regmap entirely for the I/O path:
67+
+ *
68+
+ * READ : write_byte_data(addr_hi, addr_lo) sets the 16-bit EEPROM
69+
+ * address pointer, then read_byte() fetches bytes one-by-one
70+
+ * (EEPROM auto-increments the pointer after each read).
71+
+ *
72+
+ * WRITE: write_word_data(addr_hi, cpu_to_le16((data<<8)|addr_lo))
73+
+ * encodes the address and data byte in one SMBus word write.
74+
+ */
75+
+static ssize_t at24_smbus_read(struct at24_data *at24, char *buf,
76+
+ unsigned int offset, size_t count)
77+
+{
78+
+ struct i2c_client *client = at24->smbus_client;
79+
+ unsigned long timeout, read_time;
80+
+ u8 addr_hi = (offset >> 8) & 0xff;
81+
+ u8 addr_lo = offset & 0xff;
82+
+ ssize_t ret;
83+
+ size_t i;
84+
+
85+
+ count = at24_adjust_read_count(at24, offset, count);
86+
+
87+
+ timeout = jiffies + msecs_to_jiffies(at24_write_timeout);
88+
+ do {
89+
+ read_time = jiffies;
90+
+
91+
+ /* Set 16-bit address pointer: command=addr_hi, value=addr_lo */
92+
+ ret = i2c_smbus_write_byte_data(client, addr_hi, addr_lo);
93+
+ if (ret < 0)
94+
+ goto retry;
95+
+
96+
+ /* Read bytes sequentially -- EEPROM auto-increments pointer */
97+
+ for (i = 0; i < count; i++) {
98+
+ ret = i2c_smbus_read_byte(client);
99+
+ if (ret < 0)
100+
+ goto retry;
101+
+ buf[i] = (u8)ret;
102+
+ }
103+
+ dev_dbg(&client->dev, "smbus read %zu@%u --> ok\n", count, offset);
104+
+ return count;
105+
+retry:
106+
+ usleep_range(1000, 1500);
107+
+ } while (time_before(read_time, timeout));
108+
+
109+
+ return -ETIMEDOUT;
110+
+}
111+
+
112+
+static ssize_t at24_smbus_write(struct at24_data *at24, const char *buf,
113+
+ unsigned int offset, size_t count)
114+
+{
115+
+ struct i2c_client *client = at24->smbus_client;
116+
+ unsigned long timeout, write_time;
117+
+ size_t i;
118+
+ int ret;
119+
+
120+
+ /* Write one byte at a time (page writes need raw I2C transfers) */
121+
+ count = at24_adjust_write_count(at24, offset, count);
122+
+
123+
+ for (i = 0; i < count; i++, offset++) {
124+
+ timeout = jiffies + msecs_to_jiffies(at24_write_timeout);
125+
+ do {
126+
+ write_time = jiffies;
127+
+ /*
128+
+ * command = addr[15:8]
129+
+ * word LSB = addr[7:0]
130+
+ * word MSB = data byte
131+
+ * EEPROM: set 16-bit address then write one data byte.
132+
+ */
133+
+ ret = i2c_smbus_write_word_data(client,
134+
+ (offset >> 8) & 0xff,
135+
+ cpu_to_le16(((u16)(u8)buf[i] << 8) |
136+
+ (offset & 0xff)));
137+
+ if (!ret) {
138+
+ dev_dbg(&client->dev, "smbus write 1@%u --> ok\n",
139+
+ offset);
140+
+ break;
141+
+ }
142+
+ usleep_range(1000, 1500);
143+
+ } while (time_before(write_time, timeout));
144+
+
145+
+ if (ret)
146+
+ return (i > 0) ? (ssize_t)i : -ETIMEDOUT;
147+
+ }
148+
+
149+
+ return count;
150+
+}
151+
+
152+
static ssize_t at24_regmap_read(struct at24_data *at24, char *buf,
153+
unsigned int offset, size_t count)
154+
{
155+
@@ -353,6 +452,9 @@
156+
regmap = at24_translate_offset(at24, &offset);
157+
count = at24_adjust_read_count(at24, offset, count);
158+
159+
+ if (at24->smbus_addr16_fallback)
160+
+ return at24_smbus_read(at24, buf, offset, count);
161+
+
162+
/* adjust offset for mac and serial read ops */
163+
offset += at24->offset_adj;
164+
165+
@@ -411,6 +513,10 @@
166+
167+
regmap = at24_translate_offset(at24, &offset);
168+
count = at24_adjust_write_count(at24, offset, count);
169+
+
170+
+ if (at24->smbus_addr16_fallback)
171+
+ return at24_smbus_write(at24, buf, offset, count);
172+
+
173+
timeout = jiffies + msecs_to_jiffies(at24_write_timeout);
174+
175+
do {
176+
@@ -601,6 +707,7 @@
177+
bool i2c_fn_i2c, i2c_fn_block;
178+
unsigned int i, num_addresses;
179+
struct at24_data *at24;
180+
+ bool smbus_addr16_fallback;
181+
bool full_power;
182+
struct regmap *regmap;
183+
bool writable;
184+
@@ -611,6 +718,23 @@
185+
i2c_fn_block = i2c_check_functionality(client->adapter,
186+
I2C_FUNC_SMBUS_WRITE_I2C_BLOCK);
187+
188+
+ /*
189+
+ * Detect SMBus-only adapters (e.g. AMD PIIX4) that have BYTE_DATA +
190+
+ * WORD_DATA but lack I2C_FUNC_I2C and I2C_FUNC_SMBUS_I2C_BLOCK.
191+
+ * For 16-bit addressed EEPROMs, bypass regmap and use SMBus helpers.
192+
+ */
193+
+ smbus_addr16_fallback = false;
194+
+ if (!i2c_fn_i2c && !i2c_fn_block) {
195+
+ bool has_byte = i2c_check_functionality(client->adapter,
196+
+ I2C_FUNC_SMBUS_BYTE_DATA);
197+
+ bool has_word = i2c_check_functionality(client->adapter,
198+
+ I2C_FUNC_SMBUS_WORD_DATA);
199+
+ if (has_byte && has_word) {
200+
+ smbus_addr16_fallback = true;
201+
+ dev_dbg(dev, "SMBus-only adapter: enabling 16-bit addr SMBus fallback\n");
202+
+ }
203+
+ }
204+
+
205+
cdata = i2c_get_match_data(client);
206+
if (!cdata)
207+
return -ENODEV;
208+
@@ -681,6 +805,13 @@
209+
regmap_config.val_bits = 8;
210+
regmap_config.reg_bits = (flags & AT24_FLAG_ADDR16) ? 16 : 8;
211+
regmap_config.disable_locking = true;
212+
+ /*
213+
+ * Force reg_bits=8 when the SMBus fallback path is active so that
214+
+ * regmap_get_i2c_bus() can pick regmap_smbus_byte and succeed on PIIX4.
215+
+ * The actual 16-bit address I/O is done entirely by our helpers.
216+
+ */
217+
+ if (smbus_addr16_fallback && (flags & AT24_FLAG_ADDR16))
218+
+ regmap_config.reg_bits = 8;
219+
220+
regmap = devm_regmap_init_i2c(client, &regmap_config);
221+
if (IS_ERR(regmap))
222+
@@ -700,6 +831,8 @@
223+
at24->num_addresses = num_addresses;
224+
at24->offset_adj = at24_get_offset_adj(flags, byte_len);
225+
at24->client_regmaps[0] = regmap;
226+
+ at24->smbus_addr16_fallback = smbus_addr16_fallback;
227+
+ at24->smbus_client = client;
228+
229+
at24->vcc_reg = devm_regulator_get(dev, "vcc");
230+
if (IS_ERR(at24->vcc_reg))

0 commit comments

Comments
 (0)