Commit | Line | Data |
---|---|---|
1802d0be | 1 | // SPDX-License-Identifier: GPL-2.0-only |
beb58aa3 OJ |
2 | /* |
3 | * Copyright (C) 2006-2007 PA Semi, Inc | |
4 | * | |
5 | * SMBus host driver for PA Semi PWRficient | |
beb58aa3 OJ |
6 | */ |
7 | ||
8 | #include <linux/module.h> | |
9 | #include <linux/pci.h> | |
10 | #include <linux/kernel.h> | |
11 | #include <linux/stddef.h> | |
12 | #include <linux/sched.h> | |
13 | #include <linux/i2c.h> | |
14 | #include <linux/delay.h> | |
5a0e3ad6 | 15 | #include <linux/slab.h> |
21782180 | 16 | #include <linux/io.h> |
beb58aa3 | 17 | |
9bc5f4f6 | 18 | #include "i2c-pasemi-core.h" |
beb58aa3 OJ |
19 | |
20 | /* Register offsets */ | |
21 | #define REG_MTXFIFO 0x00 | |
22 | #define REG_MRXFIFO 0x04 | |
23 | #define REG_SMSTA 0x14 | |
e826192c | 24 | #define REG_IMASK 0x18 |
beb58aa3 | 25 | #define REG_CTL 0x1c |
3abdc89b | 26 | #define REG_REV 0x28 |
beb58aa3 OJ |
27 | |
28 | /* Register defs */ | |
29 | #define MTXFIFO_READ 0x00000400 | |
30 | #define MTXFIFO_STOP 0x00000200 | |
31 | #define MTXFIFO_START 0x00000100 | |
32 | #define MTXFIFO_DATA_M 0x000000ff | |
33 | ||
34 | #define MRXFIFO_EMPTY 0x00000100 | |
35 | #define MRXFIFO_DATA_M 0x000000ff | |
36 | ||
37 | #define SMSTA_XEN 0x08000000 | |
be8a1f7c | 38 | #define SMSTA_MTN 0x00200000 |
beb58aa3 OJ |
39 | |
40 | #define CTL_MRR 0x00000400 | |
41 | #define CTL_MTR 0x00000200 | |
3abdc89b | 42 | #define CTL_EN 0x00000800 |
beb58aa3 OJ |
43 | #define CTL_CLK_M 0x000000ff |
44 | ||
beb58aa3 OJ |
45 | static inline void reg_write(struct pasemi_smbus *smbus, int reg, int val) |
46 | { | |
a2c34bfd | 47 | dev_dbg(smbus->dev, "smbus write reg %x val %08x\n", reg, val); |
3a7442ac | 48 | iowrite32(val, smbus->ioaddr + reg); |
beb58aa3 OJ |
49 | } |
50 | ||
51 | static inline int reg_read(struct pasemi_smbus *smbus, int reg) | |
52 | { | |
53 | int ret; | |
3a7442ac | 54 | ret = ioread32(smbus->ioaddr + reg); |
a2c34bfd | 55 | dev_dbg(smbus->dev, "smbus read reg %x val %08x\n", reg, ret); |
beb58aa3 OJ |
56 | return ret; |
57 | } | |
58 | ||
59 | #define TXFIFO_WR(smbus, reg) reg_write((smbus), REG_MTXFIFO, (reg)) | |
60 | #define RXFIFO_RD(smbus) reg_read((smbus), REG_MRXFIFO) | |
61 | ||
1a62668c SP |
62 | static void pasemi_reset(struct pasemi_smbus *smbus) |
63 | { | |
fd664ab2 SP |
64 | u32 val = (CTL_MTR | CTL_MRR | (smbus->clk_div & CTL_CLK_M)); |
65 | ||
3abdc89b SP |
66 | if (smbus->hw_rev >= 6) |
67 | val |= CTL_EN; | |
68 | ||
fd664ab2 | 69 | reg_write(smbus, REG_CTL, val); |
e826192c | 70 | reinit_completion(&smbus->irq_completion); |
1a62668c SP |
71 | } |
72 | ||
beb58aa3 OJ |
73 | static void pasemi_smb_clear(struct pasemi_smbus *smbus) |
74 | { | |
75 | unsigned int status; | |
76 | ||
77 | status = reg_read(smbus, REG_SMSTA); | |
78 | reg_write(smbus, REG_SMSTA, status); | |
79 | } | |
80 | ||
80322143 | 81 | static int pasemi_smb_waitready(struct pasemi_smbus *smbus) |
beb58aa3 | 82 | { |
e826192c | 83 | int timeout = 100; |
beb58aa3 OJ |
84 | unsigned int status; |
85 | ||
e826192c AS |
86 | if (smbus->use_irq) { |
87 | reinit_completion(&smbus->irq_completion); | |
88 | reg_write(smbus, REG_IMASK, SMSTA_XEN | SMSTA_MTN); | |
89 | wait_for_completion_timeout(&smbus->irq_completion, msecs_to_jiffies(100)); | |
90 | reg_write(smbus, REG_IMASK, 0); | |
beb58aa3 | 91 | status = reg_read(smbus, REG_SMSTA); |
e826192c AS |
92 | } else { |
93 | status = reg_read(smbus, REG_SMSTA); | |
94 | while (!(status & SMSTA_XEN) && timeout--) { | |
95 | msleep(1); | |
96 | status = reg_read(smbus, REG_SMSTA); | |
97 | } | |
beb58aa3 OJ |
98 | } |
99 | ||
be8a1f7c OJ |
100 | /* Got NACK? */ |
101 | if (status & SMSTA_MTN) | |
102 | return -ENXIO; | |
103 | ||
beb58aa3 | 104 | if (timeout < 0) { |
c06f50ed | 105 | dev_warn(smbus->dev, "Timeout, status 0x%08x\n", status); |
beb58aa3 OJ |
106 | reg_write(smbus, REG_SMSTA, status); |
107 | return -ETIME; | |
108 | } | |
109 | ||
110 | /* Clear XEN */ | |
111 | reg_write(smbus, REG_SMSTA, SMSTA_XEN); | |
112 | ||
113 | return 0; | |
114 | } | |
115 | ||
116 | static int pasemi_i2c_xfer_msg(struct i2c_adapter *adapter, | |
117 | struct i2c_msg *msg, int stop) | |
118 | { | |
119 | struct pasemi_smbus *smbus = adapter->algo_data; | |
120 | int read, i, err; | |
121 | u32 rd; | |
122 | ||
123 | read = msg->flags & I2C_M_RD ? 1 : 0; | |
124 | ||
30a64757 | 125 | TXFIFO_WR(smbus, MTXFIFO_START | i2c_8bit_addr_from_msg(msg)); |
beb58aa3 OJ |
126 | |
127 | if (read) { | |
128 | TXFIFO_WR(smbus, msg->len | MTXFIFO_READ | | |
129 | (stop ? MTXFIFO_STOP : 0)); | |
130 | ||
131 | err = pasemi_smb_waitready(smbus); | |
132 | if (err) | |
133 | goto reset_out; | |
134 | ||
135 | for (i = 0; i < msg->len; i++) { | |
136 | rd = RXFIFO_RD(smbus); | |
137 | if (rd & MRXFIFO_EMPTY) { | |
138 | err = -ENODATA; | |
139 | goto reset_out; | |
140 | } | |
141 | msg->buf[i] = rd & MRXFIFO_DATA_M; | |
142 | } | |
143 | } else { | |
144 | for (i = 0; i < msg->len - 1; i++) | |
145 | TXFIFO_WR(smbus, msg->buf[i]); | |
146 | ||
080dfbe1 | 147 | TXFIFO_WR(smbus, msg->buf[msg->len-1] | |
beb58aa3 | 148 | (stop ? MTXFIFO_STOP : 0)); |
bd8963e6 MP |
149 | |
150 | if (stop) { | |
151 | err = pasemi_smb_waitready(smbus); | |
152 | if (err) | |
153 | goto reset_out; | |
154 | } | |
beb58aa3 OJ |
155 | } |
156 | ||
157 | return 0; | |
158 | ||
159 | reset_out: | |
1a62668c | 160 | pasemi_reset(smbus); |
beb58aa3 OJ |
161 | return err; |
162 | } | |
163 | ||
164 | static int pasemi_i2c_xfer(struct i2c_adapter *adapter, | |
165 | struct i2c_msg *msgs, int num) | |
166 | { | |
167 | struct pasemi_smbus *smbus = adapter->algo_data; | |
168 | int ret, i; | |
169 | ||
170 | pasemi_smb_clear(smbus); | |
171 | ||
172 | ret = 0; | |
173 | ||
174 | for (i = 0; i < num && !ret; i++) | |
175 | ret = pasemi_i2c_xfer_msg(adapter, &msgs[i], (i == (num - 1))); | |
176 | ||
177 | return ret ? ret : num; | |
178 | } | |
179 | ||
180 | static int pasemi_smb_xfer(struct i2c_adapter *adapter, | |
181 | u16 addr, unsigned short flags, char read_write, u8 command, | |
182 | int size, union i2c_smbus_data *data) | |
183 | { | |
184 | struct pasemi_smbus *smbus = adapter->algo_data; | |
185 | unsigned int rd; | |
186 | int read_flag, err; | |
187 | int len = 0, i; | |
188 | ||
189 | /* All our ops take 8-bit shifted addresses */ | |
190 | addr <<= 1; | |
191 | read_flag = read_write == I2C_SMBUS_READ; | |
192 | ||
193 | pasemi_smb_clear(smbus); | |
194 | ||
195 | switch (size) { | |
196 | case I2C_SMBUS_QUICK: | |
197 | TXFIFO_WR(smbus, addr | read_flag | MTXFIFO_START | | |
198 | MTXFIFO_STOP); | |
199 | break; | |
200 | case I2C_SMBUS_BYTE: | |
201 | TXFIFO_WR(smbus, addr | read_flag | MTXFIFO_START); | |
202 | if (read_write) | |
203 | TXFIFO_WR(smbus, 1 | MTXFIFO_STOP | MTXFIFO_READ); | |
204 | else | |
205 | TXFIFO_WR(smbus, MTXFIFO_STOP | command); | |
206 | break; | |
207 | case I2C_SMBUS_BYTE_DATA: | |
208 | TXFIFO_WR(smbus, addr | MTXFIFO_START); | |
209 | TXFIFO_WR(smbus, command); | |
210 | if (read_write) { | |
211 | TXFIFO_WR(smbus, addr | I2C_SMBUS_READ | MTXFIFO_START); | |
212 | TXFIFO_WR(smbus, 1 | MTXFIFO_READ | MTXFIFO_STOP); | |
213 | } else { | |
214 | TXFIFO_WR(smbus, MTXFIFO_STOP | data->byte); | |
215 | } | |
216 | break; | |
217 | case I2C_SMBUS_WORD_DATA: | |
218 | TXFIFO_WR(smbus, addr | MTXFIFO_START); | |
219 | TXFIFO_WR(smbus, command); | |
220 | if (read_write) { | |
221 | TXFIFO_WR(smbus, addr | I2C_SMBUS_READ | MTXFIFO_START); | |
222 | TXFIFO_WR(smbus, 2 | MTXFIFO_READ | MTXFIFO_STOP); | |
223 | } else { | |
224 | TXFIFO_WR(smbus, data->word & MTXFIFO_DATA_M); | |
225 | TXFIFO_WR(smbus, MTXFIFO_STOP | (data->word >> 8)); | |
226 | } | |
227 | break; | |
228 | case I2C_SMBUS_BLOCK_DATA: | |
229 | TXFIFO_WR(smbus, addr | MTXFIFO_START); | |
230 | TXFIFO_WR(smbus, command); | |
231 | if (read_write) { | |
232 | TXFIFO_WR(smbus, addr | I2C_SMBUS_READ | MTXFIFO_START); | |
233 | TXFIFO_WR(smbus, 1 | MTXFIFO_READ); | |
234 | rd = RXFIFO_RD(smbus); | |
235 | len = min_t(u8, (rd & MRXFIFO_DATA_M), | |
236 | I2C_SMBUS_BLOCK_MAX); | |
080dfbe1 | 237 | TXFIFO_WR(smbus, len | MTXFIFO_READ | |
beb58aa3 OJ |
238 | MTXFIFO_STOP); |
239 | } else { | |
240 | len = min_t(u8, data->block[0], I2C_SMBUS_BLOCK_MAX); | |
241 | TXFIFO_WR(smbus, len); | |
242 | for (i = 1; i < len; i++) | |
243 | TXFIFO_WR(smbus, data->block[i]); | |
244 | TXFIFO_WR(smbus, data->block[len] | MTXFIFO_STOP); | |
245 | } | |
246 | break; | |
247 | case I2C_SMBUS_PROC_CALL: | |
248 | read_write = I2C_SMBUS_READ; | |
249 | TXFIFO_WR(smbus, addr | MTXFIFO_START); | |
250 | TXFIFO_WR(smbus, command); | |
251 | TXFIFO_WR(smbus, data->word & MTXFIFO_DATA_M); | |
252 | TXFIFO_WR(smbus, (data->word >> 8) & MTXFIFO_DATA_M); | |
253 | TXFIFO_WR(smbus, addr | I2C_SMBUS_READ | MTXFIFO_START); | |
254 | TXFIFO_WR(smbus, 2 | MTXFIFO_STOP | MTXFIFO_READ); | |
255 | break; | |
256 | case I2C_SMBUS_BLOCK_PROC_CALL: | |
257 | len = min_t(u8, data->block[0], I2C_SMBUS_BLOCK_MAX - 1); | |
258 | read_write = I2C_SMBUS_READ; | |
259 | TXFIFO_WR(smbus, addr | MTXFIFO_START); | |
260 | TXFIFO_WR(smbus, command); | |
261 | TXFIFO_WR(smbus, len); | |
262 | for (i = 1; i <= len; i++) | |
263 | TXFIFO_WR(smbus, data->block[i]); | |
264 | TXFIFO_WR(smbus, addr | I2C_SMBUS_READ); | |
265 | TXFIFO_WR(smbus, MTXFIFO_READ | 1); | |
266 | rd = RXFIFO_RD(smbus); | |
267 | len = min_t(u8, (rd & MRXFIFO_DATA_M), | |
268 | I2C_SMBUS_BLOCK_MAX - len); | |
080dfbe1 | 269 | TXFIFO_WR(smbus, len | MTXFIFO_READ | MTXFIFO_STOP); |
beb58aa3 OJ |
270 | break; |
271 | ||
272 | default: | |
273 | dev_warn(&adapter->dev, "Unsupported transaction %d\n", size); | |
274 | return -EINVAL; | |
275 | } | |
276 | ||
277 | err = pasemi_smb_waitready(smbus); | |
278 | if (err) | |
279 | goto reset_out; | |
280 | ||
281 | if (read_write == I2C_SMBUS_WRITE) | |
282 | return 0; | |
283 | ||
284 | switch (size) { | |
285 | case I2C_SMBUS_BYTE: | |
286 | case I2C_SMBUS_BYTE_DATA: | |
287 | rd = RXFIFO_RD(smbus); | |
288 | if (rd & MRXFIFO_EMPTY) { | |
289 | err = -ENODATA; | |
290 | goto reset_out; | |
291 | } | |
292 | data->byte = rd & MRXFIFO_DATA_M; | |
293 | break; | |
294 | case I2C_SMBUS_WORD_DATA: | |
295 | case I2C_SMBUS_PROC_CALL: | |
296 | rd = RXFIFO_RD(smbus); | |
297 | if (rd & MRXFIFO_EMPTY) { | |
298 | err = -ENODATA; | |
299 | goto reset_out; | |
300 | } | |
301 | data->word = rd & MRXFIFO_DATA_M; | |
302 | rd = RXFIFO_RD(smbus); | |
303 | if (rd & MRXFIFO_EMPTY) { | |
304 | err = -ENODATA; | |
305 | goto reset_out; | |
306 | } | |
307 | data->word |= (rd & MRXFIFO_DATA_M) << 8; | |
308 | break; | |
309 | case I2C_SMBUS_BLOCK_DATA: | |
310 | case I2C_SMBUS_BLOCK_PROC_CALL: | |
311 | data->block[0] = len; | |
312 | for (i = 1; i <= len; i ++) { | |
313 | rd = RXFIFO_RD(smbus); | |
314 | if (rd & MRXFIFO_EMPTY) { | |
315 | err = -ENODATA; | |
316 | goto reset_out; | |
317 | } | |
318 | data->block[i] = rd & MRXFIFO_DATA_M; | |
319 | } | |
320 | break; | |
321 | } | |
322 | ||
323 | return 0; | |
324 | ||
325 | reset_out: | |
1a62668c | 326 | pasemi_reset(smbus); |
beb58aa3 OJ |
327 | return err; |
328 | } | |
329 | ||
330 | static u32 pasemi_smb_func(struct i2c_adapter *adapter) | |
331 | { | |
332 | return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | | |
333 | I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | | |
334 | I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_PROC_CALL | | |
335 | I2C_FUNC_SMBUS_BLOCK_PROC_CALL | I2C_FUNC_I2C; | |
336 | } | |
337 | ||
338 | static const struct i2c_algorithm smbus_algorithm = { | |
339 | .master_xfer = pasemi_i2c_xfer, | |
340 | .smbus_xfer = pasemi_smb_xfer, | |
341 | .functionality = pasemi_smb_func, | |
342 | }; | |
343 | ||
9bc5f4f6 | 344 | int pasemi_i2c_common_probe(struct pasemi_smbus *smbus) |
6adb00c7 SP |
345 | { |
346 | int error; | |
347 | ||
348 | smbus->adapter.owner = THIS_MODULE; | |
349 | snprintf(smbus->adapter.name, sizeof(smbus->adapter.name), | |
350 | "PA Semi SMBus adapter (%s)", dev_name(smbus->dev)); | |
6adb00c7 SP |
351 | smbus->adapter.algo = &smbus_algorithm; |
352 | smbus->adapter.algo_data = smbus; | |
353 | ||
354 | /* set up the sysfs linkage to our parent device */ | |
355 | smbus->adapter.dev.parent = smbus->dev; | |
e826192c AS |
356 | smbus->use_irq = 0; |
357 | init_completion(&smbus->irq_completion); | |
6adb00c7 | 358 | |
3abdc89b SP |
359 | if (smbus->hw_rev != PASEMI_HW_REV_PCI) |
360 | smbus->hw_rev = reg_read(smbus, REG_REV); | |
361 | ||
e826192c AS |
362 | reg_write(smbus, REG_IMASK, 0); |
363 | ||
1a62668c | 364 | pasemi_reset(smbus); |
6adb00c7 | 365 | |
a2c34bfd | 366 | error = devm_i2c_add_adapter(smbus->dev, &smbus->adapter); |
6adb00c7 SP |
367 | if (error) |
368 | return error; | |
369 | ||
370 | return 0; | |
371 | } | |
f44bff19 | 372 | EXPORT_SYMBOL_GPL(pasemi_i2c_common_probe); |
e826192c AS |
373 | |
374 | irqreturn_t pasemi_irq_handler(int irq, void *dev_id) | |
375 | { | |
376 | struct pasemi_smbus *smbus = dev_id; | |
377 | ||
378 | reg_write(smbus, REG_IMASK, 0); | |
379 | complete(&smbus->irq_completion); | |
380 | return IRQ_HANDLED; | |
381 | } | |
f44bff19 AB |
382 | EXPORT_SYMBOL_GPL(pasemi_irq_handler); |
383 | ||
384 | MODULE_LICENSE("GPL"); | |
385 | MODULE_AUTHOR("Olof Johansson <olof@lixom.net>"); | |
386 | MODULE_DESCRIPTION("PA Semi PWRficient SMBus driver"); |