This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TDA4VM: Support on writing SPI driver under Linux

Part Number: TDA4VM

Hi TI support teams,

I'm now implementing a Linux Driver to control an IC which is using SPI as communication channel and have some issue.

Please help check.

My devicetree will define the pin like these:

&main_spi5 {
	pinctrl-names = "default";
	pinctrl-0 = <&poc_sepic_hsd_pins_default>;
	status = "okay";

	/* POC_1_2 */
	poc_1_2: poc_1_2@0 {
		compatible = "ti,tps92682";
		reg = <0>; /* CE0 */
		spi-max-frequency = <1000000>;
		pinctrl-names = "default";
		pinctrl-0 = <&mygpio0_pins_sepic_enable>;
		en-gpios = <&main_gpio0 19 GPIO_ACTIVE_HIGH>;
		enable-active-high;
	};
};

poc_sepic_hsd_pins_default: poc_sepic_hsd_pins_default {
		pinctrl-single,pins = <
			J721E_IOPAD(0x1a0, PIN_INPUT, 3) /* (W29) RGMII6_TXC.SPI5_CLK SPI_POC_HSD.SCLK */
			J721E_IOPAD(0x19c, PIN_INPUT, 3) /* (W27) RGMII6_TD0.SPI5_CS0 SPI_CS_POC_1_2 */
			J721E_IOPAD(0x1b4, PIN_INPUT, 3) /* (W25) RGMII6_RD0.SPI5_CS1 SPI_CS_POC_3_4 */
			J721E_IOPAD(0x194, PIN_INPUT, 3) /* (W28) RGMII6_TD2.SPI5_CS2 */
			J721E_IOPAD(0x198, PIN_INPUT, 3) /* (V25) RGMII6_TD1.SPI5_D0 SPI_POC_HSD.MISO */
			J721E_IOPAD(0x1b0, PIN_INPUT, 3) /* (W24) RGMII6_RD1.SPI5_D1 SPI_POC_HSD.MOSI */
		>;
	};

And here is my driver that using the spi_write/spi_read to wirte/read via SPI:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/acpi.h>

#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>

#include <linux/device.h>

#include <linux/delay.h>

/* Register definition */
#define REG_EN			0x00
#define REG_CFG1		0x01
#define REG_SWDIV		0x03
#define REG_ISLOPE		0x04	
#define REG_SOFTSTART	0x06
#define REG_PWMDIV		0x09
#define REG_CH1PWMH		0x0B
#define REG_CH1PWML		0x0A
#define REG_CH2PWMH		0x0D
#define REG_CH2PWML		0x0C
#define REG_ILIM		0x0E
#define REG_IFT			0x0F
#define REG_MFT			0x10
#define REG_FEN1		0x13
#define REG_FEN2		0x14
#define REG_OV			0x16
#define REG_FLT1		0x11
#define REG_FLT2		0x12
#define REG_CH1IADJ		0x07
#define REG_CH2IADJ		0x08

#define NUM_OF_CONF		13
#define WRITE_REG		1
#define READ_REG		0
#define REG_ACTION_IDX	0
#define REG_ADDR_IDX	1
#define REG_VAL_IDX		2

static u16 regConfig[NUM_OF_CONF][3] =
{
	{READ_REG,	REG_FLT1, 		0x00}, // READ FLT1 - clear all the fault read bits and PC bit
	{READ_REG,	REG_FLT2, 		0x00}, // READ FLT1 - clear all the fault read bits and PC bit
	{WRITE_REG,	REG_EN, 		0xBC}, // Set FPINRST = 1 Enable every channel setup - B0
	{WRITE_REG,	REG_CFG1, 		0x03}, // LH = 1, chanel set to CV mode - 0x13
	{WRITE_REG,	REG_SWDIV, 		0x00}, // 00: Division = 2. CHxCLK = fCLKM / 2 FSW= 400kHz
	{WRITE_REG,	REG_ISLOPE, 	0x55}, // VSLP(PK) = 250 mV
	// {WRITE_REG,	REG_SOFTSTART, 	0x66}, // 0110: Division factor = 16 for both channel
	// {WRITE_REG,	REG_PWMDIV, 	0x01}, // 01: PWMCLK = CLKM ÷ 2
	// {WRITE_REG,	REG_CH1PWMH, 	0x03}, // Setup PWM width
	// {WRITE_REG,	REG_CH1PWML, 	0xFF}, // Setup PWM width
	// {WRITE_REG,	REG_CH2PWMH, 	0x03}, // Setup PWM width
	// {WRITE_REG,	REG_CH2PWML, 	0xFF}, // Setup PWM width
	{WRITE_REG,	REG_ILIM,	 	0x5F}, // 01: ILIM event counter threshold = 4, 11: VILIM(THR) = 250 mV
	{WRITE_REG,	REG_IFT, 		0x0A}, // 10: ILIM Fault Timer maximum count = 16
	{WRITE_REG,	REG_MFT, 		0x33}, // 0011: Main Fault Timer maximum count = 2000
	// {WRITE_REG,	REG_FEN1, 		0x3F}, // CHxFBOEN, CHxOVEN, CHxUVEN set - Fault enable for FB pin open fault, output OV, output UV
	// {WRITE_REG,	REG_FEN2, 		0x0F}, // CHxILIMEN, CHxISOEN set - Fault enable for ILIM fault, ISN pin open fault
	{WRITE_REG,	REG_OV, 		0x44}, // 100: OVTHR = VFBREF×(1.100) - Factor 1,1 for both channels; max 45V
	{WRITE_REG,	REG_CH1IADJ,	0x96}, // Equaltion 44 calculation - 12V output
	{WRITE_REG,	REG_CH2IADJ,	0x96}, // Equaltion 44 calculation - 12V output
	{WRITE_REG,	REG_EN, 		0x33}, // Set FPINRST = 1 Enable every channel setup - B0
};

/*-------------------------------------------------------------------------*/

#define SPIDEV_MAJOR			0	
#define N_SPI_MINORS			127	/* ... up to 256 */

static DECLARE_BITMAP(minors, N_SPI_MINORS);

#define SPI_MODE_MASK	(SPI_CPHA | SPI_CPOL | SPI_CS_HIGH \
				| SPI_LSB_FIRST | SPI_READY | SPI_TX_QUAD | SPI_RX_QUAD )

struct spidev_data {
	dev_t			devt;
	spinlock_t		spi_lock;
	struct spi_device	*spi;
	struct list_head	device_entry;

	/* TX/RX buffers are NULL unless this device is open (users > 0) */
	struct mutex		buf_lock;
	unsigned		users;
	u16			*tx_buffer;
	u16			*rx_buffer;
	u32			speed_hz;
	struct device *dev;
};

static LIST_HEAD(device_list);
static DEFINE_MUTEX(device_list_lock);

static unsigned bufsiz = 4096;
module_param(bufsiz, uint, S_IRUGO);
MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message");


static struct class *tps92682_spidev_class;

static const struct of_device_id tps92682_dt_ids[] = {
	{ .compatible = "ti,tps92682" },
	{},
};
MODULE_DEVICE_TABLE(of, tps92682_dt_ids);

/*-------------------------------------------------------------------------*/
u16 assembleSPICmd_682(u16 write, u16 address, u8 data)
{
	/* debug print to check input */
	// printk("assembleSPICmd_682 - write = %u, addr = 0x%x, data= 0x%x", write, address, data);
	u16 assembledCmd = 0; // Build this to shift through parity calculation
	u16 parity = 0; // Parity bit calculated here
	u16 packet = 0; // This will be what we send
	if(write)
	{
		assembledCmd |= 0x8000; // Set CMD = 1
	}
	assembledCmd |= (((address << 9) & 0x7E00) | (u16)(data & 0x00FF));
	packet = assembledCmd;
	// Calculate parity
	while(assembledCmd > 0)
	{
		// Count the number of 1s in the LSb
		if(assembledCmd & 0x0001)
		{
		parity++;
		}
		// Shift right
		assembledCmd >>= 1;
	}
	// If the LSb is a 0 (even # of 1s), we need to add the odd parity bit
	if(!(parity & 0x0001))
	{
		packet |= (1 << 8);
	}
	printk("assembleSPICmd_682 - packet 0x%x", packet);
	return(packet);
}


static ssize_t tps92682_transfer(struct spi_device *spi, u16 isWrite, u16 address, u8 data)
{
	u16 spi92682cmd = 0;
	ssize_t	status;
	struct spidev_data	*spidev = spi_get_drvdata(spi);
	int ret = 0;

	mutex_lock(&spidev->buf_lock);

	// AssembleSPI command
	spi92682cmd = assembleSPICmd_682(isWrite, address, data);
	
	memset(spidev->tx_buffer, 0, bufsiz);
	memset(spidev->rx_buffer, 0, bufsiz);
	memcpy(spidev->tx_buffer, &spi92682cmd, sizeof(spi92682cmd));

	// if(isWrite)
	// {
		ret = spi_write(spi,spidev->tx_buffer,2);
		if (ret) {
			printk("Failed writing: %d\n", ret);
		}
	// }else
	// {
	// 	ret = spi_read(spi,spidev->rx_buffer,2);
	// 	if (ret) {
	// 		printk("Failed reading: %d\n", ret);
	// 	}
	// }
	printk("Write/Read OK: %d\n", ret);
	mutex_unlock(&spidev->buf_lock);
	return status;
}

/*-------------------------------------------------------------------------*/
void tps92682_chip_init(struct spi_device *spi)
{
	struct spidev_data	*spidev = spi_get_drvdata(spi);
	int	retval = 0;
	u32	tmp;

	mutex_lock(&spidev->buf_lock);
	spi->mode = SPI_MODE_0;
	spi->bits_per_word = 16;
	retval = spi_setup(spi);
	if (retval < 0)
	{
		printk("spi mode 0x%x 0x%x setup failed\n", spi->mode, tmp);
	}
	else
		printk("spi mode 0x%x 0x%x setup ok\n", spi->mode, tmp);
	mutex_unlock(&spidev->buf_lock);
	/* allocate buffer for tx & rx msg */
	if (!spidev->tx_buffer) {
		spidev->tx_buffer = kmalloc(bufsiz, GFP_KERNEL);
		if (!spidev->tx_buffer) {
			printk("spidev->tx_buffer allocate failed");
			return;
		}
	}
	printk("tps92682_chip_init tx_buffer pointer 0x%x", spidev->tx_buffer);

	if (!spidev->rx_buffer) {
		spidev->rx_buffer = kmalloc(bufsiz, GFP_KERNEL);
		if (!spidev->rx_buffer) {
			printk("spidev->rx_buffer allocate failed");
			return;
		}
	}
	// /* First check */
	// tps92682_transfer(spi, 1, 0x00, 0x00);
	// // udelay(100);
	// tps92682_transfer(spi, 1, 0x00, 0x01);
	// // udelay(100);
	// tps92682_transfer(spi, 1, 0x00, 0x02);
	// // udelay(100);
	// tps92682_transfer(spi, 0, 0x00, 0x00);
	// // udelay(100);
	// tps92682_transfer(spi, 1, 0x00, 0x03);
	// // udelay(100);

	// tps92682_transfer(spi, 1, 0x00, 0xB0);
	// tps92682_transfer(spi, 0, 0x00, 0x00);
	// tps92682_transfer(spi, 0, 0x00, 0x00);

	// tps92682_transfer(spi, 0, 0x11, 0x00);
	// tps92682_transfer(spi, 0, 0x11, 0x00);

	// tps92682_transfer(spi, 0, 0x12, 0x00);
	// tps92682_transfer(spi, 0, 0x12, 0x00);

	// tps92682_transfer(spi, 1, 0x00, 0xB0);
	// tps92682_transfer(spi, 1, 0x00, 0xB0);

	// tps92682_transfer(spi, 0, 0x00, 0x00);
	// udelay(100);
	// tps92682_transfer(spi, 0, 0x00, 0x00);
	// udelay(100);
	// tps92682_transfer(spi, 1, 0x00, 0x0C);
	// udelay(100);
	// tps92682_transfer(spi, 0, 0x00, 0x00);
	// udelay(100);
	// tps92682_transfer(spi, 0, 0x00, 0x00);
	// udelay(100);
	/* Chip config */
	int i = 0;
	for (i = 0; i < NUM_OF_CONF; i++)
	{
		tps92682_transfer(spi, regConfig[i][REG_ACTION_IDX], regConfig[i][REG_ADDR_IDX], regConfig[i][REG_VAL_IDX]);
		// udelay(100);
	}
	return;
}

static const struct file_operations tps92682_fops = {
	.owner =	THIS_MODULE,
};

static int tps92682_probe(struct spi_device *spi)
{
	struct spidev_data	*spidev;
	int			status, ret;
	unsigned long		minor;
	int error;
	/*
	 * spidev should never be referenced in DT without a specific
	 * compatible string, it is a Linux implementation thing
	 * rather than a description of the hardware.
	 */
	WARN(spi->dev.of_node &&
	     of_device_is_compatible(spi->dev.of_node, "spidev"),
	     "%pOF: buggy DT: spidev listed directly in DT\n", spi->dev.of_node);

	/* Allocate driver data */
	spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
	if (!spidev)
		return -ENOMEM;
	printk("tps92682_probe spidev: 0x%x", spidev);
	/* Initialize the driver data */
	spidev->spi = spi;
	spin_lock_init(&spidev->spi_lock);
	mutex_init(&spidev->buf_lock);

	INIT_LIST_HEAD(&spidev->device_entry);

	/* If we can allocate a minor number, hook up this device.
	 * Reusing minors is fine so long as udev or mdev is working.
	 */
	mutex_lock(&device_list_lock);
	minor = find_first_zero_bit(minors, N_SPI_MINORS);
	if (minor < N_SPI_MINORS) {

		spidev->devt = MKDEV(SPIDEV_MAJOR, minor);

		spidev->dev = device_create(tps92682_spidev_class, &spi->dev, spidev->devt,
				    spidev, "tps92682_spidev%d.%d",
				    spi->master->bus_num, spi->chip_select);
		status = PTR_ERR_OR_ZERO(spidev->dev);
		if(IS_ERR(spidev->dev))
			printk("tps92682 device_create error");
		printk("tps92682_probe device_create with status: %d - %d - %d - %d - %x", status, spi->master->bus_num, spi->chip_select, spidev->devt,spidev->dev);
	} else {
		dev_dbg(&spi->dev, "no minor number available!\n");
		status = -ENODEV;
	}
	if (status == 0) {
		set_bit(minor, minors);
		list_add(&spidev->device_entry, &device_list);
	}
	mutex_unlock(&device_list_lock);

	spidev->speed_hz = spi->max_speed_hz;

	if (status == 0)
		spi_set_drvdata(spi, spidev);
	else
		kfree(spidev);

	struct gpio_desc *gpio_en = devm_gpiod_get_optional(spidev->dev, "en", GPIOD_OUT_HIGH);
	error = PTR_ERR_OR_ZERO(gpio_en);
	if (error) {
		printk("failed to request gpio: %d\n", error);
		return error;
	}
	printk("gpio val 1: %d\n", gpiod_get_value(gpio_en));
	// printk("tps92682 gpio_en ok");
	// gpiod_set_value(gpio_en, 1);
	// msleep(2000);
	// printk("gpio val 2: %d\n", gpiod_get_value(gpio_en));
	// printk("tps92682 gpio_en set ok");
	// struct gpio_desc *gpio_en;
	// int ret;
	// gpio_hog_lookup_name("p19", &gpio_en);
	// if (ret)
	// 	printk("Error gpio lookup");
	// printk("gpio val 2: %d\n", dm_gpio_get_value(gpio_en));
	tps92682_chip_init(spi);
	return status;
}

static int tps92682_remove(struct spi_device *spi)
{
	printk("tps92682_remove");
	struct spidev_data	*spidev = spi_get_drvdata(spi);

	/* prevent new opens */
	mutex_lock(&device_list_lock);
	/* make sure ops on existing fds can abort cleanly */
	spin_lock_irq(&spidev->spi_lock);
	spidev->spi = NULL;
	spin_unlock_irq(&spidev->spi_lock);

	list_del(&spidev->device_entry);
	device_destroy(tps92682_spidev_class, spidev->devt);
	clear_bit(MINOR(spidev->devt), minors);
	if (spidev->users == 0)
		kfree(spidev);
	mutex_unlock(&device_list_lock);

	return 0;
}

static struct spi_driver tps92682_spi_driver = {
	.driver = {
		.name =		"tps92682",
		.of_match_table = of_match_ptr(tps92682_dt_ids),
	},
	.probe =	tps92682_probe,
	.remove =	tps92682_remove,

	/* NOTE:  suspend/resume methods are not necessary here.
	 * We don't do anything except pass the requests to/from
	 * the underlying controller.  The refrigerator handles
	 * most issues; the controller driver handles the rest.
	 */
};

/*-------------------------------------------------------------------------*/

static int __init tps92682_spidev_init(void)
{
	int status;
	printk("tps92682_spidev_init");
	/* Claim our 256 reserved device numbers.  Then register a class
	 * that will key udev/mdev to add/remove /dev nodes.  Last, register
	 * the driver which manages those device numbers.
	 */
	// BUILD_BUG_ON(N_SPI_MINORS > 256);
	status = register_chrdev(SPIDEV_MAJOR, "tps92682_spi", &tps92682_fops);
	if (status < 0)
	{
		printk("tps92682_spidev_init register_chrdev with status: %d", status);
		return status;
	}

	tps92682_spidev_class = class_create(THIS_MODULE, "tps92682_spidev");
	if (IS_ERR(tps92682_spidev_class)) {
		unregister_chrdev(SPIDEV_MAJOR, tps92682_spi_driver.driver.name);
		printk("tps92682_spidev_init class_create with status: %d", status);
		return PTR_ERR(tps92682_spidev_class);
	}

	status = spi_register_driver(&tps92682_spi_driver);
	if (status < 0) {
		class_destroy(tps92682_spidev_class);
		unregister_chrdev(SPIDEV_MAJOR, tps92682_spi_driver.driver.name);
		printk("tps92682_spidev_init spi_register_driver with status: %d", status);
	}
	printk("tps92682_spidev_init done with status: %d", status);

	return status;
}
module_init(tps92682_spidev_init);

static void __exit tps92682_spidev_exit(void)
{
	printk("tps92682_spidev_exit");
	spi_unregister_driver(&tps92682_spi_driver);
	class_destroy(tps92682_spidev_class);
	unregister_chrdev(SPIDEV_MAJOR, tps92682_spi_driver.driver.name);
}
module_exit(tps92682_spidev_exit);

/*-------------------------------------------------------------------------*/

MODULE_DESCRIPTION("SPI controller driver for TI TPS92682");
MODULE_AUTHOR("Loc Ta Vinh <LocTV3@fpt.com>");
MODULE_LICENSE("GPL");

Could you please help take a look to see if my implementation is ok? Are there any issue with it because it seems that the SPI communicate still not work.

I've captured the SPI data like below. I use logic analyzer to capture it.

Here you can see the SCK, MOSI and MISO.

The SCK and MOSI seems normal, but I don't see any value on the MISO line.

Could you please take a look about that. I'm not sure what I'm missing here.

Best regards,

Loc.