1 | /* |
2 | * (C) Copyright 2009-2010 |
3 | * Nokia Siemens Networks, michael.lawnick.ext@nsn.com |
4 | * |
5 | * Portions Copyright (C) 2010 - 2016 Cavium, Inc. |
6 | * |
7 | * This is a driver for the i2c adapter in Cavium Networks' OCTEON processors. |
8 | * |
9 | * This file is licensed under the terms of the GNU General Public |
10 | * License version 2. This program is licensed "as is" without any |
11 | * warranty of any kind, whether express or implied. |
12 | */ |
13 | |
14 | #include <linux/atomic.h> |
15 | #include <linux/delay.h> |
16 | #include <linux/i2c.h> |
17 | #include <linux/interrupt.h> |
18 | #include <linux/io.h> |
19 | #include <linux/kernel.h> |
20 | #include <linux/module.h> |
21 | #include <linux/of.h> |
22 | #include <linux/platform_device.h> |
23 | #include <linux/sched.h> |
24 | #include <linux/slab.h> |
25 | |
26 | #include <asm/octeon/octeon.h> |
27 | #include "i2c-octeon-core.h" |
28 | |
29 | #define DRV_NAME "i2c-octeon" |
30 | |
31 | /** |
32 | * octeon_i2c_int_enable - enable the CORE interrupt |
33 | * @i2c: The struct octeon_i2c |
34 | * |
35 | * The interrupt will be asserted when there is non-STAT_IDLE state in |
36 | * the SW_TWSI_EOP_TWSI_STAT register. |
37 | */ |
38 | static void octeon_i2c_int_enable(struct octeon_i2c *i2c) |
39 | { |
40 | octeon_i2c_write_int(i2c, TWSI_INT_CORE_EN); |
41 | } |
42 | |
43 | /* disable the CORE interrupt */ |
44 | static void octeon_i2c_int_disable(struct octeon_i2c *i2c) |
45 | { |
46 | /* clear TS/ST/IFLG events */ |
47 | octeon_i2c_write_int(i2c, data: 0); |
48 | } |
49 | |
50 | /** |
51 | * octeon_i2c_int_enable78 - enable the CORE interrupt |
52 | * @i2c: The struct octeon_i2c |
53 | * |
54 | * The interrupt will be asserted when there is non-STAT_IDLE state in the |
55 | * SW_TWSI_EOP_TWSI_STAT register. |
56 | */ |
57 | static void octeon_i2c_int_enable78(struct octeon_i2c *i2c) |
58 | { |
59 | atomic_inc_return(v: &i2c->int_enable_cnt); |
60 | enable_irq(irq: i2c->irq); |
61 | } |
62 | |
63 | static void __octeon_i2c_irq_disable(atomic_t *cnt, int irq) |
64 | { |
65 | int count; |
66 | |
67 | /* |
68 | * The interrupt can be disabled in two places, but we only |
69 | * want to make the disable_irq_nosync() call once, so keep |
70 | * track with the atomic variable. |
71 | */ |
72 | count = atomic_dec_if_positive(v: cnt); |
73 | if (count >= 0) |
74 | disable_irq_nosync(irq); |
75 | } |
76 | |
77 | /* disable the CORE interrupt */ |
78 | static void octeon_i2c_int_disable78(struct octeon_i2c *i2c) |
79 | { |
80 | __octeon_i2c_irq_disable(cnt: &i2c->int_enable_cnt, irq: i2c->irq); |
81 | } |
82 | |
83 | /** |
84 | * octeon_i2c_hlc_int_enable78 - enable the ST interrupt |
85 | * @i2c: The struct octeon_i2c |
86 | * |
87 | * The interrupt will be asserted when there is non-STAT_IDLE state in |
88 | * the SW_TWSI_EOP_TWSI_STAT register. |
89 | */ |
90 | static void octeon_i2c_hlc_int_enable78(struct octeon_i2c *i2c) |
91 | { |
92 | atomic_inc_return(v: &i2c->hlc_int_enable_cnt); |
93 | enable_irq(irq: i2c->hlc_irq); |
94 | } |
95 | |
96 | /* disable the ST interrupt */ |
97 | static void octeon_i2c_hlc_int_disable78(struct octeon_i2c *i2c) |
98 | { |
99 | __octeon_i2c_irq_disable(cnt: &i2c->hlc_int_enable_cnt, irq: i2c->hlc_irq); |
100 | } |
101 | |
102 | /* HLC interrupt service routine */ |
103 | static irqreturn_t octeon_i2c_hlc_isr78(int irq, void *dev_id) |
104 | { |
105 | struct octeon_i2c *i2c = dev_id; |
106 | |
107 | i2c->hlc_int_disable(i2c); |
108 | wake_up(&i2c->queue); |
109 | |
110 | return IRQ_HANDLED; |
111 | } |
112 | |
113 | static void octeon_i2c_hlc_int_enable(struct octeon_i2c *i2c) |
114 | { |
115 | octeon_i2c_write_int(i2c, TWSI_INT_ST_EN); |
116 | } |
117 | |
118 | static u32 octeon_i2c_functionality(struct i2c_adapter *adap) |
119 | { |
120 | return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK) | |
121 | I2C_FUNC_SMBUS_READ_BLOCK_DATA | I2C_SMBUS_BLOCK_PROC_CALL; |
122 | } |
123 | |
124 | static const struct i2c_algorithm octeon_i2c_algo = { |
125 | .master_xfer = octeon_i2c_xfer, |
126 | .functionality = octeon_i2c_functionality, |
127 | }; |
128 | |
129 | static const struct i2c_adapter octeon_i2c_ops = { |
130 | .owner = THIS_MODULE, |
131 | .name = "OCTEON adapter" , |
132 | .algo = &octeon_i2c_algo, |
133 | }; |
134 | |
135 | static int octeon_i2c_probe(struct platform_device *pdev) |
136 | { |
137 | struct device_node *node = pdev->dev.of_node; |
138 | int irq, result = 0, hlc_irq = 0; |
139 | struct octeon_i2c *i2c; |
140 | bool cn78xx_style; |
141 | |
142 | cn78xx_style = of_device_is_compatible(device: node, "cavium,octeon-7890-twsi" ); |
143 | if (cn78xx_style) { |
144 | hlc_irq = platform_get_irq(pdev, 0); |
145 | if (hlc_irq < 0) |
146 | return hlc_irq; |
147 | |
148 | irq = platform_get_irq(pdev, 2); |
149 | if (irq < 0) |
150 | return irq; |
151 | } else { |
152 | /* All adaptors have an irq. */ |
153 | irq = platform_get_irq(pdev, 0); |
154 | if (irq < 0) |
155 | return irq; |
156 | } |
157 | |
158 | i2c = devm_kzalloc(dev: &pdev->dev, size: sizeof(*i2c), GFP_KERNEL); |
159 | if (!i2c) { |
160 | result = -ENOMEM; |
161 | goto out; |
162 | } |
163 | i2c->dev = &pdev->dev; |
164 | |
165 | i2c->roff.sw_twsi = 0x00; |
166 | i2c->roff.twsi_int = 0x10; |
167 | i2c->roff.sw_twsi_ext = 0x18; |
168 | |
169 | i2c->twsi_base = devm_platform_ioremap_resource(pdev, index: 0); |
170 | if (IS_ERR(ptr: i2c->twsi_base)) { |
171 | result = PTR_ERR(ptr: i2c->twsi_base); |
172 | goto out; |
173 | } |
174 | |
175 | /* |
176 | * "clock-rate" is a legacy binding, the official binding is |
177 | * "clock-frequency". Try the official one first and then |
178 | * fall back if it doesn't exist. |
179 | */ |
180 | if (of_property_read_u32(np: node, propname: "clock-frequency" , out_value: &i2c->twsi_freq) && |
181 | of_property_read_u32(np: node, propname: "clock-rate" , out_value: &i2c->twsi_freq)) { |
182 | dev_err(i2c->dev, |
183 | "no I2C 'clock-rate' or 'clock-frequency' property\n" ); |
184 | result = -ENXIO; |
185 | goto out; |
186 | } |
187 | |
188 | i2c->sys_freq = octeon_get_io_clock_rate(); |
189 | |
190 | init_waitqueue_head(&i2c->queue); |
191 | |
192 | i2c->irq = irq; |
193 | |
194 | if (cn78xx_style) { |
195 | i2c->hlc_irq = hlc_irq; |
196 | |
197 | i2c->int_enable = octeon_i2c_int_enable78; |
198 | i2c->int_disable = octeon_i2c_int_disable78; |
199 | i2c->hlc_int_enable = octeon_i2c_hlc_int_enable78; |
200 | i2c->hlc_int_disable = octeon_i2c_hlc_int_disable78; |
201 | |
202 | irq_set_status_flags(i2c->irq, IRQ_NOAUTOEN); |
203 | irq_set_status_flags(i2c->hlc_irq, IRQ_NOAUTOEN); |
204 | |
205 | result = devm_request_irq(dev: &pdev->dev, irq: i2c->hlc_irq, |
206 | handler: octeon_i2c_hlc_isr78, irqflags: 0, |
207 | DRV_NAME, dev_id: i2c); |
208 | if (result < 0) { |
209 | dev_err(i2c->dev, "failed to attach interrupt\n" ); |
210 | goto out; |
211 | } |
212 | } else { |
213 | i2c->int_enable = octeon_i2c_int_enable; |
214 | i2c->int_disable = octeon_i2c_int_disable; |
215 | i2c->hlc_int_enable = octeon_i2c_hlc_int_enable; |
216 | i2c->hlc_int_disable = octeon_i2c_int_disable; |
217 | } |
218 | |
219 | result = devm_request_irq(dev: &pdev->dev, irq: i2c->irq, |
220 | handler: octeon_i2c_isr, irqflags: 0, DRV_NAME, dev_id: i2c); |
221 | if (result < 0) { |
222 | dev_err(i2c->dev, "failed to attach interrupt\n" ); |
223 | goto out; |
224 | } |
225 | |
226 | if (OCTEON_IS_MODEL(OCTEON_CN38XX)) |
227 | i2c->broken_irq_check = true; |
228 | |
229 | result = octeon_i2c_init_lowlevel(i2c); |
230 | if (result) { |
231 | dev_err(i2c->dev, "init low level failed\n" ); |
232 | goto out; |
233 | } |
234 | |
235 | octeon_i2c_set_clock(i2c); |
236 | |
237 | i2c->adap = octeon_i2c_ops; |
238 | i2c->adap.timeout = msecs_to_jiffies(m: 2); |
239 | i2c->adap.retries = 5; |
240 | i2c->adap.bus_recovery_info = &octeon_i2c_recovery_info; |
241 | i2c->adap.dev.parent = &pdev->dev; |
242 | i2c->adap.dev.of_node = node; |
243 | i2c_set_adapdata(adap: &i2c->adap, data: i2c); |
244 | platform_set_drvdata(pdev, data: i2c); |
245 | |
246 | result = i2c_add_adapter(adap: &i2c->adap); |
247 | if (result < 0) |
248 | goto out; |
249 | dev_info(i2c->dev, "probed\n" ); |
250 | return 0; |
251 | |
252 | out: |
253 | return result; |
254 | }; |
255 | |
256 | static void octeon_i2c_remove(struct platform_device *pdev) |
257 | { |
258 | struct octeon_i2c *i2c = platform_get_drvdata(pdev); |
259 | |
260 | i2c_del_adapter(adap: &i2c->adap); |
261 | }; |
262 | |
263 | static const struct of_device_id octeon_i2c_match[] = { |
264 | { .compatible = "cavium,octeon-3860-twsi" , }, |
265 | { .compatible = "cavium,octeon-7890-twsi" , }, |
266 | {}, |
267 | }; |
268 | MODULE_DEVICE_TABLE(of, octeon_i2c_match); |
269 | |
270 | static struct platform_driver octeon_i2c_driver = { |
271 | .probe = octeon_i2c_probe, |
272 | .remove_new = octeon_i2c_remove, |
273 | .driver = { |
274 | .name = DRV_NAME, |
275 | .of_match_table = octeon_i2c_match, |
276 | }, |
277 | }; |
278 | |
279 | module_platform_driver(octeon_i2c_driver); |
280 | |
281 | MODULE_AUTHOR("Michael Lawnick <michael.lawnick.ext@nsn.com>" ); |
282 | MODULE_DESCRIPTION("I2C-Bus adapter for Cavium OCTEON processors" ); |
283 | MODULE_LICENSE("GPL" ); |
284 | |