sim: Initial Linux i2c bus support

This adds the inital wiring for i2c bus support in the sim target
and for Linux host adds the lower half that uses the i2c chardev.

Signed-off-by: Brennan Ashton <bashton@brennanashton.com>
This commit is contained in:
Brennan Ashton 2020-10-29 01:41:58 -07:00 committed by Xiang Xiao
parent 2dfd7a4e8f
commit 54832f37f2
10 changed files with 534 additions and 6 deletions

View file

@ -534,6 +534,36 @@ config SIM_HCISOCKET
control of the device, but is abstracted from the
physical interface which is still handled by Linux.
config SIM_I2CBUS
bool "Simulated I2C Bus"
default n
select I2C
---help---
Build in support for simulated i2c bus
if SIM_I2CBUS
choice
prompt "Simulated I2C Bus Type"
default SIM_I2CBUS_LINUX
config SIM_I2CBUS_LINUX
bool "Linux I2C Bus Character Dev"
depends on HOST_LINUX
---help---
Attach a Linux I2C bus via the character device
interface. This should be used with caution as it
could interfere with devices internal to the system.
It is recommended to use this with a USB<>I2C device
like the MCP2221 and set udev rules so that only
the bus provided by this device can be controlled
by the user running the simulator.
https://www.kernel.org/doc/html/latest/i2c/dev-interface.html
endchoice
endif
config SIM_UART_NUMBER
int "The number of tty ports on sim platform, range is 0~4"
default 0

View file

@ -201,6 +201,14 @@ ifeq ($(CONFIG_SIM_HCISOCKET),y)
CSRCS += up_hcisocket.c
endif
ifeq ($(CONFIG_I2C_RESET),y)
HOSTCFLAGS += -DCONFIG_I2C_RESET=1
endif
ifeq ($(CONFIG_SIM_I2CBUS_LINUX),y)
HOSTSRCS += up_i2cbuslinux.c
endif
ifeq ($(CONFIG_RPTUN),y)
CSRCS += up_rptun.c
endif

View file

@ -32,6 +32,7 @@
NXSYMBOLS(__cxa_atexit)
NXSYMBOLS(bind)
NXSYMBOLS(calloc)
NXSYMBOLS(clock_gettime)
NXSYMBOLS(close)
NXSYMBOLS(closedir)
@ -46,6 +47,7 @@ NXSYMBOLS(if_nametoindex)
NXSYMBOLS(ioctl)
NXSYMBOLS(lseek)
NXSYMBOLS(malloc)
NXSYMBOLS(memcpy)
NXSYMBOLS(mkdir)
NXSYMBOLS(mmap)
NXSYMBOLS(munmap)

View file

@ -0,0 +1,76 @@
/****************************************************************************
* arch/sim/src/sim/up_i2cbus.h
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
#ifndef _ARCH_SIM_SRC_SIM_I2CBUS_H_
#define _ARCH_SIM_SRC_SIM_I2CBUS_H_
/****************************************************************************
* Included Files
****************************************************************************/
#include <stdint.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* NuttX msg flags (see i2c_master.h) */
#define NUTTX_I2C_M_READ 0x0001 /* Read data, from slave to master */
#define NUTTX_I2C_M_TEN 0x0002 /* Ten bit address */
#define NUTTX_I2C_M_NOSTOP 0x0040 /* Message should not end with a STOP */
#define NUTTX_I2C_M_NOSTART 0x0080 /* Message should not begin with a START */
/****************************************************************************
* Public Types
****************************************************************************/
/* NuttX i2c msg struct (see i2c_master.h) */
struct i2c_msg_s
{
uint32_t frequency; /* I2C frequency */
uint16_t addr; /* Slave address (7- or 10-bit) */
uint16_t flags; /* See I2C_M_* definitions */
uint8_t *buffer; /* Buffer to be transferred */
ssize_t length; /* Length of the buffer in bytes */
};
/* Structs needed for interacting with the NuttX i2c_master */
struct i2c_master_s
{
const struct i2c_ops_s *ops;
};
struct i2c_ops_s
{
int (*transfer)(struct i2c_master_s *dev,
struct i2c_msg_s *msgs, int count);
#ifdef CONFIG_I2C_RESET
int (*reset)(struct i2c_master_s *dev);
#endif
};
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
#endif /* _ARCH_SIM_SRC_SIM_I2CBUS_H_ */

View file

@ -0,0 +1,310 @@
/****************************************************************************
* arch/sim/src/sim/up_i2cbuslinux.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include "up_i2cbus.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define ERROR(fmt, ...) \
syslog(LOG_ERR, "up_i2cbuslinux: " fmt "\n", ##__VA_ARGS__)
#define INFO(fmt, ...) \
syslog(LOG_ERR, "up_i2cbuslinux: " fmt "\n", ##__VA_ARGS__)
#define DEBUG(fmt, ...)
/****************************************************************************
* Private Types
****************************************************************************/
struct linux_i2cbus_master_s
{
const struct i2c_ops_s *ops; /* I2C vtable */
int file;
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int linux_i2cbus_transfer(struct i2c_master_s *dev,
struct i2c_msg_s *msgs, int count);
#ifdef CONFIG_I2C_RESET
static int linux_i2cbus_reset(struct i2c_master_s *dev);
#endif
/****************************************************************************
* Private Data
****************************************************************************/
static struct i2c_ops_s i2c_linux_ops =
{
.transfer = linux_i2cbus_transfer,
#ifdef CONFIG_I2C_RESET
.reset = linux_i2cbus_reset,
#endif
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: linux_i2cbus_reset
*
* Description:
* Provide i2c reset
*
****************************************************************************/
#ifdef CONFIG_I2C_RESET
static int linux_i2cbus_reset(struct i2c_master_s *dev)
{
return -1; /* Not implemented */
}
#endif
/****************************************************************************
* Name: linux_i2cbus_transfer
*
* Description:
* Provide i2c transfer
*
****************************************************************************/
static int linux_i2cbus_transfer(struct i2c_master_s *dev,
struct i2c_msg_s *msgs, int count)
{
int ret = 0;
struct linux_i2cbus_master_s *priv = (struct linux_i2cbus_master_s *)dev;
int file = priv->file;
uint8_t *pack_buf = NULL;
struct i2c_rdwr_ioctl_data ioctl_data;
struct i2c_msg l_msgs[2]; /* We only support up to 2 messages */
ioctl_data.msgs = l_msgs;
/* Many i2c bus do not play well with combined messages via the Linux
* interface, this makes stitching things together a little harder
* because NuttX provides the ability to hold the bus without ending
* with a STOP which is not ideal in general, and not possible with
* Linux.
*/
if ((msgs[0].flags & NUTTX_I2C_M_TEN) || (msgs[1].flags & NUTTX_I2C_M_TEN))
{
/* Linux also has somewhat poor support for 10bit addresses and they
* are quite rare so we just don't support them for now here
*/
return -1;
}
ioctl_data.msgs = l_msgs;
if (count == 1)
{
if (msgs[0].flags & NUTTX_I2C_M_NOSTOP || \
msgs[0].flags & NUTTX_I2C_M_NOSTART)
{
/* Do not support leaving the bus hanging or try to send
* without first starting.
*/
return -1;
}
l_msgs[0].addr = msgs[0].addr;
l_msgs[0].buf = msgs[0].buffer;
l_msgs[0].len = msgs[0].length;
l_msgs[0].flags = 0;
if (msgs[0].flags & NUTTX_I2C_M_READ)
{
l_msgs[0].flags |= I2C_M_RD;
}
ioctl_data.nmsgs = 1;
}
else if(count == 2)
{
/* Addresses should be the same */
if (msgs[0].addr != msgs[1].addr)
{
return -1;
}
/* Check if we are about to do a read of a register
* NuttX interface represents this as WRITE(NOSTOP) + READ
* Linux interface represents this as WRITE + READ
*/
if (msgs[0].flags & NUTTX_I2C_M_NOSTOP && \
msgs[1].flags & NUTTX_I2C_M_READ)
{
l_msgs[0].addr = msgs[0].addr;
l_msgs[0].flags = 0;
l_msgs[0].buf = msgs[0].buffer;
l_msgs[0].len = msgs[0].length;
l_msgs[1].addr = msgs[1].addr;
l_msgs[1].flags = I2C_M_RD;
l_msgs[1].buf = msgs[1].buffer;
l_msgs[1].len = msgs[1].length;
ioctl_data.nmsgs = 2;
}
else if (!(msgs[0].flags & NUTTX_I2C_M_READ) && \
!(msgs[1].flags & NUTTX_I2C_M_READ) && \
(msgs[0].flags & NUTTX_I2C_M_NOSTOP) && \
(msgs[1].flags & NUTTX_I2C_M_NOSTART))
{
/* These writes are actually just a single write in Linux
* so we pack the data in a single buffer and the unpack
* it at the end. This could support for for more than just 2
* messages, but in most cases it is just two because it is
* connivent to write the register address and the data into two
* different buffers.
*/
pack_buf = malloc(msgs[0].length + msgs[1].length);
if (pack_buf == NULL)
{
return -1;
}
memcpy(pack_buf, msgs[0].buffer, msgs[0].length);
memcpy(pack_buf + msgs[0].length, msgs[1].buffer, msgs[1].length);
l_msgs[0].len = msgs[0].length + msgs[1].length;
l_msgs[0].flags = 0;
l_msgs[0].addr = msgs[0].addr;
ioctl_data.msgs[0].buf = pack_buf;
ioctl_data.nmsgs = 1;
}
else
{
/* Many busses cannot handle more than 2 messages */
return -1;
}
}
else
{
return -1;
}
if (ioctl(file, I2C_RDWR, &ioctl_data) < 1)
{
ret = -1;
}
/* Unpack from buffer back to msg buffers if needed */
if (pack_buf != NULL)
{
if (ret == 0)
{
int idx;
uint8_t *msg_p = pack_buf;
for (idx = 0; idx < count; idx++)
{
memcpy(msgs[idx].buffer, msg_p, msgs[idx].length);
msg_p += msgs[idx].length;
}
}
free(pack_buf);
}
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: sim_i2cbus_initialize
*
* Description:
* Initialize one I2C bus
*
****************************************************************************/
struct i2c_master_s *sim_i2cbus_initialize(int bus)
{
struct linux_i2cbus_master_s *priv;
char filename[20];
priv = (struct linux_i2cbus_master_s *)malloc(sizeof(priv));
if (priv == NULL)
{
ERROR("Failed to allocate private i2c master driver");
return NULL;
}
snprintf(filename, 19, "/dev/i2c-%d", bus);
priv->file = open(filename, O_RDWR);
if (priv->file < 0)
{
ERROR("Failed to open %s: %d", filename, priv->file);
free(priv);
return NULL;
}
priv->ops = &i2c_linux_ops;
return (struct i2c_master_s *)priv;
}
/****************************************************************************
* Name: sim_i2cbus_uninitialize
*
* Description:
* Uninitialize an I2C bus
*
****************************************************************************/
int sim_i2cbus_uninitialize(struct i2c_master_s *dev)
{
struct linux_i2cbus_master_s *priv = (struct linux_i2cbus_master_s *)dev;
if (priv->file >= 0)
{
close(priv->file);
}
free(priv);
return 0;
}

View file

@ -160,6 +160,7 @@ struct tcb_s;
struct spi_dev_s;
struct qspi_dev_s;
struct ioexpander_dev_s;
struct i2c_master_s;
/****************************************************************************
* Public Data
@ -404,6 +405,13 @@ struct audio_lowerhalf_s *sim_audio_initialize(bool playback);
void sim_audio_loop(void);
#endif
/* up_i2cbus*.c *************************************************************/
#ifdef CONFIG_SIM_I2CBUS
struct i2c_master_s *sim_i2cbus_initialize(int bus);
int sim_i2cbus_uninitialize(struct i2c_master_s *dev);
#endif
/* Debug ********************************************************************/
#ifdef CONFIG_STACK_COLORATION

View file

@ -54,3 +54,11 @@ config SIM_WTGAHRS2_UARTN
We can select the number accoding to which SIM_UARTX_NAME is uesd to sensor.
This range is 0-4.
endif
config SIM_I2CBUS_ID
int "I2C host bus ID to attach to simulator"
default 0
depends on SIM_I2CBUS
---help---
This is the bus identifier that should be used by the host implementation to
attach to the simulator driver.

View file

@ -0,0 +1,43 @@
#
# This file is autogenerated: PLEASE DO NOT EDIT IT.
#
# You can use "make menuconfig" to make any modifications to the installed .config file.
# You can then do "make savedefconfig" to generate a new defconfig file that includes your
# modifications.
#
# CONFIG_NSH_CMDOPT_HEXDUMP is not set
CONFIG_ARCH="sim"
CONFIG_ARCH_BOARD="sim"
CONFIG_ARCH_BOARD_SIM=y
CONFIG_ARCH_CHIP="sim"
CONFIG_ARCH_SIM=y
CONFIG_BOARDCTL_APP_SYMTAB=y
CONFIG_BOARDCTL_POWEROFF=y
CONFIG_BOARDCTL_ROMDISK=y
CONFIG_BOARD_LOOPSPERMSEC=0
CONFIG_BOOT_RUNFROMEXTSRAM=y
CONFIG_BUILTIN=y
CONFIG_DEBUG_SYMBOLS=y
CONFIG_DEV_ZERO=y
CONFIG_EXAMPLES_HELLO=y
CONFIG_FS_PROCFS=y
CONFIG_IDLETHREAD_STACKSIZE=4096
CONFIG_LIBC_EXECFUNCS=y
CONFIG_MAX_TASKS=64
CONFIG_NFILE_DESCRIPTORS=32
CONFIG_NSH_ARCHINIT=y
CONFIG_NSH_BUILTIN_APPS=y
CONFIG_NSH_READLINE=y
CONFIG_POSIX_SPAWN_PROXY_STACKSIZE=2048
CONFIG_READLINE_TABCOMPLETION=y
CONFIG_SCHED_HAVE_PARENT=y
CONFIG_SCHED_ONEXIT=y
CONFIG_SCHED_WAITPID=y
CONFIG_SDCLONE_DISABLE=y
CONFIG_SIM_I2CBUS=y
CONFIG_START_MONTH=6
CONFIG_START_YEAR=2008
CONFIG_SYSTEM_I2CTOOL=y
CONFIG_SYSTEM_NSH=y
CONFIG_USERMAIN_STACKSIZE=4096
CONFIG_USER_ENTRYPOINT="nsh_main"

View file

@ -1,5 +1,5 @@
/****************************************************************************
* boards/sim/sim/sim/src/sam_bringup.c
* boards/sim/sim/sim/src/sim_bringup.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
@ -40,6 +40,8 @@
#include <nuttx/wireless/bluetooth/bt_driver.h>
#include <nuttx/wireless/bluetooth/bt_null.h>
#include <nuttx/wireless/ieee802154/ieee802154_loopback.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/sensors/mpu60x0.h>
#ifdef CONFIG_LCD_DEV
#include <nuttx/lcd/lcd_dev.h>
@ -68,6 +70,13 @@ int sim_bringup(void)
#ifdef CONFIG_RAMMTD
FAR uint8_t *ramstart;
#endif
#ifdef CONFIG_SIM_I2CBUS
FAR struct i2c_master_s *i2cbus;
#endif
#ifdef CONFIG_MPU60X0_I2C
FAR struct mpu_config_s *mpu_config;
#endif
int ret = OK;
#ifdef CONFIG_FS_BINFS
@ -335,5 +344,41 @@ int sim_bringup(void)
}
#endif
#ifdef CONFIG_SIM_I2CBUS
/* Initialize the i2c master bus device */
i2cbus = sim_i2cbus_initialize(CONFIG_SIM_I2CBUS_ID);
if (i2cbus == NULL)
{
syslog(LOG_ERR, "ERROR: sim_i2cbus_initialize failed.\n");
}
#if defined(CONFIG_SYSTEM_I2CTOOL) || defined(CONFIG_MPU60X0_I2C)
else
{
ret = i2c_register(i2cbus, 0);
if (ret < 0)
{
syslog(LOG_ERR, "ERROR: Failed to register I2C%d driver: %d\n",
0, ret);
sim_i2cbus_uninitialize(i2cbus);
}
#ifdef CONFIG_MPU60X0_I2C
mpu_config = kmm_zalloc(sizeof(struct mpu_config_s));
if (mpu_config == NULL)
{
syslog(LOG_ERR, "ERROR: Failed to allocate mpu60x0 driver\n");
}
else
{
mpu_config->i2c = i2cbus;
mpu_config->addr = 0x68;
mpu60x0_register("/dev/imu0", mpu_config);
}
#endif
}
#endif
#endif
return ret;
}

View file

@ -453,13 +453,11 @@ static int __mpu_write_reg_i2c(FAR struct mpu_dev_s *dev,
msg[0].flags = I2C_M_NOSTOP;
msg[0].buffer = &reg_addr;
msg[0].length = 1;
msg[1].frequency = CONFIG_MPU60X0_I2C_FREQ;
msg[1].addr = dev->config.addr;
msg[1].flags = I2C_M_NOSTART;
msg[1].buffer = (FAR uint8_t *)buf;
msg[1].length = len;
ret = I2C_TRANSFER(dev->config.i2c, msg, 2);
if (ret < 0)
{
@ -718,19 +716,19 @@ static int mpu_reset(FAR struct mpu_dev_s *dev)
do
{
up_mdelay(50); /* msecs (arbitrary) */
nxsig_usleep(50000); /* usecs (arbitrary) */
}
while (__mpu_read_pwr_mgmt_1(dev) & PWR_MGMT_1__DEVICE_RESET);
/* Reset signal paths */
__mpu_write_signal_path_reset(dev, SIGNAL_PATH_RESET__ALL_RESET);
up_mdelay(2);
nxsig_usleep(2000);
/* Disable SLEEP, use PLL with z-axis clock source */
__mpu_write_pwr_mgmt_1(dev, 3);
up_mdelay(2);
nxsig_usleep(2000);
/* Disable i2c if we're on spi. */