1. 程式人生 > >linux spi主機控制器pl022驅動註冊以及匹配裝置過程

linux spi主機控制器pl022驅動註冊以及匹配裝置過程

最近看海思的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提供的片內外設可以從官網檢視: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0224b/index.html 由於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: [email protected] {
				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: [email protected] {
				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: [email protected] {
				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: [email protected] {
				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>;
			};