You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
247 lines
7.3 KiB
247 lines
7.3 KiB
/* |
|
* Copyright (c) 2025 Realtek, SIBG-SD7 |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#include <zephyr/kernel.h> |
|
#include <zephyr/devicetree.h> |
|
#include <zephyr/drivers/pinctrl.h> |
|
#include <zephyr/drivers/gpio.h> |
|
#include <zephyr/drivers/clock_control.h> |
|
#include <zephyr/drivers/clock_control/clock_control_rts5912.h> |
|
#include <zephyr/dt-bindings/gpio/realtek-gpio.h> |
|
#include <reg/reg_gpio.h> |
|
#include "i2c_realtek_rts5912.h" |
|
#include <zephyr/logging/log.h> |
|
|
|
LOG_MODULE_REGISTER(i2c_rts5912, CONFIG_I2C_LOG_LEVEL); |
|
|
|
BUILD_ASSERT(CONFIG_I2C_RTS5912_INIT_PRIORITY > CONFIG_I2C_INIT_PRIORITY, |
|
"The I2C Realtek RTS5912 driver must be initialized after the I2C DW driver"); |
|
|
|
/* i2c_dw has define the DT_DRV_COMPAT at i2c_dw.h |
|
* so, need to undefine and define our own DT_DRV_COMPAT |
|
*/ |
|
#ifdef DT_DRV_COMPAT |
|
#undef DT_DRV_COMPAT |
|
#define DT_DRV_COMPAT realtek_rts5912_i2c |
|
#endif |
|
|
|
#define RECOVERY_TIME 30 /* in ms */ |
|
|
|
struct i2c_rts5912_config { |
|
const struct device *clk_dev; |
|
struct rts5912_sccon_subsys sccon_cfg; |
|
const struct device *dw_i2c_dev; |
|
/* SCL GPIO cells */ |
|
struct gpio_dt_spec scl_gpios; |
|
/* SDA GPIO cells */ |
|
struct gpio_dt_spec sda_gpios; |
|
}; |
|
|
|
static inline uint32_t get_regs(const struct device *dev) |
|
{ |
|
return (uint32_t)DEVICE_MMIO_GET(dev); |
|
} |
|
|
|
static int i2c_rts5912_recover_bus(const struct device *dev) |
|
{ |
|
struct i2c_rts5912_config const *config = dev->config; |
|
|
|
/* DW configure data */ |
|
struct device const *dw_i2c_dev = config->dw_i2c_dev; |
|
const struct i2c_dw_rom_config *const rom = dw_i2c_dev->config; |
|
struct i2c_dw_dev_config *bus = dw_i2c_dev->data; |
|
uint32_t reg_base = get_regs(dw_i2c_dev); |
|
|
|
uint32_t value; |
|
uint32_t start; |
|
int ret = 0; |
|
gpio_flags_t flags; |
|
int i; |
|
|
|
LOG_DBG("starting bus recover"); |
|
/* disable all interrupt mask */ |
|
write_intr_mask(DW_DISABLE_ALL_I2C_INT, reg_base); |
|
/* enable controller to make sure function works */ |
|
set_bit_enable_en(reg_base); |
|
|
|
if (bus->state & I2C_DW_SDA_STUCK) { |
|
/* |
|
* initiate the SDA Recovery Mechanism |
|
* (that is, send at most 9 SCL clocks and STOP to release the |
|
* SDA line) and then this bit gets auto clear |
|
*/ |
|
LOG_DBG("CLK Recovery Start"); |
|
/* initiate the Master Clock Reset */ |
|
start = k_uptime_get_32(); |
|
set_bit_enable_clk_reset(reg_base); |
|
while (test_bit_enable_clk_reset(reg_base) && |
|
(k_uptime_get_32() - start < RECOVERY_TIME)) { |
|
; |
|
} |
|
/* check if SCL bus clk is not reset */ |
|
if (test_bit_enable_clk_reset(reg_base)) { |
|
LOG_ERR("ERROR: CLK recovery Fail"); |
|
ret = -1; |
|
} else { |
|
LOG_DBG("CLK Recovery Success"); |
|
} |
|
|
|
LOG_DBG("SDA Recovery Start"); |
|
start = k_uptime_get_32(); |
|
set_bit_enable_sdarecov(reg_base); |
|
while (test_bit_enable_sdarecov(reg_base) && |
|
(k_uptime_get_32() - start < RECOVERY_TIME)) { |
|
; |
|
} |
|
/* Check if bus is not clear */ |
|
if (test_bit_status_sdanotrecov(reg_base)) { |
|
LOG_ERR("ERROR: SDA Recovery Fail"); |
|
ret = -1; |
|
} else { |
|
LOG_DBG("SDA Recovery Success"); |
|
} |
|
} else if (bus->state & I2C_DW_SCL_STUCK) { |
|
/* the controller initiates the transfer abort */ |
|
LOG_DBG("ABORT transfer"); |
|
start = k_uptime_get_32(); |
|
set_bit_enable_abort(reg_base); |
|
while (test_bit_enable_abort(reg_base) && |
|
(k_uptime_get_32() - start < RECOVERY_TIME)) { |
|
; |
|
} |
|
/* check if Controller is not abort */ |
|
if (test_bit_enable_abort(reg_base)) { |
|
LOG_ERR("ERROR: ABORT Fail!"); |
|
ret = -1; |
|
} else { |
|
LOG_DBG("ABORT success"); |
|
} |
|
} |
|
value = read_clr_intr(reg_base); |
|
value = read_clr_tx_abrt(reg_base); |
|
/* disable controller */ |
|
clear_bit_enable_en(reg_base); |
|
|
|
/* Input type selection */ |
|
flags = GPIO_INPUT | RTS5912_GPIO_SCHEN; |
|
/* Set SCL of I2C as GPIO pin */ |
|
gpio_pin_configure_dt(&config->scl_gpios, flags); |
|
/* Get SCL GPIO status */ |
|
gpio_pin_get_dt(&config->scl_gpios); |
|
|
|
/* Output type selection */ |
|
flags = GPIO_OUTPUT_HIGH | RTS5912_GPIO_SCHEN; |
|
/* Set SCL of I2C as GPIO pin */ |
|
gpio_pin_configure_dt(&config->scl_gpios, flags); |
|
/* Set SDA of I2C as GPIO pin */ |
|
gpio_pin_configure_dt(&config->sda_gpios, flags); |
|
|
|
/* send a ACK */ |
|
gpio_pin_set_dt(&config->sda_gpios, 0); |
|
k_busy_wait(10); |
|
gpio_pin_set_dt(&config->scl_gpios, 0); |
|
k_busy_wait(10); |
|
gpio_pin_set_dt(&config->sda_gpios, 1); |
|
k_busy_wait(10); |
|
|
|
/* 9 cycles of SCL with SDA held high */ |
|
for (i = 0; i < 9; i++) { |
|
gpio_pin_set_dt(&config->scl_gpios, 1); |
|
k_busy_wait(50); |
|
gpio_pin_set_dt(&config->scl_gpios, 0); |
|
k_busy_wait(50); |
|
} |
|
|
|
/* send a stop bit */ |
|
gpio_pin_set_dt(&config->sda_gpios, 0); |
|
k_busy_wait(10); |
|
gpio_pin_set_dt(&config->scl_gpios, 1); |
|
k_busy_wait(10); |
|
gpio_pin_set_dt(&config->sda_gpios, 1); |
|
k_busy_wait(10); |
|
|
|
/* Set GPIO back to I2C alternate function */ |
|
ret = pinctrl_apply_state(rom->pcfg, PINCTRL_STATE_DEFAULT); |
|
if (ret < 0) { |
|
LOG_ERR("Failed to configure I2C pins"); |
|
return ret; |
|
} |
|
|
|
/* enable controller */ |
|
set_bit_enable_en(reg_base); |
|
|
|
start = k_uptime_get_32(); |
|
set_bit_enable_abort(reg_base); |
|
while (test_bit_enable_abort(reg_base) && (k_uptime_get_32() - start < RECOVERY_TIME)) { |
|
; |
|
} |
|
if (test_bit_enable_abort(reg_base)) { |
|
LOG_ERR("ERROR: ABORT Fail!"); |
|
ret = -1; |
|
} else { |
|
LOG_DBG("ABORT success"); |
|
} |
|
/* disable controller */ |
|
clear_bit_enable_en(reg_base); |
|
|
|
if (ret) { |
|
LOG_ERR("ERROR: Bus Recover Fail, a slave device may be faulty or require a power " |
|
"reset"); |
|
} else { |
|
LOG_DBG("BUS Recover success"); |
|
} |
|
return ret; |
|
} |
|
|
|
static int i2c_rts5912_initialize(const struct device *dev) |
|
{ |
|
const struct i2c_rts5912_config *const config = dev->config; |
|
int ret = 0; |
|
|
|
/* Register our recovery routine with the DW I2C driver. */ |
|
if (!device_is_ready(config->dw_i2c_dev)) { |
|
LOG_ERR("DW i2c not ready"); |
|
return -ENODEV; |
|
} |
|
i2c_dw_register_recover_bus_cb(config->dw_i2c_dev, i2c_rts5912_recover_bus, dev); |
|
|
|
if (!device_is_ready(config->clk_dev)) { |
|
LOG_ERR("clock source not ready"); |
|
return -ENODEV; |
|
} |
|
ret = clock_control_on(config->clk_dev, (clock_control_subsys_t)&config->sccon_cfg); |
|
if (ret != 0) { |
|
LOG_ERR("enable i2c[%s] clock source power fail", dev->name); |
|
return ret; |
|
} |
|
|
|
uint32_t reg_base = get_regs(config->dw_i2c_dev); |
|
/* clear enable register */ |
|
clear_bit_enable_en(reg_base); |
|
/* disable block mode */ |
|
clear_bit_enable_block(reg_base); |
|
|
|
return ret; |
|
} |
|
|
|
#define DEV_CONFIG_CLK_DEV_INIT(n) \ |
|
.clk_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ |
|
.sccon_cfg = { \ |
|
.clk_grp = DT_INST_CLOCKS_CELL(n, clk_grp), \ |
|
.clk_idx = DT_INST_CLOCKS_CELL(n, clk_idx), \ |
|
} |
|
|
|
#define I2C_DEVICE_INIT_RTS5912(n) \ |
|
static const struct i2c_rts5912_config i2c_rts5912_##n##_config = { \ |
|
DEV_CONFIG_CLK_DEV_INIT(n), \ |
|
.dw_i2c_dev = DEVICE_DT_GET(DT_INST_PHANDLE(n, dw_i2c_dev)), \ |
|
.scl_gpios = GPIO_DT_SPEC_INST_GET(n, scl_gpios), \ |
|
.sda_gpios = GPIO_DT_SPEC_INST_GET(n, sda_gpios)}; \ |
|
I2C_DEVICE_DT_INST_DEFINE(n, i2c_rts5912_initialize, NULL, NULL, \ |
|
&i2c_rts5912_##n##_config, POST_KERNEL, \ |
|
CONFIG_I2C_RTS5912_INIT_PRIORITY, NULL); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(I2C_DEVICE_INIT_RTS5912)
|
|
|