|  | /* | 
|  | * Copyright (c) 2018 Intel Corporation | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT altr_nios2_i2c | 
|  |  | 
|  | #include <errno.h> | 
|  | #include <drivers/i2c.h> | 
|  | #include <soc.h> | 
|  | #include <sys/util.h> | 
|  | #include <altera_common.h> | 
|  | #include "altera_avalon_i2c.h" | 
|  |  | 
|  | #define LOG_LEVEL CONFIG_I2C_LOG_LEVEL | 
|  | #include <logging/log.h> | 
|  | LOG_MODULE_REGISTER(i2c_nios2); | 
|  |  | 
|  | #define NIOS2_I2C_TIMEOUT_USEC		1000 | 
|  |  | 
|  | #define DEV_CFG(dev) \ | 
|  | ((struct i2c_nios2_config *)(dev)->config) | 
|  |  | 
|  | struct i2c_nios2_config { | 
|  | ALT_AVALON_I2C_DEV_t	i2c_dev; | 
|  | IRQ_DATA_t		irq_data; | 
|  | struct k_sem		sem_lock; | 
|  | }; | 
|  |  | 
|  | static int i2c_nios2_configure(const struct device *dev, uint32_t dev_config) | 
|  | { | 
|  | struct i2c_nios2_config *config = DEV_CFG(dev); | 
|  | int32_t rc = 0; | 
|  |  | 
|  | k_sem_take(&config->sem_lock, K_FOREVER); | 
|  | if (!(I2C_MODE_MASTER & dev_config)) { | 
|  | LOG_ERR("i2c config mode error\n"); | 
|  | rc = -EINVAL; | 
|  | goto i2c_cfg_err; | 
|  | } | 
|  |  | 
|  | if (I2C_ADDR_10_BITS & dev_config) { | 
|  | LOG_ERR("i2c config addressing error\n"); | 
|  | rc = -EINVAL; | 
|  | goto i2c_cfg_err; | 
|  | } | 
|  |  | 
|  | if (I2C_SPEED_GET(dev_config) != I2C_SPEED_STANDARD) { | 
|  | LOG_ERR("i2c config speed error\n"); | 
|  | rc = -EINVAL; | 
|  | goto i2c_cfg_err; | 
|  | } | 
|  |  | 
|  | alt_avalon_i2c_init(&config->i2c_dev); | 
|  |  | 
|  | i2c_cfg_err: | 
|  | k_sem_give(&config->sem_lock); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static int i2c_nios2_transfer(const struct device *dev, struct i2c_msg *msgs, | 
|  | uint8_t num_msgs, uint16_t addr) | 
|  | { | 
|  | struct i2c_nios2_config *config = DEV_CFG(dev); | 
|  | ALT_AVALON_I2C_STATUS_CODE status; | 
|  | uint32_t restart, stop; | 
|  | int32_t i, timeout, rc = 0; | 
|  |  | 
|  | k_sem_take(&config->sem_lock, K_FOREVER); | 
|  | /* register the optional interrupt callback */ | 
|  | alt_avalon_i2c_register_optional_irq_handler( | 
|  | &config->i2c_dev, &config->irq_data); | 
|  |  | 
|  | /* Iterate over all the messages */ | 
|  | for (i = 0; i < num_msgs; i++) { | 
|  |  | 
|  | /* convert restart flag */ | 
|  | if (msgs->flags & I2C_MSG_RESTART) { | 
|  | restart = ALT_AVALON_I2C_RESTART; | 
|  | } else { | 
|  | restart = ALT_AVALON_I2C_NO_RESTART; | 
|  | } | 
|  |  | 
|  | /* convert stop flag */ | 
|  | if (msgs->flags & I2C_MSG_STOP) { | 
|  | stop = ALT_AVALON_I2C_STOP; | 
|  | } else { | 
|  | stop = ALT_AVALON_I2C_NO_STOP; | 
|  | } | 
|  |  | 
|  | /* Set the slave device address */ | 
|  | alt_avalon_i2c_master_target_set(&config->i2c_dev, addr); | 
|  |  | 
|  | /* Start the transfer */ | 
|  | if (msgs->flags & I2C_MSG_READ) { | 
|  | status = alt_avalon_i2c_master_receive_using_interrupts( | 
|  | &config->i2c_dev, | 
|  | msgs->buf, msgs->len, | 
|  | restart, stop); | 
|  | } else { | 
|  | status = alt_avalon_i2c_master_transmit_using_interrupts | 
|  | (&config->i2c_dev, | 
|  | msgs->buf, msgs->len, | 
|  | restart, stop); | 
|  | } | 
|  |  | 
|  | /* Return an error if the transfer didn't | 
|  | * start successfully e.g., if the bus was busy | 
|  | */ | 
|  | if (status != ALT_AVALON_I2C_SUCCESS) { | 
|  | LOG_ERR("i2c transfer error %lu\n", status); | 
|  | rc = -EIO; | 
|  | goto i2c_transfer_err; | 
|  | } | 
|  |  | 
|  | timeout = NIOS2_I2C_TIMEOUT_USEC; | 
|  | while (timeout) { | 
|  | k_busy_wait(1); | 
|  | status = alt_avalon_i2c_interrupt_transaction_status( | 
|  | &config->i2c_dev); | 
|  | if (status == ALT_AVALON_I2C_SUCCESS) { | 
|  | break; | 
|  | } | 
|  | timeout--; | 
|  | } | 
|  |  | 
|  | if (timeout <= 0) { | 
|  | LOG_ERR("i2c busy or timeout error %lu\n", status); | 
|  | rc = -EIO; | 
|  | goto i2c_transfer_err; | 
|  | } | 
|  |  | 
|  | /* move to the next message */ | 
|  | msgs++; | 
|  | } | 
|  |  | 
|  | i2c_transfer_err: | 
|  | alt_avalon_i2c_disable(&config->i2c_dev); | 
|  | k_sem_give(&config->sem_lock); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static void i2c_nios2_isr(const struct device *dev) | 
|  | { | 
|  | struct i2c_nios2_config *config = DEV_CFG(dev); | 
|  |  | 
|  | /* Call Altera HAL driver ISR */ | 
|  | alt_handle_irq(&config->i2c_dev, DT_INST_IRQN(0)); | 
|  | } | 
|  |  | 
|  | static int i2c_nios2_init(const struct device *dev); | 
|  |  | 
|  | static struct i2c_driver_api i2c_nios2_driver_api = { | 
|  | .configure = i2c_nios2_configure, | 
|  | .transfer = i2c_nios2_transfer, | 
|  | }; | 
|  |  | 
|  | static struct i2c_nios2_config i2c_nios2_cfg = { | 
|  | .i2c_dev = { | 
|  | .i2c_base = (alt_u32 *)DT_INST_REG_ADDR(0), | 
|  | .irq_controller_ID = I2C_0_IRQ_INTERRUPT_CONTROLLER_ID, | 
|  | .irq_ID = DT_INST_IRQN(0), | 
|  | .ip_freq_in_hz = DT_INST_PROP(0, clock_frequency), | 
|  | }, | 
|  | }; | 
|  |  | 
|  | I2C_DEVICE_DT_INST_DEFINE(0, i2c_nios2_init, NULL, | 
|  | NULL, &i2c_nios2_cfg, | 
|  | POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, | 
|  | &i2c_nios2_driver_api); | 
|  |  | 
|  | static int i2c_nios2_init(const struct device *dev) | 
|  | { | 
|  | struct i2c_nios2_config *config = DEV_CFG(dev); | 
|  | int rc; | 
|  |  | 
|  | /* initialize semaphore */ | 
|  | k_sem_init(&config->sem_lock, 1, 1); | 
|  |  | 
|  | rc = i2c_nios2_configure(dev, | 
|  | I2C_MODE_MASTER | | 
|  | I2C_SPEED_SET(I2C_SPEED_STANDARD)); | 
|  | if (rc) { | 
|  | LOG_ERR("i2c configure failed %d\n", rc); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | /* clear ISR register content */ | 
|  | alt_avalon_i2c_int_clear(&config->i2c_dev, | 
|  | ALT_AVALON_I2C_ISR_ALL_CLEARABLE_INTS_MSK); | 
|  | IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), | 
|  | i2c_nios2_isr, DEVICE_DT_INST_GET(0), 0); | 
|  | irq_enable(DT_INST_IRQN(0)); | 
|  | return 0; | 
|  | } |