linux spi主機控制器pl022驅動註冊以及匹配裝置過程
最近看海思的spi比較多,海思3519的spi ip使用的時ARM提供的pl022,這裡對pl022驅動註冊和匹配裝置樹中的裝置這個過程捋一下。
pl022是ARM提供的片內外設,很多廠商都用了這個ip,只在一些細小的區別。所以它的驅動也是非常通用的。pl022的手冊可以看這裡點選開啟連結
我們需要首先了解amba匯流排。本段摘自https://blog.csdn.net/yuanlulu/article/details/7339836
*
* 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相關的資料結構
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>;
};