zephyr/drivers/i2c/i2c_cc13xx_cc26xx.c
Gerard Marull-Paretas cc2f0e9c08 pm: use enum for device PM states
Move all PM_DEVICE_STATE_* definitions to an enum. The
PM_DEVICE_STATE_SET and PM_DEVICE_STATE_GET definitions have been kept
out of the enum since they do not represent any state. However, their
name has not been changed since they will be removed soon.

All drivers and tests have been adjusted accordingly.

Signed-off-by: Gerard Marull-Paretas <gerard.marull@nordicsemi.no>
2021-07-07 14:13:12 -04:00

480 lines
11 KiB
C

/*
* Copyright (c) 2019 Brett Witherspoon
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT ti_cc13xx_cc26xx_i2c
#include <kernel.h>
#include <drivers/i2c.h>
#include <pm/device.h>
#include <pm/pm.h>
#define LOG_LEVEL CONFIG_I2C_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(i2c_cc13xx_cc26xx);
#include <driverlib/i2c.h>
#include <driverlib/ioc.h>
#include <driverlib/prcm.h>
#include <ti/drivers/Power.h>
#include <ti/drivers/power/PowerCC26X2.h>
#include "i2c-priv.h"
struct i2c_cc13xx_cc26xx_data {
struct k_sem lock;
struct k_sem complete;
volatile uint32_t error;
#ifdef CONFIG_PM
Power_NotifyObj postNotify;
uint32_t dev_config;
#endif
#ifdef CONFIG_PM_DEVICE
enum pm_device_state pm_state;
#endif
};
struct i2c_cc13xx_cc26xx_config {
uint32_t base;
uint32_t scl_pin;
uint32_t sda_pin;
};
static inline struct i2c_cc13xx_cc26xx_data *get_dev_data(const struct device *dev)
{
return dev->data;
}
static inline const struct i2c_cc13xx_cc26xx_config *
get_dev_config(const struct device *dev)
{
return dev->config;
}
static int i2c_cc13xx_cc26xx_transmit(const struct device *dev,
const uint8_t *buf,
uint32_t len, uint16_t addr)
{
const uint32_t base = get_dev_config(dev)->base;
struct i2c_cc13xx_cc26xx_data *data = get_dev_data(dev);
/* Sending address without data is not supported */
if (len == 0) {
return -EIO;
}
I2CMasterSlaveAddrSet(base, addr, false);
/* The following assumes a single master. Use I2CMasterBusBusy() if
* wanting to implement multiple master support.
*/
/* Single transmission */
if (len == 1) {
I2CMasterDataPut(base, *buf);
I2CMasterControl(base, I2C_MASTER_CMD_SINGLE_SEND);
k_sem_take(&data->complete, K_FOREVER);
return data->error == I2C_MASTER_ERR_NONE ? 0 : -EIO;
}
/* Burst transmission */
I2CMasterDataPut(base, buf[0]);
I2CMasterControl(base, I2C_MASTER_CMD_BURST_SEND_START);
k_sem_take(&data->complete, K_FOREVER);
if (data->error != I2C_MASTER_ERR_NONE) {
goto send_error_stop;
}
for (int i = 1; i < len - 1; i++) {
I2CMasterDataPut(base, buf[i]);
I2CMasterControl(base, I2C_MASTER_CMD_BURST_SEND_CONT);
k_sem_take(&data->complete, K_FOREVER);
if (data->error != I2C_MASTER_ERR_NONE) {
goto send_error_stop;
}
}
I2CMasterDataPut(base, buf[len - 1]);
I2CMasterControl(base, I2C_MASTER_CMD_BURST_SEND_FINISH);
k_sem_take(&data->complete, K_FOREVER);
if (data->error != I2C_MASTER_ERR_NONE) {
return -EIO;
}
return 0;
send_error_stop:
I2CMasterControl(base, I2C_MASTER_CMD_BURST_SEND_ERROR_STOP);
return -EIO;
}
static int i2c_cc13xx_cc26xx_receive(const struct device *dev, uint8_t *buf,
uint32_t len,
uint16_t addr)
{
const uint32_t base = get_dev_config(dev)->base;
struct i2c_cc13xx_cc26xx_data *data = get_dev_data(dev);
/* Sending address without data is not supported */
if (len == 0) {
return -EIO;
}
I2CMasterSlaveAddrSet(base, addr, true);
/* The following assumes a single master. Use I2CMasterBusBusy() if
* wanting to implement multiple master support.
*/
/* Single receive */
if (len == 1) {
I2CMasterControl(base, I2C_MASTER_CMD_SINGLE_RECEIVE);
k_sem_take(&data->complete, K_FOREVER);
if (data->error != I2C_MASTER_ERR_NONE) {
return -EIO;
}
*buf = I2CMasterDataGet(base);
return 0;
}
/* Burst receive */
I2CMasterControl(base, I2C_MASTER_CMD_BURST_RECEIVE_START);
k_sem_take(&data->complete, K_FOREVER);
if (data->error != I2C_MASTER_ERR_NONE) {
goto recv_error_stop;
}
buf[0] = I2CMasterDataGet(base);
for (int i = 1; i < len - 1; i++) {
I2CMasterControl(base, I2C_MASTER_CMD_BURST_RECEIVE_CONT);
k_sem_take(&data->complete, K_FOREVER);
if (data->error != I2C_MASTER_ERR_NONE) {
goto recv_error_stop;
}
buf[i] = I2CMasterDataGet(base);
}
I2CMasterControl(base, I2C_MASTER_CMD_BURST_RECEIVE_FINISH);
k_sem_take(&data->complete, K_FOREVER);
if (data->error != I2C_MASTER_ERR_NONE) {
return -EIO;
}
buf[len - 1] = I2CMasterDataGet(base);
return 0;
recv_error_stop:
I2CMasterControl(base, I2C_MASTER_CMD_BURST_RECEIVE_ERROR_STOP);
return -EIO;
}
static int i2c_cc13xx_cc26xx_transfer(const struct device *dev,
struct i2c_msg *msgs,
uint8_t num_msgs, uint16_t addr)
{
int ret = 0;
if (num_msgs == 0) {
return 0;
}
k_sem_take(&get_dev_data(dev)->lock, K_FOREVER);
#ifdef CONFIG_PM
pm_constraint_set(PM_STATE_STANDBY);
#endif
for (int i = 0; i < num_msgs; i++) {
/* Not supported by hardware */
if (msgs[i].flags & I2C_MSG_ADDR_10_BITS) {
ret = -EIO;
break;
}
if ((msgs[i].flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) {
ret = i2c_cc13xx_cc26xx_transmit(dev, msgs[i].buf,
msgs[i].len, addr);
} else {
ret = i2c_cc13xx_cc26xx_receive(dev, msgs[i].buf,
msgs[i].len, addr);
}
if (ret) {
break;
}
}
#ifdef CONFIG_PM
pm_constraint_release(PM_STATE_STANDBY);
#endif
k_sem_give(&get_dev_data(dev)->lock);
return ret;
}
#define CPU_FREQ DT_PROP(DT_PATH(cpus, cpu_0), clock_frequency)
static int i2c_cc13xx_cc26xx_configure(const struct device *dev,
uint32_t dev_config)
{
bool fast;
switch (I2C_SPEED_GET(dev_config)) {
case I2C_SPEED_STANDARD:
fast = false;
break;
case I2C_SPEED_FAST:
fast = true;
break;
default:
LOG_ERR("Unsupported speed");
return -EIO;
}
/* Support for slave mode has not been implemented */
if (!(dev_config & I2C_MODE_MASTER)) {
LOG_ERR("Slave mode is not supported");
return -EIO;
}
/* This is deprecated and could be ignored in the future */
if (dev_config & I2C_ADDR_10_BITS) {
LOG_ERR("10-bit addressing mode is not supported");
return -EIO;
}
/* Enables and configures I2C master */
I2CMasterInitExpClk(get_dev_config(dev)->base, CPU_FREQ, fast);
#ifdef CONFIG_PM
get_dev_data(dev)->dev_config = dev_config;
#endif
return 0;
}
static void i2c_cc13xx_cc26xx_isr(const void *arg)
{
const uint32_t base = get_dev_config(arg)->base;
struct i2c_cc13xx_cc26xx_data *data = get_dev_data(arg);
if (I2CMasterIntStatus(base, true)) {
I2CMasterIntClear(base);
data->error = I2CMasterErr(base);
k_sem_give(&data->complete);
}
}
#ifdef CONFIG_PM
/*
* ======== postNotifyFxn ========
* Called by Power module when waking up the CPU from Standby. The i2c needs
* to be reconfigured afterwards, unless Zephyr's device PM turned it off, in
* which case it'd be responsible for turning it back on and reconfigure it.
*/
static int postNotifyFxn(unsigned int eventType, uintptr_t eventArg,
uintptr_t clientArg)
{
const struct device *dev = (const struct device *)clientArg;
int ret = Power_NOTIFYDONE;
int16_t res_id;
/* Reconfigure the hardware if returning from sleep */
if (eventType == PowerCC26XX_AWAKE_STANDBY) {
res_id = PowerCC26XX_PERIPH_I2C0;
if (Power_getDependencyCount(res_id) != 0) {
/* Reconfigure and enable I2C only if powered */
if (i2c_cc13xx_cc26xx_configure(dev,
get_dev_data(dev)->dev_config) != 0) {
ret = Power_NOTIFYERROR;
}
I2CMasterIntEnable(get_dev_config(dev)->base);
}
}
return (ret);
}
#endif
#ifdef CONFIG_PM_DEVICE
static int i2c_cc13xx_cc26xx_set_power_state(const struct device *dev,
enum pm_device_state new_state)
{
int ret = 0;
if ((new_state == PM_DEVICE_STATE_ACTIVE) &&
(new_state != get_dev_data(dev)->pm_state)) {
Power_setDependency(PowerCC26XX_PERIPH_I2C0);
IOCPinTypeI2c(get_dev_config(dev)->base,
get_dev_config(dev)->sda_pin,
get_dev_config(dev)->scl_pin);
ret = i2c_cc13xx_cc26xx_configure(dev,
get_dev_data(dev)->dev_config);
if (ret == 0) {
I2CMasterIntEnable(get_dev_config(dev)->base);
get_dev_data(dev)->pm_state = new_state;
}
} else {
__ASSERT_NO_MSG(new_state == PM_DEVICE_STATE_LOW_POWER ||
new_state == PM_DEVICE_STATE_SUSPEND ||
new_state == PM_DEVICE_STATE_OFF);
if (get_dev_data(dev)->pm_state == PM_DEVICE_STATE_ACTIVE) {
I2CMasterIntDisable(get_dev_config(dev)->base);
I2CMasterDisable(get_dev_config(dev)->base);
/* Reset pin type to default GPIO configuration */
IOCPortConfigureSet(get_dev_config(dev)->scl_pin,
IOC_PORT_GPIO, IOC_STD_OUTPUT);
IOCPortConfigureSet(get_dev_config(dev)->sda_pin,
IOC_PORT_GPIO, IOC_STD_OUTPUT);
Power_releaseDependency(PowerCC26XX_PERIPH_I2C0);
get_dev_data(dev)->pm_state = new_state;
}
}
return ret;
}
static int i2c_cc13xx_cc26xx_pm_control(const struct device *dev,
uint32_t ctrl_command,
enum pm_device_state *state, pm_device_cb cb,
void *arg)
{
int ret = 0;
if (ctrl_command == PM_DEVICE_STATE_SET) {
enum pm_device_state new_state = *state;
if (new_state != get_dev_data(dev)->pm_state) {
ret = i2c_cc13xx_cc26xx_set_power_state(dev,
new_state);
}
} else {
__ASSERT_NO_MSG(ctrl_command == PM_DEVICE_STATE_GET);
*state = get_dev_data(dev)->pm_state;
}
if (cb) {
cb(dev, ret, state, arg);
}
return ret;
}
#endif /* CONFIG_PM_DEVICE */
static int i2c_cc13xx_cc26xx_init(const struct device *dev)
{
uint32_t cfg;
int err;
#ifdef CONFIG_PM_DEVICE
get_dev_data(dev)->pm_state = PM_DEVICE_STATE_ACTIVE;
#endif
#ifdef CONFIG_PM
/* Set Power dependencies & constraints */
Power_setDependency(PowerCC26XX_PERIPH_I2C0);
/* Register notification function */
Power_registerNotify(&get_dev_data(dev)->postNotify,
PowerCC26XX_AWAKE_STANDBY,
postNotifyFxn, (uintptr_t)dev);
#else
/* Enable I2C power domain */
PRCMPowerDomainOn(PRCM_DOMAIN_SERIAL);
/* Enable I2C peripheral clock */
PRCMPeripheralRunEnable(PRCM_PERIPH_I2C0);
/* Enable in sleep mode until proper power management is added */
PRCMPeripheralSleepEnable(PRCM_PERIPH_I2C0);
PRCMPeripheralDeepSleepEnable(PRCM_PERIPH_I2C0);
/* Load PRCM settings */
PRCMLoadSet();
while (!PRCMLoadGet()) {
continue;
}
/* I2C should not be accessed until power domain is on. */
while (PRCMPowerDomainStatus(PRCM_DOMAIN_SERIAL) !=
PRCM_DOMAIN_POWER_ON) {
continue;
}
#endif
IRQ_CONNECT(DT_INST_IRQN(0),
DT_INST_IRQ(0, priority),
i2c_cc13xx_cc26xx_isr, DEVICE_DT_INST_GET(0), 0);
irq_enable(DT_INST_IRQN(0));
/* Configure IOC module to route SDA and SCL signals */
IOCPinTypeI2c(get_dev_config(dev)->base, get_dev_config(dev)->sda_pin,
get_dev_config(dev)->scl_pin);
cfg = i2c_map_dt_bitrate(DT_INST_PROP(0, clock_frequency));
err = i2c_cc13xx_cc26xx_configure(dev, cfg | I2C_MODE_MASTER);
if (err) {
LOG_ERR("Failed to configure");
return err;
}
I2CMasterIntEnable(get_dev_config(dev)->base);
return 0;
}
static const struct i2c_driver_api i2c_cc13xx_cc26xx_driver_api = {
.configure = i2c_cc13xx_cc26xx_configure,
.transfer = i2c_cc13xx_cc26xx_transfer
};
static const struct i2c_cc13xx_cc26xx_config i2c_cc13xx_cc26xx_config = {
.base = DT_INST_REG_ADDR(0),
.sda_pin = DT_INST_PROP(0, sda_pin),
.scl_pin = DT_INST_PROP(0, scl_pin)
};
static struct i2c_cc13xx_cc26xx_data i2c_cc13xx_cc26xx_data = {
.lock = Z_SEM_INITIALIZER(i2c_cc13xx_cc26xx_data.lock, 1, 1),
.complete = Z_SEM_INITIALIZER(i2c_cc13xx_cc26xx_data.complete, 0, 1),
.error = I2C_MASTER_ERR_NONE
};
DEVICE_DT_INST_DEFINE(0,
i2c_cc13xx_cc26xx_init,
i2c_cc13xx_cc26xx_pm_control,
&i2c_cc13xx_cc26xx_data, &i2c_cc13xx_cc26xx_config,
POST_KERNEL, CONFIG_I2C_INIT_PRIORITY,
&i2c_cc13xx_cc26xx_driver_api);