1 /*
2 * Arm SCP/MCP Software
3 * Copyright (c) 2019-2022, Arm Limited and Contributors. All rights reserved.
4 *
5 * SPDX-License-Identifier: BSD-3-Clause
6 */
7 #include <dw_apb_i2c.h>
8
9 #include <mod_dw_apb_i2c.h>
10 #include <mod_i2c.h>
11 #include <mod_timer.h>
12
13 #include <fwk_id.h>
14 #include <fwk_interrupt.h>
15 #include <fwk_mm.h>
16 #include <fwk_module.h>
17 #include <fwk_status.h>
18
19 #include <stdbool.h>
20 #include <stddef.h>
21
22 struct dw_apb_i2c_ctx {
23 const struct mod_dw_apb_i2c_dev_config *config;
24 const struct mod_i2c_driver_response_api *i2c_api;
25 const struct mod_timer_api *timer_api;
26 fwk_id_t i2c_id;
27 struct dw_apb_i2c_reg *i2c_reg;
28 bool read_on_going;
29 uint8_t byte_count;
30 uint8_t *data;
31 };
32
33 static struct dw_apb_i2c_ctx *ctx_table;
34
35 /*
36 * Static helpers
37 */
is_i2c_disabled(void * param)38 static bool is_i2c_disabled(void *param)
39 {
40 struct dw_apb_i2c_reg *i2c_reg = (struct dw_apb_i2c_reg *)param;
41
42 return ((i2c_reg->IC_ENABLE_STATUS & IC_ENABLE_STATUS_MASK) ==
43 IC_ENABLE_STATUS_DISABLED);
44 }
45
disable_i2c(struct dw_apb_i2c_ctx * ctx)46 static int disable_i2c(struct dw_apb_i2c_ctx *ctx)
47 {
48 int status;
49 fwk_id_t timer_id;
50 const struct mod_timer_api *timer_api;
51 struct dw_apb_i2c_reg *i2c_reg;
52
53 timer_api = ctx->timer_api;
54 timer_id = ctx->config->timer_id;
55 i2c_reg = ctx->i2c_reg;
56
57 /* Check whether the device is already disabled */
58 if (is_i2c_disabled(i2c_reg)) {
59 return FWK_SUCCESS;
60 }
61
62 /* The bus should be idle */
63 if ((ctx->i2c_reg->IC_STATUS & IC_STATUS_MST_ACTIVITY_MASK) != 0) {
64 return FWK_E_DEVICE;
65 }
66
67 /* Disable the I2C device */
68 ctx->i2c_reg->IC_ENABLE = IC_ENABLE_STATUS_DISABLED;
69
70 /* Wait until the device is disabled */
71 status = timer_api->wait(timer_id, I2C_TIMEOUT_US, is_i2c_disabled,
72 i2c_reg);
73 if (status != FWK_SUCCESS) {
74 return FWK_E_TIMEOUT;
75 }
76
77 return FWK_SUCCESS;
78 }
79
enable_i2c(struct dw_apb_i2c_ctx * ctx,uint8_t target_address)80 static int enable_i2c(struct dw_apb_i2c_ctx *ctx, uint8_t target_address)
81 {
82 int status;
83 struct dw_apb_i2c_reg *i2c_reg;
84
85 i2c_reg = ctx->i2c_reg;
86
87 /* Disable the I2C device to configure it */
88 status = disable_i2c(ctx);
89 if (status != FWK_SUCCESS) {
90 return FWK_E_DEVICE;
91 }
92
93 /* Program the target address */
94 i2c_reg->IC_TAR = (target_address & IC_TAR_ADDRESS);
95
96 /* Enable STOP detected interrupt and TX aborted interrupt */
97 i2c_reg->IC_INTR_MASK = (IC_INTR_STOP_DET_MASK | IC_INTR_TX_ABRT_MASK);
98
99 /* Enable the I2C device */
100 i2c_reg->IC_ENABLE = IC_ENABLE_STATUS_ENABLED;
101
102 return FWK_SUCCESS;
103 }
104
105 /*
106 * An IRQ is triggered if the transaction has been completed successfully or
107 * if the transaction has been aborted.
108 */
i2c_isr(uintptr_t data)109 static void i2c_isr(uintptr_t data)
110 {
111 unsigned int i;
112 int i2c_status = FWK_E_DEVICE;
113 struct dw_apb_i2c_reg *i2c_reg;
114 struct dw_apb_i2c_ctx *ctx = (struct dw_apb_i2c_ctx *)data;
115
116 i2c_reg = ctx->i2c_reg;
117
118 /* The transaction has completed successfully */
119 if (i2c_reg->IC_INTR_STAT & IC_INTR_STOP_DET_MASK) {
120 i2c_reg->IC_CLR_STOP_DET;
121 i2c_status = FWK_SUCCESS;
122 if (ctx->read_on_going) {
123 ctx->read_on_going = false;
124 /* Read the data from the device buffer */
125 for (i = 0; i < ctx->byte_count; i++) {
126 ctx->data[i] =
127 (uint8_t)(i2c_reg->IC_DATA_CMD & IC_DATA_CMD_DATA_MASK);
128 }
129 }
130 }
131
132 /* The transaction has been aborted */
133 if (i2c_reg->IC_INTR_STAT & IC_INTR_TX_ABRT_MASK) {
134 i2c_reg->IC_CLR_TX_ABRT;
135 }
136
137 ctx->i2c_api->transaction_completed(ctx->i2c_id, i2c_status);
138 }
139
140 /*
141 * Driver API
142 */
transmit_as_controller(fwk_id_t dev_id,struct mod_i2c_request * transmit_request)143 static int transmit_as_controller(
144 fwk_id_t dev_id,
145 struct mod_i2c_request *transmit_request)
146 {
147 int status;
148 unsigned int sent_bytes, flags;
149 struct dw_apb_i2c_ctx *ctx;
150
151 if (transmit_request->transmit_byte_count > I2C_TRANSMIT_BUFFER_LENGTH) {
152 return FWK_E_SUPPORT;
153 }
154
155 if (transmit_request->target_address == 0) {
156 return FWK_E_PARAM;
157 }
158
159 ctx = ctx_table + fwk_id_get_element_idx(dev_id);
160
161 status = enable_i2c(ctx, transmit_request->target_address);
162 if (status != FWK_SUCCESS) {
163 return FWK_E_DEVICE;
164 }
165
166 /* The program of the I2C controller cannot be interrupted. */
167 flags = fwk_interrupt_global_disable();
168
169 for (sent_bytes = 0; sent_bytes < transmit_request->transmit_byte_count;
170 sent_bytes++) {
171 ctx->i2c_reg->IC_DATA_CMD = transmit_request->transmit_data[sent_bytes];
172 }
173
174 fwk_interrupt_global_enable(flags);
175
176 /*
177 * The data has been pushed to the I2C FIFO for transmission to the
178 * target device. An interrupt will signal the completion of the
179 * transfer. The i2c_isr() interrupt handler will be invoked to notify
180 * the caller.
181 */
182 return FWK_PENDING;
183 }
184
receive_as_controller(fwk_id_t dev_id,struct mod_i2c_request * receive_request)185 static int receive_as_controller(
186 fwk_id_t dev_id,
187 struct mod_i2c_request *receive_request)
188 {
189 int status;
190 unsigned int i, flags;
191 struct dw_apb_i2c_ctx *ctx;
192
193 if (receive_request->receive_byte_count > I2C_RECEIVE_BUFFER_LENGTH) {
194 return FWK_E_SUPPORT;
195 }
196
197 if (receive_request->target_address == 0) {
198 return FWK_E_PARAM;
199 }
200
201 ctx = ctx_table + fwk_id_get_element_idx(dev_id);
202
203 ctx->byte_count = receive_request->receive_byte_count;
204 ctx->data = receive_request->receive_data;
205 ctx->read_on_going = true;
206
207 status = enable_i2c(ctx, receive_request->target_address);
208 if (status != FWK_SUCCESS) {
209 return FWK_E_DEVICE;
210 }
211
212 /* The program of the I2C controller cannot be interrupted. */
213 flags = fwk_interrupt_global_disable();
214
215 /* Program the I2C controller with the expected reply length in bytes. */
216 for (i = 0; i < receive_request->receive_byte_count; i++) {
217 ctx->i2c_reg->IC_DATA_CMD = IC_DATA_CMD_READ;
218 }
219
220 fwk_interrupt_global_enable(flags);
221
222 /*
223 * The command has been sent to the I2C for requesting data from
224 * the target device. An interrupt will signal the completion of the
225 * transfer. The i2c_isr() interrupt handler will be invoked to notify
226 * the caller.
227 */
228 return FWK_PENDING;
229 }
230
231 static const struct mod_i2c_driver_api driver_api = {
232 .transmit_as_controller = transmit_as_controller,
233 .receive_as_controller = receive_as_controller
234 };
235
236 /*
237 * Framework handlers
238 */
dw_apb_i2c_init(fwk_id_t module_id,unsigned int element_count,const void * data)239 static int dw_apb_i2c_init(fwk_id_t module_id,
240 unsigned int element_count,
241 const void *data)
242 {
243 ctx_table = fwk_mm_calloc(element_count, sizeof(*ctx_table));
244
245 return FWK_SUCCESS;
246 }
247
dw_apb_i2c_element_init(fwk_id_t element_id,unsigned int sub_element_count,const void * data)248 static int dw_apb_i2c_element_init(fwk_id_t element_id,
249 unsigned int sub_element_count,
250 const void *data)
251 {
252 struct mod_dw_apb_i2c_dev_config *config =
253 (struct mod_dw_apb_i2c_dev_config *)data;
254
255 if (config->reg == 0) {
256 return FWK_E_DATA;
257 }
258
259 ctx_table[fwk_id_get_element_idx(element_id)].config = config;
260 ctx_table[fwk_id_get_element_idx(element_id)].i2c_reg =
261 (struct dw_apb_i2c_reg *)config->reg;
262
263 return FWK_SUCCESS;
264 }
265
dw_apb_i2c_bind(fwk_id_t id,unsigned int round)266 static int dw_apb_i2c_bind(fwk_id_t id, unsigned int round)
267 {
268 int status;
269 struct dw_apb_i2c_ctx *ctx;
270 const struct mod_dw_apb_i2c_dev_config *config;
271
272 if (!fwk_module_is_valid_element_id(id) || (round == 0)) {
273 return FWK_SUCCESS;
274 }
275
276 ctx = ctx_table + fwk_id_get_element_idx(id);
277 config = ctx->config;
278
279 status = fwk_module_bind(config->timer_id, MOD_TIMER_API_ID_TIMER,
280 &ctx->timer_api);
281 if (status != FWK_SUCCESS) {
282 return status;
283 }
284
285 return fwk_module_bind(ctx->i2c_id, mod_i2c_api_id_driver_response,
286 &ctx->i2c_api);
287 }
288
dw_apb_i2c_process_bind_request(fwk_id_t source_id,fwk_id_t target_id,fwk_id_t api_id,const void ** api)289 static int dw_apb_i2c_process_bind_request(fwk_id_t source_id,
290 fwk_id_t target_id,
291 fwk_id_t api_id,
292 const void **api)
293 {
294 struct dw_apb_i2c_ctx *ctx;
295
296 if (!fwk_module_is_valid_element_id(target_id)) {
297 return FWK_E_PARAM;
298 }
299
300 ctx = ctx_table + fwk_id_get_element_idx(target_id);
301
302 if (!fwk_id_is_equal(api_id, mod_dw_apb_i2c_api_id_driver)) {
303 return FWK_E_PARAM;
304 }
305
306 ctx->i2c_id = source_id;
307
308 *api = &driver_api;
309
310 return FWK_SUCCESS;
311 }
312
dw_apb_i2c_start(fwk_id_t id)313 static int dw_apb_i2c_start(fwk_id_t id)
314 {
315 int status;
316 struct dw_apb_i2c_ctx *ctx;
317 unsigned int i2c_irq;
318
319 /* Nothing to do for the module */
320 if (!fwk_module_is_valid_element_id(id)) {
321 return FWK_SUCCESS;
322 }
323
324 ctx = ctx_table + fwk_id_get_element_idx(id);
325 i2c_irq = ctx->config->i2c_irq;
326
327 status = fwk_interrupt_set_isr_param(i2c_irq, i2c_isr, (uintptr_t)ctx);
328 if (status != FWK_SUCCESS) {
329 return FWK_E_DEVICE;
330 }
331
332 status = fwk_interrupt_clear_pending(i2c_irq);
333 if (status != FWK_SUCCESS) {
334 return FWK_E_DEVICE;
335 }
336
337 status = fwk_interrupt_enable(i2c_irq);
338 if (status != FWK_SUCCESS) {
339 return FWK_E_DEVICE;
340 }
341
342 return FWK_SUCCESS;
343 }
344
345 const struct fwk_module module_dw_apb_i2c = {
346 .api_count = (unsigned int)MOD_DW_APB_I2C_API_IDX_COUNT,
347 .type = FWK_MODULE_TYPE_DRIVER,
348 .init = dw_apb_i2c_init,
349 .element_init = dw_apb_i2c_element_init,
350 .bind = dw_apb_i2c_bind,
351 .start = dw_apb_i2c_start,
352 .process_bind_request = dw_apb_i2c_process_bind_request,
353 };
354