linux spi主机控制器pl022驱动注册以及匹配设备过程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012142460/article/details/80868493

最近看海思的spi比较多,海思3519的spi ip使用的时ARM提供的pl022,这里对pl022驱动注册和匹配设备树中的设备这个过程捋一下。

pl022是ARM提供的片内外设,很多厂商都用了这个ip,只在一些细小的区别。所以它的驱动也是非常通用的。pl022的手册可以看这里点击打开链接

我们需要首先了解amba总线。本段摘自https://blog.csdn.net/yuanlulu/article/details/7339836


内核中专门定义了一类amba_bustype、amba_device、amba_driver。具体定义在内核的 /drivers/amba/bus.c中。
按理说AMBA是一种片内总线,对驱动程序员来说是透明的,为什么还要定义一类amba_device/amba_driver呢?
看看内核源码中的解释:
  linux/include/amba/bus.h
*
*  This device type deals with ARM PrimeCells and anything else that
*  presents a proper CID ( 0xB105F00D ) at the end of the I/O register
*  region or that is derived from a   PrimeCell.
也就是说amba_device定义的是ARM的PrimeCells提供的片内外设,当然这些外设都使用AMBA总线。这些外设有一个特征,那就是在自己的IO地址空间的尾部存放了一个固定的CID( 0xB105F00D),表明这是一个amba_device。
由于ARM众多的合作伙伴都会或多或少的使用ARM提供的片内外设,所以众多厂商的ARM处理器的一些外设可以使用相同的驱动,只有IO地址空间和中断号的区别,寄存器和操作方式都是类似的。为了管理这类驱动,内核中专门建立了amba子系统。CID正是为了向驱动表明这是一个amba_device。但是仅仅表明这是一个amba_device还是不够的,因为amba_device包括了lcd控制器、ssp、中断控制器等多种设备。为了让ambe驱动识别设备的类型,amba_device在自己IO地址空间的尾部还存放了一个四字节的 periphid,内核使用它来确认是否可以使用标准的amba驱动。
关于periphid的定义,网上有人做了总结: http://hi.baidu.com/serial_story/blog/item/8af9a216cdd1495af2de327d.html
先说一下ARM对外设的编号采用PLXXX的形式,比如SSP使用PL022的编号。
下面说一下periphid各位的含义:
PartNumber[11:0]         设备编号,比如ssp(PL022)这部分的编号就是0x022.
DesignerID[19:12]         设计厂商编号,如果是ARM设计的对应0x41(ASCII码是‘A’)
Revision[23:20]             版本号,从0开始编号
Configuration[31:24]      配置选项,一般都是0


下面来看一下amba相关的数据结构

struct amba_id {
	unsigned int		id;
	unsigned int		mask;
	void			*data;
};

amba id 结构,这个id是用来匹配amba 驱动和amba设备的。


struct amba_driver {
	struct device_driver	drv;
	int			(*probe)(struct amba_device *, const struct amba_id *);
	int			(*remove)(struct amba_device *);
	void			(*shutdown)(struct amba_device *);
	int			(*suspend)(struct amba_device *, pm_message_t);
	int			(*resume)(struct amba_device *);
	const struct amba_id	*id_table;
};

几个方法是驱动中常见的方法,最后这个参数比较重要,是一个id表,表示的是这个驱动所支持的设备表。以此来支持多款设备的相同ip。


struct amba_device {
	struct device		dev;
	struct resource		res;
	struct clk		*pclk;
	unsigned int		periphid;
	unsigned int		irq[AMBA_NR_IRQS];
};
amba设备结构体

periphid 外设id,用来和驱动中的设备表中id匹配

接口:

int amba_driver_register(struct amba_driver *);

驱动注册函数,注册时会去匹配是否有对应的设备已经存在,存在的话就会执行amba_driver中的probe函数。匹配就是通过刚才说的amba_id中的id参数。我们来追一下这个过程。

int amba_driver_register(struct amba_driver *drv)
{
	drv->drv.bus = &amba_bustype;

#define SETFN(fn)	if (drv->fn) drv->drv.fn = amba_##fn
	SETFN(probe);
	SETFN(remove);
	SETFN(shutdown);

	return driver_register(&drv->drv);     //驱动注册
}

进入驱动注册函数

/**
 * driver_register - register driver with bus
 * @drv: driver to register
 *
 * We pass off most of the work to the bus_add_driver() call,
 * since most of the things we have to do deal with the bus
 * structures.
 */
int driver_register(struct device_driver *drv)
{
	int ret;
	struct device_driver *other;

	BUG_ON(!drv->bus->p);

	if ((drv->bus->probe && drv->probe) ||
	    (drv->bus->remove && drv->remove) ||
	    (drv->bus->shutdown && drv->shutdown))
		printk(KERN_WARNING "Driver '%s' needs updating - please use "
			"bus_type methods\n", drv->name);

	other = driver_find(drv->name, drv->bus);
	if (other) {
		printk(KERN_ERR "Error: Driver '%s' is already registered, "
			"aborting...\n", drv->name);
		return -EBUSY;
	}

	ret = bus_add_driver(drv);     //添加驱动
	if (ret)
		return ret;
	ret = driver_add_groups(drv, drv->groups);
	if (ret) {
		bus_remove_driver(drv);
		return ret;
	}
	kobject_uevent(&drv->p->kobj, KOBJ_ADD);

	return ret;
}

驱动添加函数

/**
 * bus_add_driver - Add a driver to the bus.
 * @drv: driver.
 */
int bus_add_driver(struct device_driver *drv)
{
	struct bus_type *bus;
	struct driver_private *priv;
	int error = 0;

	bus = bus_get(drv->bus);
	if (!bus)
		return -EINVAL;

	pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);

	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv) {
		error = -ENOMEM;
		goto out_put_bus;
	}
	klist_init(&priv->klist_devices, NULL, NULL);
	priv->driver = drv;
	drv->p = priv;
	priv->kobj.kset = bus->p->drivers_kset;
	error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
				     "%s", drv->name);
	if (error)
		goto out_unregister;

	klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
	if (drv->bus->p->drivers_autoprobe) {
		error = driver_attach(drv);      //继续追这里
		if (error)
			goto out_unregister;
	}
	module_add_driver(drv->owner, drv);

	error = driver_create_file(drv, &driver_attr_uevent);
	if (error) {
		printk(KERN_ERR "%s: uevent attr (%s) failed\n",
			__func__, drv->name);
	}
	error = driver_add_groups(drv, bus->drv_groups);
	if (error) {
		/* How the hell do we get out of this pickle? Give up */
		printk(KERN_ERR "%s: driver_create_groups(%s) failed\n",
			__func__, drv->name);
	}

	if (!drv->suppress_bind_attrs) {
		error = add_bind_files(drv);
		if (error) {
			/* Ditto */
			printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
				__func__, drv->name);
		}
	}

	return 0;

out_unregister:
	kobject_put(&priv->kobj);
	kfree(drv->p);
	drv->p = NULL;
out_put_bus:
	bus_put(bus);
	return error;
}

这里会遍历每一个设备进行匹配

int driver_attach(struct device_driver *drv)
{
	return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}

进入__driver_attch函数中

static int __driver_attach(struct device *dev, void *data)
{
	struct device_driver *drv = data;

	/*
	 * Lock device and try to bind to it. We drop the error
	 * here and always return 0, because we need to keep trying
	 * to bind to devices and some drivers will return an error
	 * simply if it didn't support the device.
	 *
	 * driver_probe_device() will spit a warning if there
	 * is an error.
	 */

	if (!driver_match_device(drv, dev))  //进行匹配
		return 0;

	if (dev->parent)	/* Needed for USB */
		device_lock(dev->parent);
	device_lock(dev);
	if (!dev->driver)
		driver_probe_device(drv, dev);   //执行probe方法
	device_unlock(dev);
	if (dev->parent)
		device_unlock(dev->parent);

	return 0;
}

我们来看看是如何匹配的

static inline int driver_match_device(struct device_driver *drv,
				      struct device *dev)
{
	return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

我们得找到amba总线上匹配函数。匹配函数是在之前的amba_driver_register中赋值的

int amba_driver_register(struct amba_driver *drv)
{
	drv->drv.bus = &amba_bustype;

#define SETFN(fn)	if (drv->fn) drv->drv.fn = amba_##fn
	SETFN(probe);
	SETFN(remove);
	SETFN(shutdown);

	return driver_register(&drv->drv);
}
从这里找到真正的匹配函数是amba_match
struct bus_type amba_bustype = {
	.name		= "amba",
	.dev_attrs	= amba_dev_attrs,
	.match		= amba_match,     //匹配函数
	.uevent		= amba_uevent,
	.pm		= &amba_pm,
};

看看这个amba_match的实现

static int amba_match(struct device *dev, struct device_driver *drv)
{
	struct amba_device *pcdev = to_amba_device(dev);
	struct amba_driver *pcdrv = to_amba_driver(drv);

	return amba_lookup(pcdrv->id_table, pcdev) != NULL;
}

amba_lookup是匹配的过程

static const struct amba_id *
amba_lookup(const struct amba_id *table, struct amba_device *dev)
{
	int ret = 0;

	while (table->mask) {
		ret = (dev->periphid & table->mask) == table->id;
		if (ret)
			break;
		table++;
	}

	return ret ? table : NULL;
}

看来实现还是很简单的,就是amba_dev->periphid和table->id在比较。到这里设备和驱动就算是匹配上了。

匹配上之后需要找到要执行的probe函数,继续回到static int __driver_attach(struct device *dev, void *data)函数中,匹配成功后寻找probe函数

int driver_probe_device(struct device_driver *drv, struct device *dev)
{
	int ret = 0;

	if (!device_is_registered(dev))
		return -ENODEV;

	pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
		 drv->bus->name, __func__, dev_name(dev), drv->name);

	pm_runtime_barrier(dev);
	ret = really_probe(dev, drv);    //继续寻找
	pm_request_idle(dev);

	return ret;
}
static int really_probe(struct device *dev, struct device_driver *drv)
{
	int ret = 0;
	int local_trigger_count = atomic_read(&deferred_trigger_count);

	atomic_inc(&probe_count);
	pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
		 drv->bus->name, __func__, drv->name, dev_name(dev));
	WARN_ON(!list_empty(&dev->devres_head));

	dev->driver = drv;

	/* If using pinctrl, bind pins now before probing */
	ret = pinctrl_bind_pins(dev);
	if (ret)
		goto probe_failed;

	if (driver_sysfs_add(dev)) {
		printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
			__func__, dev_name(dev));
		goto probe_failed;
	}

	if (dev->bus->probe) {    //设备总线中注册了probe函数
		ret = dev->bus->probe(dev);
		if (ret)
			goto probe_failed;
	} else if (drv->probe) {   //设备总线没有注册probe函数,查看驱动中是否注册了probe函数
		ret = drv->probe(dev);
		if (ret)
			goto probe_failed;
	}

	driver_bound(dev);
	ret = 1;
	pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
		 drv->bus->name, __func__, dev_name(dev), drv->name);
	goto done;

probe_failed:
	devres_release_all(dev);
	driver_sysfs_remove(dev);
	dev->driver = NULL;
	dev_set_drvdata(dev, NULL);

	if (ret == -EPROBE_DEFER) {
		/* Driver requested deferred probing */
		dev_info(dev, "Driver %s requests probe deferral\n", drv->name);
		driver_deferred_probe_add(dev);
		/* Did a trigger occur while probing? Need to re-trigger if yes */
		if (local_trigger_count != atomic_read(&deferred_trigger_count))
			driver_deferred_probe_trigger();
	} else if (ret != -ENODEV && ret != -ENXIO) {
		/* driver matched but the probe failed */
		printk(KERN_WARNING
		       "%s: probe of %s failed with error %d\n",
		       drv->name, dev_name(dev), ret);
	} else {
		pr_debug("%s: probe of %s rejects match %d\n",
		       drv->name, dev_name(dev), ret);
	}
	/*
	 * Ignore errors returned by ->probe so that the next driver can try
	 * its luck.
	 */
	ret = 0;
done:
	atomic_dec(&probe_count);
	wake_up(&probe_waitqueue);
	return ret;
}

整个的路径:driver_register -> bus_add_driver -> driver_attach -> __driver_attach(对每个设备) -> driver_probe_device -> drv->bus->match(dev, drv)(检查这个设备是否与新注册的驱动匹配) ->    really_probe -> dev->bus->probe(dev)(如果存在) (否则) -> drv->probe(dev)。

对于pl022驱动来讲,我们看看它的具体情况

首先是要实现一个amba_driver

static struct amba_driver pl022_driver = {
	.drv = {
		.name	= "ssp-pl022",
		.pm	= &pl022_dev_pm_ops,
	},
	.id_table	= pl022_ids,
	.probe		= pl022_probe,
	.remove		= pl022_remove,
};

看一看它的pl022_ids,table表的最后一定要置0,表示结束。

static struct amba_id pl022_ids[] = {
	{
		/*
		 * ARM PL022 variant, this has a 16bit wide
		 * and 8 locations deep TX/RX FIFO
		 */
		.id	= 0x00041022,
		.mask	= 0x000fffff,
		.data	= &vendor_arm,
	},
	{
		/*
		 * ST Micro derivative, this has 32bit wide
		 * and 32 locations deep TX/RX FIFO
		 */
		.id	= 0x01080022,
		.mask	= 0xffffffff,
		.data	= &vendor_st,
	},
	{
		/*
		 * ST-Ericsson derivative "PL023" (this is not
		 * an official ARM number), this is a PL022 SSP block
		 * stripped to SPI mode only, it has 32bit wide
		 * and 32 locations deep TX/RX FIFO but no extended
		 * CR0/CR1 register
		 */
		.id	= 0x00080023,
		.mask	= 0xffffffff,
		.data	= &vendor_st_pl023,
	},
	{
		/*
		 * PL022 variant that has a chip select control register whih
		 * allows control of 5 output signals nCS[0:4].
		 */
		.id	= 0x000b6022,
		.mask	= 0x000fffff,
		.data	= &vendor_lsi,
	},
	{
		/*
		 * Hisilicon derivative, this has a 16bit wide
		 * and 256 locations deep TX/RX FIFO
		 */
		.id	= 0x00800022,
		.mask	= 0xffffffff,
		.data	= &vendor_hisi,
	},
	{ 0, 0 },
};

最后一个是海思的id。

这个id是和设备树对应的,海思的设备树如下,periphid是和上面的id相互匹配的。

			spi_bus0: spi@12120000 {
				compatible = "arm,pl0229", "arm,primecell";
				arm,primecell-periphid = <0x00800022>;
				reg = <0x12120000 0x1000>;
			/*	dmas = <&dmac 1 &dmac 2>;*/
				interrupts = <0 9 4>;
				clocks = <&clock HI3519_SPI0_CLK>;
				clock-names = "apb_pclk";
				dmas = <&dmac 13 1>,<&dmac 12 2>;
				dma-names = "tx","rx";
				status = "disabled";
				num-cs = <1>;
				#address-cells = <1>;
				#size-cells = <0>;
			};

			spi_bus1: spi@12121000 {
				compatible = "arm,pl022", "arm,primecell";
				arm,primecell-periphid = <0x00800022>;
				reg = <0x12121000 0x1000>, <0x12030004 0x4>;
				/*dmas = <&dmac 1 &dmac 2>;*/
				interrupts = <0 10 4>;
				clocks = <&clock HI3519_SPI1_CLK>;
				clock-names = "apb_pclk";
				dmas = <&dmac 14 1>, <&dmac 15 2>;
				dma-names = "tx","rx";
				status = "disabled";
				num-cs = <2>;
				#address-cells = <1>;
				#size-cells = <0>;
				hisi,spi_cs_sb = <26>;
				hisi,spi_cs_mask_bit = <0x0c000000>;
			};

			spi_bus2: spi@12122000 {
				compatible = "arm,pl022", "arm,primecell";
				arm,primecell-periphid = <0x00800022>;
				reg = <0x12122000 0x1000>;
			/*	dmas = <&dmac 1 &dmac 2>;*/
				interrupts = <0 11 4>;
				clocks = <&clock HI3519_SPI2_CLK>;
				clock-names = "apb_pclk";
				dmas = <&dmac 13 1>,<&dmac 12 2>;
				dma-names = "tx","rx";
				status = "disabled";
				num-cs = <1>;
				#address-cells = <1>;
				#size-cells = <0>;
			};

			spi_bus3: spi@12123000 {
				compatible = "arm,pl022", "arm,primecell";
				arm,primecell-periphid = <0x00800022>;
				reg = <0x12123000 0x1000>;
				interrupts = <0 12 4>;
				clocks = <&clock HI3519_SPI3_CLK>;
				clock-names = "apb_pclk";
				dmas = <&dmac 12 1>,<&dmac 13 2>;
				dma-names = "tx","rx";
				status = "disabled";
				num-cs = <1>;
				#address-cells = <1>;
				#size-cells = <0>;
			};




猜你喜欢

转载自blog.csdn.net/u012142460/article/details/80868493
今日推荐