drivers: i2c: rv32m1: add I2C driver for the RV32M1 RI5CY SoC

Add driver and device tree binding for the Low Power Inter-Integrated
Circuit (LPI2C) controllers found in the RV32M1 RI5CY SoC.

Signed-off-by: Henrik Brix Andersen <henrik@brixandersen.dk>
This commit is contained in:
Henrik Brix Andersen 2019-04-01 21:09:44 +02:00 committed by Maureen Helm
commit 8a4dbb5b03
10 changed files with 423 additions and 0 deletions

View file

@ -140,6 +140,7 @@
/drivers/usb/ @jfischer-phytec-iot @finikorg /drivers/usb/ @jfischer-phytec-iot @finikorg
/drivers/usb/device/usb_dc_stm32.c @ydamigos @loicpoulain /drivers/usb/device/usb_dc_stm32.c @ydamigos @loicpoulain
/drivers/i2c/i2c_ll_stm32* @ldts @ydamigos /drivers/i2c/i2c_ll_stm32* @ldts @ydamigos
/drivers/i2c/i2c_rv32m1_lpi2c* @henrikbrixandersen
/drivers/wifi/ @jukkar @tbursztyka @pfalcon /drivers/wifi/ @jukkar @tbursztyka @pfalcon
/drivers/wifi/eswifi/ @loicpoulain /drivers/wifi/eswifi/ @loicpoulain
/dts/arm/st/ @erwango /dts/arm/st/ @erwango

View file

@ -20,6 +20,7 @@ zephyr_library_sources_ifdef(CONFIG_I2C_SBCON i2c_sbcon.c)
zephyr_library_sources_ifdef(CONFIG_I2C_SIFIVE i2c_sifive.c) zephyr_library_sources_ifdef(CONFIG_I2C_SIFIVE i2c_sifive.c)
zephyr_library_sources_ifdef(CONFIG_I2C_NIOS2 i2c_nios2.c) zephyr_library_sources_ifdef(CONFIG_I2C_NIOS2 i2c_nios2.c)
zephyr_library_sources_ifdef(CONFIG_I2C_GECKO i2c_gecko.c) zephyr_library_sources_ifdef(CONFIG_I2C_GECKO i2c_gecko.c)
zephyr_library_sources_ifdef(CONFIG_I2C_RV32M1_LPI2C i2c_rv32m1_lpi2c.c)
zephyr_library_sources_ifdef(CONFIG_I2C_STM32_V1 zephyr_library_sources_ifdef(CONFIG_I2C_STM32_V1
i2c_ll_stm32_v1.c i2c_ll_stm32_v1.c

View file

@ -300,4 +300,11 @@ config I2C_NIOS2
help help
Enable the Nios-II I2C driver. Enable the Nios-II I2C driver.
config I2C_RV32M1_LPI2C
bool "RV32M1 LPI2C driver"
depends on HAS_RV32M1_LPI2C && CLOCK_CONTROL
select HAS_DTS_I2C
help
Enable the RV32M1 LPI2C driver.
endif # I2C endif # I2C

View file

@ -0,0 +1,313 @@
/*
* Copyright (c) 2019, Henrik Brix Andersen <henrik@brixandersen.dk>
*
* Based on the i2c_mcux_lpi2c.c driver, which is:
* Copyright (c) 2016 Freescale Semiconductor, Inc.
* Copyright (c) 2019, NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <i2c.h>
#include <clock_control.h>
#include <fsl_lpi2c.h>
#include <logging/log.h>
LOG_MODULE_REGISTER(rv32m1_lpi2c);
#include "i2c-priv.h"
struct rv32m1_lpi2c_config {
LPI2C_Type *base;
char *clock_controller;
clock_control_subsys_t clock_subsys;
clock_ip_name_t clock_ip_name;
u32_t clock_ip_src;
u32_t bitrate;
void (*irq_config_func)(struct device *dev);
};
struct rv32m1_lpi2c_data {
lpi2c_master_handle_t handle;
struct k_sem transfer_sync;
struct k_sem completion_sync;
status_t completion_status;
};
static int rv32m1_lpi2c_configure(struct device *dev, u32_t dev_config)
{
const struct rv32m1_lpi2c_config *config = dev->config->config_info;
struct device *clk;
u32_t baudrate;
u32_t clk_freq;
int err;
if (!(I2C_MODE_MASTER & dev_config)) {
/* Slave mode not supported - yet */
LOG_ERR("Slave mode not supported");
return -ENOTSUP;
}
if (I2C_ADDR_10_BITS & dev_config) {
/* FSL LPI2C driver only supports 7-bit addressing */
LOG_ERR("10 bit addressing not supported");
return -ENOTSUP;
}
switch (I2C_SPEED_GET(dev_config)) {
case I2C_SPEED_STANDARD:
baudrate = KHZ(100);
break;
case I2C_SPEED_FAST:
baudrate = KHZ(400);
break;
case I2C_SPEED_FAST_PLUS:
baudrate = MHZ(1);
break;
/* TODO: only if SCL pin implements current source pull-up */
/* case I2C_SPEED_HIGH: */
/* baudrate = KHZ(3400); */
/* break; */
/* TODO: ultra-fast requires pin_config setting */
/* case I2C_SPEED_ULTRA: */
/* baudrate = MHZ(5); */
/* break; */
default:
LOG_ERR("Unsupported speed");
return -ENOTSUP;
}
clk = device_get_binding(config->clock_controller);
if (!clk) {
LOG_ERR("Could not get clock controller '%s'",
config->clock_controller);
return -EINVAL;
}
err = clock_control_get_rate(clk, config->clock_subsys, &clk_freq);
if (err) {
LOG_ERR("Could not get clock frequency (err %d)", err);
return -EINVAL;
}
LPI2C_MasterSetBaudRate(config->base, clk_freq, baudrate);
return 0;
}
static void rv32m1_lpi2c_master_transfer_callback(LPI2C_Type *base,
lpi2c_master_handle_t *handle,
status_t completionStatus,
void *userData)
{
struct device *dev = userData;
struct rv32m1_lpi2c_data *data = dev->driver_data;
ARG_UNUSED(base);
ARG_UNUSED(handle);
data->completion_status = completionStatus;
k_sem_give(&data->completion_sync);
}
static u32_t rv32m1_lpi2c_convert_flags(int msg_flags)
{
u32_t flags = 0U;
if (!(msg_flags & I2C_MSG_STOP)) {
flags |= kLPI2C_TransferNoStopFlag;
}
if (msg_flags & I2C_MSG_RESTART) {
flags |= kLPI2C_TransferRepeatedStartFlag;
}
return flags;
}
static int rv32m1_lpi2c_transfer(struct device *dev, struct i2c_msg *msgs,
u8_t num_msgs, u16_t addr)
{
const struct rv32m1_lpi2c_config *config = dev->config->config_info;
struct rv32m1_lpi2c_data *data = dev->driver_data;
lpi2c_master_transfer_t transfer;
status_t status;
int ret = 0;
k_sem_take(&data->transfer_sync, K_FOREVER);
/* Iterate over all the messages */
for (int i = 0; i < num_msgs; i++) {
if (I2C_MSG_ADDR_10_BITS & msgs->flags) {
ret = -ENOTSUP;
goto out;
}
/* Initialize the transfer descriptor */
transfer.flags = rv32m1_lpi2c_convert_flags(msgs->flags);
/* Prevent the controller to send a start condition between
* messages, except if explicitly requested.
*/
if (i != 0 && !(msgs->flags & I2C_MSG_RESTART)) {
transfer.flags |= kLPI2C_TransferNoStartFlag;
}
transfer.slaveAddress = addr;
transfer.direction = (msgs->flags & I2C_MSG_READ)
? kLPI2C_Read : kLPI2C_Write;
transfer.subaddress = 0;
transfer.subaddressSize = 0;
transfer.data = msgs->buf;
transfer.dataSize = msgs->len;
/* Start the transfer */
status = LPI2C_MasterTransferNonBlocking(config->base,
&data->handle,
&transfer);
/* Return an error if the transfer didn't start successfully
* e.g., if the bus was busy
*/
if (status != kStatus_Success) {
LOG_DBG("Could not start transfer (status %d)", status);
ret = -EIO;
goto out;
}
/* Wait for the transfer to complete */
k_sem_take(&data->completion_sync, K_FOREVER);
/* Return an error if the transfer didn't complete
* successfully. e.g., nak, timeout, lost arbitration
*/
if (data->completion_status != kStatus_Success) {
LOG_DBG("Transfer failed (status %d)",
data->completion_status);
LPI2C_MasterTransferAbort(config->base, &data->handle);
ret = -EIO;
goto out;
}
/* Move to the next message */
msgs++;
}
out:
k_sem_give(&data->transfer_sync);
return ret;
}
static void rv32m1_lpi2c_isr(void *arg)
{
struct device *dev = (struct device *)arg;
const struct rv32m1_lpi2c_config *config = dev->config->config_info;
struct rv32m1_lpi2c_data *data = dev->driver_data;
LPI2C_MasterTransferHandleIRQ(config->base, &data->handle);
}
static int rv32m1_lpi2c_init(struct device *dev)
{
const struct rv32m1_lpi2c_config *config = dev->config->config_info;
struct rv32m1_lpi2c_data *data = dev->driver_data;
lpi2c_master_config_t master_config;
struct device *clk;
u32_t clk_freq, dev_cfg;
int err;
CLOCK_SetIpSrc(config->clock_ip_name, config->clock_ip_src);
clk = device_get_binding(config->clock_controller);
if (!clk) {
LOG_ERR("Could not get clock controller '%s'",
config->clock_controller);
return -EINVAL;
}
err = clock_control_on(clk, config->clock_subsys);
if (err) {
LOG_ERR("Could not turn on clock (err %d)", err);
return -EINVAL;
}
err = clock_control_get_rate(clk, config->clock_subsys, &clk_freq);
if (err) {
LOG_ERR("Could not get clock frequency (err %d)", err);
return -EINVAL;
}
LPI2C_MasterGetDefaultConfig(&master_config);
LPI2C_MasterInit(config->base, &master_config, clk_freq);
LPI2C_MasterTransferCreateHandle(config->base, &data->handle,
rv32m1_lpi2c_master_transfer_callback,
dev);
dev_cfg = i2c_map_dt_bitrate(config->bitrate);
err = rv32m1_lpi2c_configure(dev, dev_cfg | I2C_MODE_MASTER);
if (err) {
LOG_ERR("Could not configure controller (err %d)", err);
return err;
}
config->irq_config_func(dev);
return 0;
}
static const struct i2c_driver_api rv32m1_lpi2c_driver_api = {
.configure = rv32m1_lpi2c_configure,
.transfer = rv32m1_lpi2c_transfer,
};
#define RV32M1_LPI2C_DEVICE(id) \
static void rv32m1_lpi2c_irq_config_func_##id(struct device *dev); \
static const struct rv32m1_lpi2c_config rv32m1_lpi2c_##id##_config = { \
.base = \
(LPI2C_Type *)DT_OPENISA_RV32M1_LPI2C_I2C_##id##_BASE_ADDRESS, \
.clock_controller = \
DT_OPENISA_RV32M1_LPI2C_I2C_##id##_CLOCK_CONTROLLER, \
.clock_subsys = \
(clock_control_subsys_t) \
DT_OPENISA_RV32M1_LPI2C_I2C_##id##_CLOCK_NAME, \
.clock_ip_name = kCLOCK_Lpi2c##id, \
.clock_ip_src = kCLOCK_IpSrcFircAsync, \
.bitrate = DT_OPENISA_RV32M1_LPI2C_I2C_##id##_CLOCK_FREQUENCY, \
.irq_config_func = rv32m1_lpi2c_irq_config_func_##id, \
}; \
static struct rv32m1_lpi2c_data rv32m1_lpi2c_##id##_data = { \
.transfer_sync = Z_SEM_INITIALIZER( \
rv32m1_lpi2c_##id##_data.transfer_sync, 1, 1), \
.completion_sync = Z_SEM_INITIALIZER( \
rv32m1_lpi2c_##id##_data.completion_sync, 0, 1), \
}; \
DEVICE_AND_API_INIT(rv32m1_lpi2c_##id, \
DT_OPENISA_RV32M1_LPI2C_I2C_##id##_LABEL, \
&rv32m1_lpi2c_init, \
&rv32m1_lpi2c_##id##_data, \
&rv32m1_lpi2c_##id##_config, \
POST_KERNEL, CONFIG_I2C_INIT_PRIORITY, \
&rv32m1_lpi2c_driver_api); \
static void rv32m1_lpi2c_irq_config_func_##id(struct device *dev) \
{ \
IRQ_CONNECT(DT_OPENISA_RV32M1_LPI2C_I2C_##id##_IRQ, \
DT_OPENISA_RV32M1_LPI2C_I2C_##id##_IRQ_PRI, \
rv32m1_lpi2c_isr, DEVICE_GET(rv32m1_lpi2c_##id), \
0); \
irq_enable(DT_OPENISA_RV32M1_LPI2C_I2C_##id##_IRQ); \
} \
#ifdef CONFIG_I2C_0
RV32M1_LPI2C_DEVICE(0)
#endif
#ifdef CONFIG_I2C_1
RV32M1_LPI2C_DEVICE(1)
#endif
#ifdef CONFIG_I2C_2
RV32M1_LPI2C_DEVICE(2)
#endif
#ifdef CONFIG_I2C_3
RV32M1_LPI2C_DEVICE(3)
#endif

View file

@ -0,0 +1,30 @@
#
# Copyright (c) 2019, Henrik Brix Andersen <henrik@brixandersen.dk>
#
# SPDX-License-Identifier: Apache-2.0
#
---
title: OpenISA LPI2C
version: 0.1
description: >
This binding gives a base representation of the OpenISA LPI2C controller
inherits:
!include i2c.yaml
properties:
compatible:
constraint: "openisa,rv32m1-lpi2c"
reg:
type: int
description: mmio register space
generation: define
category: required
interrupts:
type: compound
category: required
description: required interrupts
generation: define

View file

@ -5,6 +5,7 @@
#include <dt-bindings/interrupt-controller/openisa-intmux.h> #include <dt-bindings/interrupt-controller/openisa-intmux.h>
#include <dt-bindings/gpio/gpio.h> #include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/i2c/i2c.h>
/ { / {
#address-cells = <1>; #address-cells = <1>;
@ -29,6 +30,10 @@
uart-1 = &uart1; uart-1 = &uart1;
uart-2 = &uart2; uart-2 = &uart2;
uart-3 = &uart3; uart-3 = &uart3;
i2c-0 = &i2c0;
i2c-1 = &i2c1;
i2c-2 = &i2c2;
i2c-3 = &i2c3;
}; };
cpus { cpus {
@ -244,5 +249,57 @@
label = "UART_3"; label = "UART_3";
status = "disabled"; status = "disabled";
}; };
i2c0: lpi2c@4003a000 {
compatible = "openisa,rv32m1-lpi2c";
reg = <0x4003a000 0x170>;
interrupt-parent = <&event>;
interrupts = <15>;
clocks = <&pcc0 0xe8>;
label = "I2C_0";
clock-frequency = <I2C_BITRATE_STANDARD>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
i2c1: lpi2c@4003b000 {
compatible = "openisa,rv32m1-lpi2c";
reg = <0x4003b000 0x170>;
interrupt-parent = <&event>;
interrupts = <16>;
clocks = <&pcc0 0xec>;
label = "I2C_1";
clock-frequency = <I2C_BITRATE_STANDARD>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
i2c2: lpi2c@4003c000 {
compatible = "openisa,rv32m1-lpi2c";
reg = <0x4003c000 0x170>;
interrupt-parent = <&intmux>;
interrupts = <INTMUX_LEVEL2_IRQ(INTMUX_CH1, 11)>;
clocks = <&pcc0 0xf0>;
label = "I2C_2";
clock-frequency = <I2C_BITRATE_STANDARD>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
i2c3: lpi2c@4102e000 {
compatible = "openisa,rv32m1-lpi2c";
reg = <0x4102e000 0x170>;
interrupt-parent = <&intmux>;
interrupts = <INTMUX_LEVEL2_IRQ(INTMUX_CH1, 24)>;
clocks = <&pcc1 0xb8>;
label = "I2C_3";
clock-frequency = <I2C_BITRATE_STANDARD>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
}; };
}; };

View file

@ -10,3 +10,8 @@ config HAS_RV32M1_LPUART
bool bool
help help
Set if the low power uart (LPUART) module is present in the SoC. Set if the low power uart (LPUART) module is present in the SoC.
config HAS_RV32M1_LPI2C
bool
help
Set if the low power i2c (LPI2C) module is present in the SoC.

View file

@ -2,3 +2,4 @@ zephyr_include_directories(.)
zephyr_sources(fsl_clock.c) zephyr_sources(fsl_clock.c)
zephyr_sources_ifdef(CONFIG_UART_RV32M1_LPUART fsl_lpuart.c) zephyr_sources_ifdef(CONFIG_UART_RV32M1_LPUART fsl_lpuart.c)
zephyr_sources_ifdef(CONFIG_I2C_RV32M1_LPI2C fsl_lpi2c.c)

View file

@ -185,4 +185,11 @@ config UART_RV32M1_LPUART
endif # SERIAL endif # SERIAL
if I2C
config I2C_RV32M1_LPI2C
def_bool y
endif # I2C
endif # SOC_OPENISA_RV32M1_RISCV32 endif # SOC_OPENISA_RV32M1_RISCV32

View file

@ -12,6 +12,7 @@ config SOC_OPENISA_RV32M1_RISCV32
# (We can't make it a 'depends on' without causing a dependency loop). # (We can't make it a 'depends on' without causing a dependency loop).
select XIP select XIP
select HAS_RV32M1_LPUART select HAS_RV32M1_LPUART
select HAS_RV32M1_LPI2C
select ATOMIC_OPERATIONS_C select ATOMIC_OPERATIONS_C
select VEGA_SDK_HAL select VEGA_SDK_HAL
select RISCV_SOC_INTERRUPT_INIT select RISCV_SOC_INTERRUPT_INIT