Post Go back to editing

Custom IP's linux kernel stuck at Device Tree Probing

Category: Software
Product Number: AD9375

Hi, I'm using ADI Transceiver Toolbox to genrate custom IP for ad9375+zc706 board. I have successfully built the IP core and compile it with hdl workflow and generated a `BOOT.bin` file. The MATLAB version is 2022b and vivado version is 2021.2.

In order to interface with this FPGA image, I need a software interface, which includes a iio driver patch for 2021_R2 linux kernel. I wrote the iio device driver similar to AD9361's frequency hopping example. My iio device is called txpower and I attach the .c code here.

/*
 * TX Power HDL CORE driver
 *
 * Copyright 2024
 *
 */

#include <linux/module.h>
// #include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/errno.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/io.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/clk.h>

#include <linux/of_device.h>
#include <linux/of_dma.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>

#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>

/* Transceiver TxPower Core Control */

// Writable
#define TXPOWER_TX_MAG		 		 	0x100
#define TXPOWER_NCO_INC					0x104
#define TXPOWER_LOOKBACK_DELAY				0x108
#define TXPOWER_TX_SEL					0x10C
#define TXPOWER_RX0DMA_SEL	    0x110
#define TXPOWER_RX1DMA_SEL	    0x114

struct cf_axi_txpower_state {
	struct iio_info		iio_info;
	void __iomem		*regs;
	unsigned int tx_mag;
	int nco_inc;
	unsigned int loopback_delay;
	unsigned int tx_sel;
	unsigned int rx0dma_sel;
	unsigned int rx1dma_sel;
};

enum {
	CF_AXI_TXPOWER_TX_MAG,
	CF_AXI_TXPOWER_NCO_INC,
	CF_AXI_TXPOWER_LOOKBACK_DELAY,
	CF_AXI_TXPOWER_TX_SEL,
	CF_AXI_TXPOWER_RX0DMA_SEL,
	CF_AXI_TXPOWER_RX1DMA_SEL,
};

static inline void txpower_write(struct cf_axi_txpower_state *st,
			     unsigned reg, unsigned val)
{
	iowrite32(val, st->regs + reg);
}

static inline unsigned int txpower_read(struct cf_axi_txpower_state *st, unsigned reg)
{
	return ioread32(st->regs + reg);
}

static inline void txpower_write_config(struct cf_axi_txpower_state *st)
{
	// Set defaults
	st->tx_mag = 16383;
	st->nco_inc = 0;
	st->loopback_delay = 0;
	st->tx_sel = 1;
	st->rx0dma_sel = 1;
	st->rx1dma_sel = 1;

	// txpower_write(st, TXPOWER_TX_MAG, st->tx_mag);
	// txpower_write(st, TXPOWER_NCO_INC, st->nco_inc);
	txpower_write(st, TXPOWER_LOOKBACK_DELAY, st->loopback_delay);
	// txpower_write(st, TXPOWER_TX_SEL, st->tx_sel);
	// txpower_write(st, TXPOWER_RX0DMA_SEL, st->rx0dma_sel);
	// txpower_write(st, TXPOWER_RX1DMA_SEL, st->rx1dma_sel);
}

static ssize_t cf_axi_txpower_store(struct device *dev,
				struct device_attribute *attr,
				const char *buf, size_t len)
{
	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
	struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
	struct cf_axi_txpower_state *st = iio_priv(indio_dev);
	unsigned long readin;
	int ret = 0;

	mutex_lock(&indio_dev->mlock);
	switch ((u32)this_attr->address) {
		case CF_AXI_TXPOWER_TX_MAG:
			ret = kstrtoul(buf, 0, &readin);
			if (ret)
				break;
			st->tx_mag = readin;
			txpower_write(st, TXPOWER_TX_MAG, readin);
			break;
		case CF_AXI_TXPOWER_NCO_INC:
			ret = kstrtoul(buf, 0, &readin);
			if (ret)
				break;
			st->nco_inc = readin;
			txpower_write(st, TXPOWER_NCO_INC, readin);
			break;
		case CF_AXI_TXPOWER_LOOKBACK_DELAY:
			ret = kstrtoul(buf, 0, &readin);
			if (ret)
				break;
			if (readin > 400)
			{
				ret = -EINVAL;
				break;
			}
			st->loopback_delay = readin;
			txpower_write(st, TXPOWER_LOOKBACK_DELAY, readin);
			break;
		case CF_AXI_TXPOWER_TX_SEL:
			ret = kstrtoul(buf, 0, &readin);
			if (ret)
				break;
			if (readin > 8)
			{
				ret = -EINVAL;
				break;
			}
			st->tx_sel = readin;
			txpower_write(st, TXPOWER_TX_SEL, readin);
			break;
		case CF_AXI_TXPOWER_RX0DMA_SEL:
			ret = kstrtoul(buf, 0, &readin);
			if (ret)
				break;
			if (readin > 8)
			{
				ret = -EINVAL;
				break;
			}
			st->rx0dma_sel = readin;
			txpower_write(st, TXPOWER_RX0DMA_SEL, readin);
			break;
		case CF_AXI_TXPOWER_RX1DMA_SEL:
			ret = kstrtoul(buf, 0, &readin);
			if (ret)
				break;
			if (readin > 8)
			{
				ret = -EINVAL;
				break;
			}
			st->rx1dma_sel = readin;
			txpower_write(st, TXPOWER_RX1DMA_SEL, readin);
			break;
		default:
			ret = -ENODEV;
	}
	mutex_unlock(&indio_dev->mlock);

	return ret ? ret : len;
}

static ssize_t cf_axi_txpower_show(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{
	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
	struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
	struct cf_axi_txpower_state *st = iio_priv(indio_dev);
	int ret;

	mutex_lock(&indio_dev->mlock);
	switch ((u32)this_attr->address) {

	case CF_AXI_TXPOWER_TX_MAG:
		ret = sprintf(buf, "%d\n", st->tx_mag);
		break;
	case CF_AXI_TXPOWER_NCO_INC:
		ret = sprintf(buf, "%d\n", st->nco_inc);
		break;
	case CF_AXI_TXPOWER_LOOKBACK_DELAY:
		ret = sprintf(buf, "%d\n", st->loopback_delay);
		break;
	case CF_AXI_TXPOWER_TX_SEL:
		ret = sprintf(buf, "%d\n", st->tx_sel);
		break;
	case CF_AXI_TXPOWER_RX0DMA_SEL:
		ret = sprintf(buf, "%d\n", st->rx0dma_sel);
		break;
	case CF_AXI_TXPOWER_RX1DMA_SEL:
		ret = sprintf(buf, "%d\n", st->rx1dma_sel);
		break;
	default:
		ret = -ENODEV;
	}
	mutex_unlock(&indio_dev->mlock);

	return ret;
}

static IIO_DEVICE_ATTR(tx_mag, S_IRUGO | S_IWUSR,
			cf_axi_txpower_show,
			cf_axi_txpower_store,
			CF_AXI_TXPOWER_TX_MAG);

static IIO_DEVICE_ATTR(nco_inc, S_IRUGO | S_IWUSR,
			cf_axi_txpower_show,
			cf_axi_txpower_store,
			CF_AXI_TXPOWER_NCO_INC);

static IIO_DEVICE_ATTR(loopback_delay, S_IRUGO | S_IWUSR,
			cf_axi_txpower_show,
			cf_axi_txpower_store,
			CF_AXI_TXPOWER_LOOKBACK_DELAY);

static IIO_DEVICE_ATTR(tx_sel, S_IRUGO | S_IWUSR,
			cf_axi_txpower_show,
			cf_axi_txpower_store,
			CF_AXI_TXPOWER_TX_SEL);

static IIO_DEVICE_ATTR(rx0dma_sel, S_IRUGO | S_IWUSR,
			cf_axi_txpower_show,
			cf_axi_txpower_store,
			CF_AXI_TXPOWER_RX0DMA_SEL);

static IIO_DEVICE_ATTR(rx1dma_sel, S_IRUGO | S_IWUSR,
			cf_axi_txpower_show,
			cf_axi_txpower_store,
			CF_AXI_TXPOWER_RX1DMA_SEL);

static struct attribute *cf_axi_txpower_attributes[] = {
	&iio_dev_attr_tx_mag.dev_attr.attr,
	&iio_dev_attr_nco_inc.dev_attr.attr,
	&iio_dev_attr_loopback_delay.dev_attr.attr,
	&iio_dev_attr_tx_sel.dev_attr.attr,
	&iio_dev_attr_rx0dma_sel.dev_attr.attr,
	&iio_dev_attr_rx1dma_sel.dev_attr.attr,
	NULL,
};

static const struct attribute_group cf_axi_txpower_attribute_group = {
	.attrs = cf_axi_txpower_attributes,
};

static int cf_axi_txpower_reg_access(struct iio_dev *indio_dev,
			      unsigned reg, unsigned writeval,
			      unsigned *readval)
{
	struct cf_axi_txpower_state *st = iio_priv(indio_dev);
	int ret;

	mutex_lock(&indio_dev->mlock);
	if (readval == NULL) {
		txpower_write(st, reg & 0xFFFF, writeval);
		ret = 0;
	} else {
		ret = txpower_read(st, reg & 0xFFFF);
		*readval = ret;
		ret = 0;
	}

	mutex_unlock(&indio_dev->mlock);

	return ret;
}

static const struct iio_info cf_axi_txpower_info = {
	.debugfs_reg_access = &cf_axi_txpower_reg_access,
	.attrs = &cf_axi_txpower_attribute_group,
};

/* Match table for of_platform binding */
static const struct of_device_id cf_axi_txpower_of_match[] = {
	{ .compatible = "adi,axi-txpower-1.00", .data = 0},
	{ },
};
MODULE_DEVICE_TABLE(of, cf_axi_txpower_of_match);

static int cf_axi_txpower_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
	struct cf_axi_txpower_state *st;
	struct iio_dev *indio_dev;
	struct resource *res;
	int ret;

	dev_err(&pdev->dev, "Device Tree Probing \'%s\'\n",
			np->name);

	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*st));
	if (!indio_dev)
		return -ENOMEM;

	st = iio_priv(indio_dev);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	st->regs = devm_ioremap(&pdev->dev, res->start, resource_size(res));
	if (!st->regs)
		return -ENOMEM;

	indio_dev->dev.parent = &pdev->dev;
	indio_dev->name = np->name;
	indio_dev->modes = INDIO_DIRECT_MODE;

	indio_dev->info = &cf_axi_txpower_info;

	dev_info(&pdev->dev, "IO Map resouce: start %d, end %d.\n",
			 res->start, res->end);

	txpower_write_config(st);

	ret = iio_device_register(indio_dev);
	if (ret < 0)
		return ret;

	dev_info(&pdev->dev, "Analog Devices CF_AXI_TXPOWER Mapped\n");

	platform_set_drvdata(pdev, indio_dev);

	return 0;
}

static int cf_axi_txpower_remove(struct platform_device *pdev)
{
	struct iio_dev *indio_dev = platform_get_drvdata(pdev);

	iio_device_unregister(indio_dev);

	return 0;
}

static struct platform_driver cf_axi_txpower_driver = {
	.driver = {
		.name = "cf_axi_txpower",
		.of_match_table = cf_axi_txpower_of_match,
	},
	.probe		= cf_axi_txpower_probe,
	.remove		= cf_axi_txpower_remove,
};
module_platform_driver(cf_axi_txpower_driver);

MODULE_AUTHOR("XX");
MODULE_DESCRIPTION("TxPower HDL CORE driver");
MODULE_LICENSE("GPL v2");

Simulink hdlwork flow interface is show here, showing the offset for all AXI4-Lite registers:

I checked the vivado project to find out the base address for custom HDL IP core AXI4 Lite, it goes 0x50000000 and the range is until 0xffff.

  

Therefore, similar to frequency hoping example, I use `dtc` tool to modify the devicetree and add the axi-txpower@50000000:

The ulimage is successfully generate but problem occur when booting, when the boot runs to probing my `txpower` iio device, it stucks and have no response. It stops when trying to run the `txpower_write_config` function.

To know what's going on, I commented line 81 to line 86 in my txpower.c file, and this time it boots with no problem. Therefore it seems there is a problem when writing the axi4 registers. If it ever runs to  `iowrite32` call in 

`txpower_write` function, the boot process stops and have no response.
 
So is there any thing wrong with my settings? I look forward to any help I can get!