1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2006-2007 PA Semi, Inc |
4 | * |
5 | * SMBus host driver for PA Semi PWRficient |
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> |
15 | #include <linux/slab.h> |
16 | #include <linux/io.h> |
17 | |
18 | #include "i2c-pasemi-core.h" |
19 | |
20 | /* Register offsets */ |
21 | #define REG_MTXFIFO 0x00 |
22 | #define REG_MRXFIFO 0x04 |
23 | #define REG_SMSTA 0x14 |
24 | #define REG_IMASK 0x18 |
25 | #define REG_CTL 0x1c |
26 | #define REG_REV 0x28 |
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 |
38 | #define SMSTA_MTN 0x00200000 |
39 | |
40 | #define CTL_MRR 0x00000400 |
41 | #define CTL_MTR 0x00000200 |
42 | #define CTL_EN 0x00000800 |
43 | #define CTL_CLK_M 0x000000ff |
44 | |
45 | static inline void reg_write(struct pasemi_smbus *smbus, int reg, int val) |
46 | { |
47 | dev_dbg(smbus->dev, "smbus write reg %x val %08x\n" , reg, val); |
48 | iowrite32(val, smbus->ioaddr + reg); |
49 | } |
50 | |
51 | static inline int reg_read(struct pasemi_smbus *smbus, int reg) |
52 | { |
53 | int ret; |
54 | ret = ioread32(smbus->ioaddr + reg); |
55 | dev_dbg(smbus->dev, "smbus read reg %x val %08x\n" , reg, ret); |
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 | |
62 | static void pasemi_reset(struct pasemi_smbus *smbus) |
63 | { |
64 | u32 val = (CTL_MTR | CTL_MRR | (smbus->clk_div & CTL_CLK_M)); |
65 | |
66 | if (smbus->hw_rev >= 6) |
67 | val |= CTL_EN; |
68 | |
69 | reg_write(smbus, REG_CTL, val); |
70 | reinit_completion(x: &smbus->irq_completion); |
71 | } |
72 | |
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, val: status); |
79 | } |
80 | |
81 | static int pasemi_smb_waitready(struct pasemi_smbus *smbus) |
82 | { |
83 | int timeout = 100; |
84 | unsigned int status; |
85 | |
86 | if (smbus->use_irq) { |
87 | reinit_completion(x: &smbus->irq_completion); |
88 | reg_write(smbus, REG_IMASK, SMSTA_XEN | SMSTA_MTN); |
89 | wait_for_completion_timeout(x: &smbus->irq_completion, timeout: msecs_to_jiffies(m: 100)); |
90 | reg_write(smbus, REG_IMASK, val: 0); |
91 | status = reg_read(smbus, REG_SMSTA); |
92 | } else { |
93 | status = reg_read(smbus, REG_SMSTA); |
94 | while (!(status & SMSTA_XEN) && timeout--) { |
95 | msleep(msecs: 1); |
96 | status = reg_read(smbus, REG_SMSTA); |
97 | } |
98 | } |
99 | |
100 | /* Got NACK? */ |
101 | if (status & SMSTA_MTN) |
102 | return -ENXIO; |
103 | |
104 | if (timeout < 0) { |
105 | dev_warn(smbus->dev, "Timeout, status 0x%08x\n" , status); |
106 | reg_write(smbus, REG_SMSTA, val: 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 | |
125 | TXFIFO_WR(smbus, MTXFIFO_START | i2c_8bit_addr_from_msg(msg)); |
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 | |
147 | TXFIFO_WR(smbus, msg->buf[msg->len-1] | |
148 | (stop ? MTXFIFO_STOP : 0)); |
149 | |
150 | if (stop) { |
151 | err = pasemi_smb_waitready(smbus); |
152 | if (err) |
153 | goto reset_out; |
154 | } |
155 | } |
156 | |
157 | return 0; |
158 | |
159 | reset_out: |
160 | pasemi_reset(smbus); |
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, msg: &msgs[i], stop: (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); |
237 | TXFIFO_WR(smbus, len | MTXFIFO_READ | |
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); |
269 | TXFIFO_WR(smbus, len | MTXFIFO_READ | MTXFIFO_STOP); |
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: |
326 | pasemi_reset(smbus); |
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 | |
344 | int pasemi_i2c_common_probe(struct pasemi_smbus *smbus) |
345 | { |
346 | int error; |
347 | |
348 | smbus->adapter.owner = THIS_MODULE; |
349 | snprintf(buf: smbus->adapter.name, size: sizeof(smbus->adapter.name), |
350 | fmt: "PA Semi SMBus adapter (%s)" , dev_name(dev: smbus->dev)); |
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; |
356 | smbus->use_irq = 0; |
357 | init_completion(x: &smbus->irq_completion); |
358 | |
359 | if (smbus->hw_rev != PASEMI_HW_REV_PCI) |
360 | smbus->hw_rev = reg_read(smbus, REG_REV); |
361 | |
362 | reg_write(smbus, REG_IMASK, val: 0); |
363 | |
364 | pasemi_reset(smbus); |
365 | |
366 | error = devm_i2c_add_adapter(dev: smbus->dev, adapter: &smbus->adapter); |
367 | if (error) |
368 | return error; |
369 | |
370 | return 0; |
371 | } |
372 | EXPORT_SYMBOL_GPL(pasemi_i2c_common_probe); |
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, val: 0); |
379 | complete(&smbus->irq_completion); |
380 | return IRQ_HANDLED; |
381 | } |
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" ); |
387 | |