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