-
Notifications
You must be signed in to change notification settings - Fork 7.4k
/
i2c_common.c
431 lines (384 loc) · 15.5 KB
/
i2c_common.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <stdio.h>
#include "sdkconfig.h"
#include "esp_types.h"
#if CONFIG_I2C_ENABLE_DEBUG_LOG
// The local log level must be defined before including esp_log.h
// Set the maximum log level for this source file
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#endif
#include "esp_log.h"
#include "esp_check.h"
#include "esp_pm.h"
#include "freertos/FreeRTOS.h"
#include "hal/i2c_hal.h"
#include "hal/gpio_hal.h"
#include "esp_private/periph_ctrl.h"
#include "esp_rom_gpio.h"
#include "i2c_private.h"
#include "driver/gpio.h"
#include "soc/clk_tree_defs.h"
#include "soc/i2c_periph.h"
#include "esp_clk_tree.h"
#include "clk_ctrl_os.h"
#include "esp_private/gpio.h"
#if SOC_LP_I2C_SUPPORTED
#include "hal/rtc_io_ll.h"
#include "driver/rtc_io.h"
#include "soc/rtc_io_channel.h"
#include "driver/lp_io.h"
#endif
#if I2C_USE_RETENTION_LINK
#include "esp_private/sleep_retention.h"
#endif
static const char *TAG = "i2c.common";
typedef struct i2c_platform_t {
_lock_t mutex; // platform level mutex lock.
i2c_bus_handle_t buses[SOC_I2C_NUM]; // array of I2C bus instances.
uint32_t count[SOC_I2C_NUM]; // reference count used to protect group install/uninstall.
} i2c_platform_t;
static i2c_platform_t s_i2c_platform = {}; // singleton platform
#if I2C_USE_RETENTION_LINK
static esp_err_t s_i2c_sleep_retention_init(void *arg)
{
i2c_bus_t *bus = (i2c_bus_t *)arg;
i2c_port_num_t port_num = bus->port_num;
esp_err_t ret = sleep_retention_entries_create(i2c_regs_retention[port_num].link_list, i2c_regs_retention[port_num].link_num, REGDMA_LINK_PRI_I2C, i2c_regs_retention[port_num].module_id);
ESP_RETURN_ON_ERROR(ret, TAG, "failed to allocate mem for sleep retention");
return ret;
}
void i2c_create_retention_module(i2c_bus_handle_t handle)
{
i2c_port_num_t port_num = handle->port_num;
_lock_acquire(&s_i2c_platform.mutex);
if (handle->retention_link_created == false) {
if (sleep_retention_module_allocate(i2c_regs_retention[port_num].module_id) != ESP_OK) {
// even though the sleep retention module create failed, I2C driver should still work, so just warning here
ESP_LOGW(TAG, "create retention module failed, power domain can't turn off");
} else {
handle->retention_link_created = true;
}
}
_lock_release(&s_i2c_platform.mutex);
}
#endif
static esp_err_t s_i2c_bus_handle_acquire(i2c_port_num_t port_num, i2c_bus_handle_t *i2c_new_bus, i2c_bus_mode_t mode)
{
#if CONFIG_I2C_ENABLE_DEBUG_LOG
esp_log_level_set(TAG, ESP_LOG_DEBUG);
#endif
bool new_bus = false;
i2c_bus_t *bus = NULL;
esp_err_t ret = ESP_OK;
if (!s_i2c_platform.buses[port_num]) {
new_bus = true;
bus = heap_caps_calloc(1, sizeof(i2c_bus_t), I2C_MEM_ALLOC_CAPS);
if (bus) {
s_i2c_platform.buses[port_num] = bus;
bus->port_num = port_num;
bus->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
bus->bus_mode = mode;
bus->is_lp_i2c = (bus->port_num < SOC_HP_I2C_NUM) ? false : true;
#if I2C_USE_RETENTION_LINK
if (bus->is_lp_i2c == false) {
sleep_retention_module_init_param_t init_param = {
.cbs = { .create = { .handle = s_i2c_sleep_retention_init, .arg = (void *)bus } }
};
esp_err_t err = sleep_retention_module_init(i2c_regs_retention[port_num].module_id, &init_param);
if (err != ESP_OK) {
ESP_LOGW(TAG, "init sleep retention failed on bus %d, power domain may be turned off during sleep", port_num);
}
} else {
ESP_LOGW(TAG, "Detected PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP is enabled while LP_I2C is used. Sleep retention is not supported on LP I2C. Please use it properly");
}
#endif
// Enable the I2C module
if (!bus->is_lp_i2c) {
I2C_RCC_ATOMIC() {
i2c_ll_enable_bus_clock(bus->port_num, true);
i2c_ll_reset_register(bus->port_num);
}
}
#if SOC_LP_I2C_SUPPORTED
else {
LP_I2C_BUS_CLK_ATOMIC() {
lp_i2c_ll_enable_bus_clock(bus->port_num - SOC_HP_I2C_NUM, true);
lp_i2c_ll_reset_register(bus->port_num - SOC_HP_I2C_NUM);
}
}
#endif
I2C_CLOCK_SRC_ATOMIC() {
i2c_hal_init(&bus->hal, port_num);
}
}
} else {
ESP_LOGE(TAG, "I2C bus id(%d) has already been acquired", port_num);
bus = s_i2c_platform.buses[port_num];
ret = ESP_ERR_INVALID_STATE;
}
if (bus) {
s_i2c_platform.count[port_num]++;
}
if (new_bus) {
ESP_LOGD(TAG, "new bus(%d) at %p", port_num, bus);
}
*i2c_new_bus = bus;
return ret;
}
bool i2c_bus_occupied(i2c_port_num_t port_num)
{
return s_i2c_platform.buses[port_num] != NULL;
}
esp_err_t i2c_acquire_bus_handle(i2c_port_num_t port_num, i2c_bus_handle_t *i2c_new_bus, i2c_bus_mode_t mode)
{
bool bus_occupied = false;
bool bus_found = false;
esp_err_t ret = ESP_OK;
_lock_acquire(&s_i2c_platform.mutex);
if (port_num == -1) {
for (int i = 0; i < SOC_HP_I2C_NUM; i++) {
bus_occupied = i2c_bus_occupied(i);
if (bus_occupied == false) {
ret = s_i2c_bus_handle_acquire(i, i2c_new_bus, mode);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "acquire bus failed");
_lock_release(&s_i2c_platform.mutex);
return ret;
}
bus_found = true;
port_num = i;
break;
}
}
ESP_RETURN_ON_FALSE((bus_found == true), ESP_ERR_NOT_FOUND, TAG, "acquire bus failed, no free bus");
} else {
ret = s_i2c_bus_handle_acquire(port_num, i2c_new_bus, mode);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "acquire bus failed");
}
}
_lock_release(&s_i2c_platform.mutex);
return ret;
}
esp_err_t i2c_release_bus_handle(i2c_bus_handle_t i2c_bus)
{
int port_num = i2c_bus->port_num;
i2c_clock_source_t clk_src = i2c_bus->clk_src;
bool do_deinitialize = false;
_lock_acquire(&s_i2c_platform.mutex);
if (s_i2c_platform.buses[port_num]) {
s_i2c_platform.count[port_num]--;
if (s_i2c_platform.count[port_num] == 0) {
do_deinitialize = true;
s_i2c_platform.buses[port_num] = NULL;
#if I2C_USE_RETENTION_LINK
if (i2c_bus->is_lp_i2c == false) {
if (i2c_bus->retention_link_created) {
sleep_retention_module_free(i2c_regs_retention[port_num].module_id);
}
sleep_retention_module_deinit(i2c_regs_retention[port_num].module_id);
}
#endif
if (i2c_bus->intr_handle) {
ESP_RETURN_ON_ERROR(esp_intr_free(i2c_bus->intr_handle), TAG, "delete interrupt service failed");
}
if (i2c_bus->pm_lock) {
ESP_RETURN_ON_ERROR(esp_pm_lock_delete(i2c_bus->pm_lock), TAG, "delete pm_lock failed");
}
// Disable I2C module
if (!i2c_bus->is_lp_i2c) {
I2C_RCC_ATOMIC() {
i2c_ll_enable_bus_clock(port_num, false);
}
}
#if SOC_LP_I2C_SUPPORTED
else {
LP_I2C_BUS_CLK_ATOMIC() {
lp_i2c_ll_enable_bus_clock(port_num - SOC_HP_I2C_NUM, false);
}
}
#endif
free(i2c_bus);
}
}
_lock_release(&s_i2c_platform.mutex);
switch (clk_src) {
#if SOC_I2C_SUPPORT_RTC
case I2C_CLK_SRC_RC_FAST:
periph_rtc_dig_clk8m_disable();
break;
#endif // SOC_I2C_SUPPORT_RTC
default:
break;
}
if (do_deinitialize) {
ESP_LOGD(TAG, "delete bus %d", port_num);
}
ESP_RETURN_ON_FALSE(s_i2c_platform.count[port_num] == 0, ESP_ERR_INVALID_STATE, TAG, "Bus not freed entirely");
return ESP_OK;
}
esp_err_t i2c_select_periph_clock(i2c_bus_handle_t handle, soc_module_clk_t clk_src)
{
esp_err_t ret = ESP_OK;
ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "I2C empty controller handle");
uint32_t periph_src_clk_hz = 0;
bool clock_selection_conflict = 0;
portENTER_CRITICAL(&handle->spinlock);
if (handle->clk_src == 0) {
handle->clk_src = clk_src;
} else {
clock_selection_conflict = (handle->clk_src != clk_src);
}
portEXIT_CRITICAL(&handle->spinlock);
ESP_RETURN_ON_FALSE(!clock_selection_conflict, ESP_ERR_INVALID_STATE, TAG,
"group clock conflict, already is %d but attempt to %d", handle->clk_src, clk_src);
// TODO: [clk_tree] to use a generic clock enable/disable or acquire/release function for all clock source
#if SOC_I2C_SUPPORT_RTC
if (clk_src == (soc_module_clk_t)I2C_CLK_SRC_RC_FAST) {
// RC_FAST clock is not enabled automatically on start up, we enable it here manually.
// Note there's a ref count in the enable/disable function, we must call them in pair in the driver.
periph_rtc_dig_clk8m_enable();
}
#endif // SOC_I2C_SUPPORT_RTC
ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz(clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &periph_src_clk_hz), TAG, "i2c get clock frequency error");
handle->clk_src_freq_hz = periph_src_clk_hz;
#if CONFIG_PM_ENABLE
bool need_pm_lock = true;
// to make the I2C work reliable, the source clock must stay alive and unchanged
// driver will create different pm lock for that purpose, according to different clock source
esp_pm_lock_type_t pm_lock_type = ESP_PM_NO_LIGHT_SLEEP;
#if SOC_I2C_SUPPORT_RTC
if (clk_src == (soc_module_clk_t)I2C_CLK_SRC_RC_FAST) {
// I2C use fifo, which connected to APB, so we cannot use I2C either when in light sleep.
pm_lock_type = ESP_PM_NO_LIGHT_SLEEP;
}
#endif // SOC_I2C_SUPPORT_RTC
#if SOC_I2C_SUPPORT_APB
if (clk_src == (soc_module_clk_t)I2C_CLK_SRC_APB) {
// APB clock frequency can be changed during DFS
pm_lock_type = ESP_PM_APB_FREQ_MAX;
}
#endif // SOC_I2C_SUPPORT_APB
if (handle->is_lp_i2c) {
// Even for LP I2C, the clock will also be powered down when going into light sleep.
pm_lock_type = ESP_PM_NO_LIGHT_SLEEP;
}
if (need_pm_lock) {
sprintf(handle->pm_lock_name, "I2C_%d", handle->port_num); // e.g. PORT_0
ret = esp_pm_lock_create(pm_lock_type, 0, handle->pm_lock_name, &handle->pm_lock);
ESP_RETURN_ON_ERROR(ret, TAG, "create pm lock failed");
}
#endif // CONFIG_PM_ENABLE
ESP_LOGD(TAG, "bus clock source frequency: %"PRIu32"hz", periph_src_clk_hz);
return ret;
}
static esp_err_t s_hp_i2c_pins_config(i2c_bus_handle_t handle)
{
int port_id = handle->port_num;
// SDA pin configurations
ESP_RETURN_ON_ERROR(gpio_set_level(handle->sda_num, 1), TAG, "i2c sda pin set level failed");
gpio_input_enable(handle->sda_num);
gpio_od_enable(handle->sda_num);
if (handle->pull_up_enable) {
gpio_pullup_en(handle->sda_num);
} else {
gpio_pullup_dis(handle->sda_num);
}
gpio_func_sel(handle->sda_num, PIN_FUNC_GPIO);
esp_rom_gpio_connect_out_signal(handle->sda_num, i2c_periph_signal[port_id].sda_out_sig, 0, 0);
esp_rom_gpio_connect_in_signal(handle->sda_num, i2c_periph_signal[port_id].sda_in_sig, 0);
// SCL pin configurations
ESP_RETURN_ON_ERROR(gpio_set_level(handle->scl_num, 1), TAG, "i2c scl pin set level failed");
gpio_input_enable(handle->scl_num);
gpio_od_enable(handle->scl_num);
if (handle->pull_up_enable) {
gpio_pullup_en(handle->scl_num);
} else {
gpio_pullup_dis(handle->scl_num);
}
gpio_func_sel(handle->scl_num, PIN_FUNC_GPIO);
esp_rom_gpio_connect_out_signal(handle->scl_num, i2c_periph_signal[port_id].scl_out_sig, 0, 0);
esp_rom_gpio_connect_in_signal(handle->scl_num, i2c_periph_signal[port_id].scl_in_sig, 0);
return ESP_OK;
}
#if SOC_LP_I2C_SUPPORTED
static esp_err_t s_lp_i2c_pins_config(i2c_bus_handle_t handle)
{
ESP_RETURN_ON_ERROR(!rtc_gpio_is_valid_gpio(handle->sda_num), TAG, "LP I2C SDA GPIO invalid");
ESP_RETURN_ON_ERROR(!rtc_gpio_is_valid_gpio(handle->scl_num), TAG, "LP I2C SCL GPIO invalid");
#if !SOC_LP_GPIO_MATRIX_SUPPORTED
/* Verify that the SDA and SCL line belong to the LP IO Mux I2C function group */
ESP_RETURN_ON_FALSE((handle->sda_num == LP_I2C_SDA_IOMUX_PAD), ESP_ERR_INVALID_ARG, TAG, LP_I2C_SDA_PIN_ERR_LOG);
ESP_RETURN_ON_FALSE((handle->scl_num == LP_I2C_SCL_IOMUX_PAD), ESP_ERR_INVALID_ARG, TAG, LP_I2C_SCL_PIN_ERR_LOG);
#endif /* !SOC_LP_GPIO_MATRIX_SUPPORTED */
int port_id = handle->port_num;
rtc_gpio_init(handle->sda_num);
rtc_gpio_set_direction(handle->sda_num, RTC_GPIO_MODE_INPUT_OUTPUT_OD);
rtc_gpio_pulldown_dis(handle->sda_num);
if (handle->pull_up_enable) {
rtc_gpio_pullup_en(handle->sda_num);
} else {
rtc_gpio_pullup_dis(handle->sda_num);
}
#if !SOC_LP_GPIO_MATRIX_SUPPORTED
rtc_gpio_iomux_func_sel(handle->sda_num, i2c_periph_signal[port_id].iomux_func);
#else
lp_gpio_connect_out_signal(handle->sda_num, i2c_periph_signal[port_id].sda_out_sig, 0, 0);
lp_gpio_connect_in_signal(handle->sda_num, i2c_periph_signal[port_id].sda_in_sig, 0);
#endif
rtc_gpio_init(handle->scl_num);
rtc_gpio_set_direction(handle->scl_num, RTC_GPIO_MODE_INPUT_OUTPUT_OD);
rtc_gpio_pulldown_dis(handle->scl_num);
if (handle->pull_up_enable) {
rtc_gpio_pullup_en(handle->scl_num);
} else {
rtc_gpio_pullup_dis(handle->scl_num);
}
#if !SOC_LP_GPIO_MATRIX_SUPPORTED
rtc_gpio_iomux_func_sel(handle->scl_num, i2c_periph_signal[port_id].iomux_func);
#else
lp_gpio_connect_out_signal(handle->scl_num, i2c_periph_signal[port_id].scl_out_sig, 0, 0);
lp_gpio_connect_in_signal(handle->scl_num, i2c_periph_signal[port_id].scl_in_sig, 0);
#endif
return ESP_OK;
}
#endif // SOC_LP_I2C_SUPPORTED
esp_err_t i2c_common_set_pins(i2c_bus_handle_t handle)
{
esp_err_t ret = ESP_OK;
if (handle->is_lp_i2c == false) {
ESP_RETURN_ON_ERROR(s_hp_i2c_pins_config(handle), TAG, "config i2c pins failed");
}
#if SOC_LP_I2C_SUPPORTED
else {
ESP_RETURN_ON_ERROR(s_lp_i2c_pins_config(handle), TAG, "config i2c lp pins failed");
}
#endif
return ret;
}
esp_err_t i2c_common_deinit_pins(i2c_bus_handle_t handle)
{
int port_id = handle->port_num;
if (handle->is_lp_i2c == false) {
ESP_RETURN_ON_ERROR(gpio_output_disable(handle->sda_num), TAG, "disable i2c pins failed");
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, i2c_periph_signal[port_id].sda_in_sig, 0);
ESP_RETURN_ON_ERROR(gpio_output_disable(handle->scl_num), TAG, "disable i2c pins failed");
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, i2c_periph_signal[port_id].scl_in_sig, 0);
}
#if SOC_LP_I2C_SUPPORTED
else {
ESP_RETURN_ON_ERROR(rtc_gpio_deinit(handle->sda_num), TAG, "deinit rtc gpio failed");
ESP_RETURN_ON_ERROR(rtc_gpio_deinit(handle->scl_num), TAG, "deinit rtc gpio failed");
#if SOC_LP_GPIO_MATRIX_SUPPORTED
lp_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, i2c_periph_signal[port_id].scl_in_sig, 0);
lp_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, i2c_periph_signal[port_id].sda_in_sig, 0);
#endif
}
#endif
return ESP_OK;
}