kernel spi framework details

来源:互联网 发布:彩票关注cms 编辑:程序博客网 时间:2024/06/11 13:06

SPIdevices have a limited userspace API, supporting basic half-duplex

read()and write() access to SPI slave devices. Using ioctl() requests,

fullduplex transfers and device I/O configuration are also available.


#include<fcntl.h>

#include<unistd.h>

#include<sys/ioctl.h>

#include<linux/types.h>

#include<linux/spi/spidev.h>


Somereasons you might want to use this programming interface include:


*Prototyping in an environment that's not crash-prone; stray pointers

inuserspace won't normally bring down any Linux system.


*Developing simple protocols used to talk to microcontrollers acting

asSPI slaves, which you may need to change quite often.


Ofcourse there are drivers that can never be written in userspace,because

theyneed to access kernel interfaces (such as IRQ handlers or otherlayers

ofthe driver stack) that are not accessible to userspace.



DEVICECREATION, DRIVER BINDING

===============================

Thesimplest way to arrange to use this driver is to just list it in the

spi_board_infofor a device as the driver it should use: the "modalias"

entryis "spidev", matching the name of the driver exposing thisAPI.

Setup the other device characteristics (bits per word, SPI clocking,

chipselectpolarity, etc) as usual, so you won't always need to override

themlater.


(Sysfsalso supports userspace driven binding/unbinding of drivers to

devices. That mechanism might be supported here in the future.)


Whenyou do that, the sysfs node for the SPI device will include a child

devicenode with a "dev" attribute that will be understood by udevor mdev.

(Largersystems will have "udev". Smaller ones may configure"mdev" into

busybox;it's less featureful, but often enough.) For a SPI device with

chipselectC on bus B, you should see:


/dev/spidevB.C... character special device, major number 153 with

adynamically chosen minor device number. This is the node

thatuserspace programs will open,createdby "udev" or "mdev".


/sys/devices/.../spiB.C... as usual, the SPI device node will

bea child of its SPI master controller.


/sys/class/spidev/spidevB.C... created when the "spidev" driver

bindsto that device. (Directory or symlink, based on whether

ornot you enabled the "deprecated sysfs files" Kconfigoption.)


Donot try to manage the /dev character device special file nodes byhand.

That'serror prone, and you'd need to pay careful attention to system

securityissues; udev/mdev should already be configured securely.


Ifyou unbind the "spidev" driver from that device, those two"spidev" nodes

(insysfs and in /dev) should automatically be removed (respectively bythe

kerneland by udev/mdev). You can unbind by removing the "spidev"driver

module,which will affect all devices using this driver. You can also unbind

byhaving kernel code remove the SPI device, probably by removing thedriver

forits SPI controller (so its spi_master vanishes).


Sincethis is a standard Linux device driver -- even though it just happens

toexpose a low level API to userspace -- it can be associated with anynumber

ofdevices at a time. Just provide one spi_board_info record for eachsuch

SPIdevice, and you'll get a /dev device node for each device.



BASICCHARACTER DEVICE API

==========================

Normalopen() and close() operations on /dev/spidevB.D files work as you

wouldexpect.


Standardread()and write()operations are obviously only half-duplex,and

thechipselect is deactivated between those operations.Full-duplexaccess,

andcomposite operation without chipselect de-activation, is availableusing

theSPI_IOC_MESSAGE(N) request.


Severalioctl() requests let your driver read or override the device'scurrent

settingsfor data transfer parameters:


SPI_IOC_RD_MODE,SPI_IOC_WR_MODE ... pass a pointer to a byte which will

return(RD) or assign (WR) the SPI transfer mode. Use the constants

SPI_MODE_0..SPI_MODE_3;or if you prefer you can combine SPI_CPOL

(clockpolarity, idle high iff this is set) or SPI_CPHA (clock phase,

sampleon trailing edge iff this is set) flags.


SPI_IOC_RD_LSB_FIRST,SPI_IOC_WR_LSB_FIRST ... pass a pointer to a byte

whichwill return (RD) or assign (WR) the bit justification used to

transferSPI words. Zero indicates MSB-first; other values indicate

theless common LSB-first encoding. In both cases the specified value

isright-justified in each word, so that unused (TX) or undefined (RX)

bitsare in the MSBs.


SPI_IOC_RD_BITS_PER_WORD,SPI_IOC_WR_BITS_PER_WORD ... pass a pointer to

abyte which will return (RD) or assign (WR) the number of bits in

eachSPI transfer word. The value zero signifies eight bits.


SPI_IOC_RD_MAX_SPEED_HZ,SPI_IOC_WR_MAX_SPEED_HZ ... pass a pointer to a

u32which will return (RD) or assign (WR) the maximum SPI transfer

speed,in Hz. The controller can't necessarily assign that specific

clockspeed.


NOTES:


-At this time there is no async I/O support; everything is purely

synchronous.


-There's currently no way to report the actual bit rate used to

shiftdata to/from a given device.


-From userspace, you can't currently change the chip select polarity;

thatcould corrupt transfers to other devices sharing the SPI bus.

EachSPI device is deselected when it's not in active use, allowing

otherdrivers to talk to other devices.


-There's a limit on the number of bytes each I/O request can transfer

tothe SPI device. It defaults to one page, but that can be changed

usinga module parameter.


-BecauseSPI has no low-level transfer acknowledgement, you usually

won'tsee any I/O errors when talking to a non-existent device.



FULLDUPLEX CHARACTER DEVICE API

================================


Seethe spidev_fdx.c sample program for one example showing the use ofthe

fullduplex programming interface. (Although it doesn't perform a fullduplex

transfer.) The model is the same as that used in the kernel spi_sync()

request;the individual transfers offer the same capabilities as are

availableto kernel drivers (except that it's not asynchronous).


Theexample shows one half-duplex RPC-style request and response message.

Theserequests commonly require that the chip not be deselected between

therequest and response. Several such requests could be chained into

asingle kernel request, even allowing the chip to be deselected after

eachresponse. (Other protocol options include changing the word size

andbitrate for each transfer segment.)


Tomake a full duplex request, provide both rx_buf and tx_buf for the

sametransfer. It's even OK if those are the same buffer.




How do these driverprogramming interfaces work?

------------------------------------------------

The<linux/spi/spi.h> header file includes kerneldoc, as does the

main source code,and you should certainly read that chapter of the

kernel API document. This is just an overview, so you get the big

picture before thosedetails.


SPI requests alwaysgo into I/O queues. Requests for a given SPI device

are always executedin FIFO order, and complete asynchronously through

completioncallbacks. There are also some simple synchronous wrappers

for those calls,including ones for common transaction types like writing

a command and thenreading its response.


There are two typesof SPI driver, here called:


SPI控制器相当于片上设备了,一般由芯片的BSP提供。这里负责具体的寄存器操作,

并且可以使用DMA进行数据传输。当然,也可以使用GPIO进行bitbang操作。

Controller drivers... controllers may bebuilt intoSystem-On-Chip

processors, andoften support both Master and Slave roles.

These drivers touchhardware registers and may use DMA.

Or they can be PIObitbangers, needing just GPIO pins.


协议驱动,将数据传输给SPI控制器,让它来负责具体的数据传输。这里只负责基于

SPI总线基础之上的协议处理。

Protocol drivers... these pass messages through the controller

driver tocommunicate with a Slave or Master device on the

other side of anSPI link.


操作Flash设备,音频设备,触摸屏等都能接到SPI总线上,但是他们使用

了同一个SPI控制器进行数据传输。

So for example oneprotocol driver might talk to the MTD layer to export

data to filesystemsstored on SPI flash like DataFlash; and others might

control audiointerfaces, present touchscreen sensors as input interfaces,

or monitortemperature and voltage levels during industrial processing.

And those might allbe sharing the same controllerdriver.


A "structspi_device" encapsulates the master-side interface between

those two types ofdriver. At this writing, Linux has no slave side

programminginterface.


There is a minimalcore of SPI programming interfaces, focussing on

using the drivermodel to connect controller and protocol drivers using

devicetables provided by board specific initialization code. SPI

shows up in sysfs inseveral locations:


/sys/devices/.../CTLR ... physical node for a given SPI controller


/sys/devices/.../CTLR/spiB.C ... spi_device on bus "B",

chipselect C,accessed through CTLR.


/sys/bus/spi/devices/spiB.C ... symlink to that physical

.../CTLR/spiB.Cdevice


/sys/devices/.../CTLR/spiB.C/modalias ... identifies the driver

that should be usedwith this device (for hotplug/coldplug)


/sys/bus/spi/drivers/D ... driver for one or more spi*.* devices


/sys/class/spi_master/spiB ... symlink (or actual device node) to

a logical nodewhich could hold class related state for the

controller managingbus "B". All spiB.* devices share one

physical SPI bussegment, with SCLK, MOSI, and MISO.


Note that the actuallocation of the controller's class state depends

on whether youenabled CONFIG_SYSFS_DEPRECATED or not. At this time,

the onlyclass-specific state is the bus number ("B" in "spiB"),so

those /sys/classentries are only useful to quickly identify busses.



板级初始化代码声明SPI外设,也就是说,必须要板级初始化代码中

声明spi外设才行,即使芯片支持自动发现检举也不行,也要手工声明,

以便让内核知道。

How doesboard-specific init code declare SPI devices?

------------------------------------------------------

Linux needs severalkinds of information to properly configure SPI devices.

That information isnormally provided by board-specific code, even for

chips that dosupport some of automated discovery/enumeration.


DECLARE CONTROLLERS

对于SoC片上设备,一般采用平台设备来表示platform_device。平台设备包括

物理地址和中断号等数据。

The first kind ofinformation is a list of what SPI controllers exist.

For System-on-Chip(SOC) based boards, these will usually be platform

devices, and thecontroller may need some platform_data in order to

operate properly. The "struct platform_device" will include resources

like the physicaladdress of the controller's first register and its IRQ.



一般会在

/home/shell.albert/project/iMX6Build/linux-2.6-imx-rel_imx_3.0.101_4.1.1/arch/arm/mach-mx6

目录下会有一些板级声明文件:

shell.albert@yantai:~/project/iMX6Build/linux-2.6-imx-rel_imx_3.0.101_4.1.1/arch/arm/mach-mx6>ls -l board*

-rw-r--r-- 1shell.albert users 10470 Oct 16 2014 board-mx6dl_arm2.h

-rw-r--r-- 1shell.albert users 10876 Oct 16 2014 board-mx6dl_hdmidongle.h

-rw-r--r-- 1shell.albert users 11705 Oct 16 2014 board-mx6dl_sabresd.h

-rw-r--r-- 1shell.albert users 63944 Oct 16 2014 board-mx6q_arm2.c

-rw-r--r-- 1shell.albert users 10105 Oct 16 2014 board-mx6q_arm2.h

-rw-r--r-- 1shell.albert users 46028 May 15 10:00 board-mx6q_arm2.o

-rw-r--r-- 1shell.albert users 23172 Oct 16 2014 board-mx6q_hdmidongle.c

-rw-r--r-- 1shell.albert users 10683 Oct 16 2014 board-mx6q_hdmidongle.h

-rw-r--r-- 1shell.albert users 16940 May 15 10:00 board-mx6q_hdmidongle.o

-rw-r--r-- 1shell.albert users 49982 Oct 16 2014 board-mx6q_sabreauto.c

-rw-r--r-- 1shell.albert users 11503 Oct 16 2014 board-mx6q_sabreauto.h

-rw-r--r-- 1shell.albert users 38300 May 15 10:00 board-mx6q_sabreauto.o

-rw-r--r-- 1shell.albert users 39521 Oct 16 2014 board-mx6q_sabrelite.c

-rw-r--r-- 1shell.albert users 24012 May 15 10:00 board-mx6q_sabrelite.o

-rw-r--r-- 1shell.albert users 56107 Oct 16 2014 board-mx6q_sabresd.c

-rw-r--r-- 1shell.albert users 10470 Oct 16 2014 board-mx6q_sabresd.h

-rw-r--r-- 1shell.albert users 37136 May 15 10:00 board-mx6q_sabresd.o

-rwxr-xr-x 1shell.albert users 37550 Oct 16 2014 board-mx6sl_arm2.c

-rw-r--r-- 1shell.albert users 18844 Oct 16 2014 board-mx6sl_common.h

-rw-r--r-- 1shell.albert users 47047 Oct 16 2014 board-mx6sl_evk.c

-rw-r--r-- 1shell.albert users 11700 Oct 16 2014 board-mx6solo_sabreauto.h

在这里面加入spi控制器和spi设备的声明。

这里对于imx6的代码如下所示:



static voidspi_device_init(void)

{

spi_register_board_info(imx6_sabresd_spi_nor_device,

ARRAY_SIZE(imx6_sabresd_spi_nor_device));

}

/*!

* Board specificinitialization.

*/

这段就是板级特定初始化代码,向内核注册了N多种设备,包括I2CSPI等。

这个函数会在启动时被调用。

static void __initmx6_sabresd_board_init(void)

{

int i;

int ret;

struct clk *clko,*clko2;

struct clk*new_parent;

int rate;

structplatform_device *voutdev;


if (cpu_is_mx6q()){

mxc_iomux_v3_setup_multiple_pads(mx6q_sabresd_pads,

ARRAY_SIZE(mx6q_sabresd_pads));

if(enet_to_gpio_6) {

iomux_v3_cfg_tenet_gpio_pad =

MX6Q_PAD_GPIO_6__ENET_IRQ_TO_GPIO_6;

mxc_iomux_v3_setup_pad(enet_gpio_pad);

} else {

iomux_v3_cfg_ti2c3_pad =

MX6Q_PAD_GPIO_6__I2C3_SDA;

mxc_iomux_v3_setup_pad(i2c3_pad);

}

} else if(cpu_is_mx6dl()) {

mxc_iomux_v3_setup_multiple_pads(mx6dl_sabresd_pads,

ARRAY_SIZE(mx6dl_sabresd_pads));


if(enet_to_gpio_6) {

iomux_v3_cfg_tenet_gpio_pad =

MX6DL_PAD_GPIO_6__ENET_IRQ_TO_GPIO_6;

mxc_iomux_v3_setup_pad(enet_gpio_pad);

} else {

iomux_v3_cfg_ti2c3_pad =

MX6DL_PAD_GPIO_6__I2C3_SDA;

mxc_iomux_v3_setup_pad(i2c3_pad);

}

}



#ifdefCONFIG_FEC_1588

/* Set GPIO_16input for IEEE-1588 ts_clk and RMII reference clock

* For MX6 GPR1bit21 meaning:

* Bit21: 0 -GPIO_16 pad output

* 1 -GPIO_16 pad input

*/

mxc_iomux_set_gpr_register(1, 21, 1, 1);

#endif


gp_reg_id =sabresd_dvfscore_data.reg_id;

soc_reg_id =sabresd_dvfscore_data.soc_id;

mx6q_sabresd_init_uart();


/*

* MX6DL/Solo onlysupports single IPU

* The followingcodes are used to change ipu id

* and display idinformation for MX6DL/Solo. Then

* register 1 IPUdevice and up to 2 displays for

* MX6DL/Solo

*/

if (cpu_is_mx6dl()){

ldb_data.ipu_id =0;

ldb_data.sec_ipu_id= 0;

}

imx6q_add_mxc_hdmi_core(&hdmi_core_data);


imx6q_add_ipuv3(0,&ipu_data[0]);

if (cpu_is_mx6q()){

imx6q_add_ipuv3(1,&ipu_data[1]);

for (i = 0; i <4 && i < ARRAY_SIZE(sabresd_fb_data); i++)

imx6q_add_ipuv3fb(i,&sabresd_fb_data[i]);

} else

for (i = 0; i <2 && i < ARRAY_SIZE(sabresd_fb_data); i++)

imx6q_add_ipuv3fb(i,&sabresd_fb_data[i]);


imx6q_add_vdoa();

imx6q_add_mipi_dsi(&mipi_dsi_pdata);

imx6q_add_lcdif(&lcdif_data);

imx6q_add_ldb(&ldb_data);

voutdev =imx6q_add_v4l2_output(0);

if(vout_mem.res_msize && voutdev) {

dma_declare_coherent_memory(&voutdev->dev,

vout_mem.res_mbase,

vout_mem.res_mbase,

vout_mem.res_msize,

(DMA_MEMORY_MAP |

DMA_MEMORY_EXCLUSIVE));

}


imx6q_add_v4l2_capture(0,&capture_data[0]);

imx6q_add_v4l2_capture(1,&capture_data[1]);

imx6q_add_mipi_csi2(&mipi_csi2_pdata);

imx6q_add_imx_snvs_rtc();


if (1 ==caam_enabled)

imx6q_add_imx_caam();


if(board_is_mx6_reva()) {

strcpy(mxc_i2c0_board_info[0].type,"wm8958");

mxc_i2c0_board_info[0].platform_data= &wm8958_config_data;

} else {

strcpy(mxc_i2c0_board_info[0].type,"wm8962");

mxc_i2c0_board_info[0].platform_data= &wm8962_config_data;

}

imx6q_add_device_gpio_leds();


imx6q_add_imx_i2c(0,&mx6q_sabresd_i2c_data);

imx6q_add_imx_i2c(1,&mx6q_sabresd_i2c_data);

imx6q_add_imx_i2c(2,&mx6q_sabresd_i2c_data);

if (cpu_is_mx6dl())

imx6q_add_imx_i2c(3,&mx6q_sabresd_i2c_data);

i2c_register_board_info(0,mxc_i2c0_board_info,

ARRAY_SIZE(mxc_i2c0_board_info));

i2c_register_board_info(1,mxc_i2c1_board_info,

ARRAY_SIZE(mxc_i2c1_board_info));

i2c_register_board_info(2,mxc_i2c2_board_info,

ARRAY_SIZE(mxc_i2c2_board_info));

ret =gpio_request(SABRESD_PFUZE_INT, "pFUZE-int");

if (ret) {

printk(KERN_ERR"requestpFUZE-int error!!\n");

return;

} else {

gpio_direction_input(SABRESD_PFUZE_INT);

mx6q_sabresd_init_pfuze100(SABRESD_PFUZE_INT);

}

/* SPI */

噢,看到没有,这里就是初始化SPI相关的部分,就是

imx6q_add_ecspi(0,&mx6q_sabresd_spi_data);

spi_device_init();


imx6q_add_mxc_hdmi(&hdmi_data);


imx6q_add_anatop_thermal_imx(1,&mx6q_sabresd_anatop_thermal_data);


if (enet_to_gpio_6)

/* Make sure theIOMUX_OBSRV_MUX1 is set to ENET_IRQ. */

mxc_iomux_set_specialbits_register(

IOMUX_OBSRV_MUX1_OFFSET,

OBSRV_MUX1_ENET_IRQ,

OBSRV_MUX1_MASK);

else

fec_data.gpio_irq= -1;

imx6_init_fec(fec_data);


imx6q_add_pm_imx(0,&mx6q_sabresd_pm_data);


/* Move sd4 tofirst because sd4 connect to emmc.

Mfgtools wantemmc is mmcblk0 and other sd card is mmcblk1.

*/

imx6q_add_sdhci_usdhc_imx(3,&mx6q_sabresd_sd4_data);

imx6q_add_sdhci_usdhc_imx(1,&mx6q_sabresd_sd2_data);

imx6q_add_sdhci_usdhc_imx(2,&mx6q_sabresd_sd3_data);

imx_add_viv_gpu(&imx6_gpu_data,&imx6q_gpu_pdata);

imx6q_sabresd_init_usb();

/* SATA is notsupported by MX6DL/Solo */

if (cpu_is_mx6q()){

#ifdefCONFIG_SATA_AHCI_PLATFORM

imx6q_add_ahci(0,&mx6q_sabresd_sata_data);

#else

mx6q_sabresd_sata_init(NULL,

(void __iomem*)ioremap(MX6Q_SATA_BASE_ADDR, SZ_4K));

#endif

}

imx6q_add_vpu();

imx6q_init_audio();

platform_device_register(&sabresd_vmmc_reg_devices);

imx_asrc_data.asrc_core_clk= clk_get(NULL, "asrc_clk");

imx_asrc_data.asrc_audio_clk= clk_get(NULL, "asrc_serial_clk");

imx6q_add_asrc(&imx_asrc_data);


/*

* Disable HannStartouch panel CABC function,

* this functionturns the panel's backlight automatically

* according to thecontent shown on the panel which

* may causeannoying unstable backlight issue.

*/

gpio_request(SABRESD_CABC_EN0,"cabc-en0");

gpio_direction_output(SABRESD_CABC_EN0,0);

gpio_request(SABRESD_CABC_EN1,"cabc-en1");

gpio_direction_output(SABRESD_CABC_EN1,0);


imx6q_add_mxc_pwm(0);

imx6q_add_mxc_pwm(1);

imx6q_add_mxc_pwm(2);

imx6q_add_mxc_pwm(3);

imx6q_add_mxc_pwm_backlight(0,&mx6_sabresd_pwm_backlight_data);


imx6q_add_otp();

imx6q_add_viim();

imx6q_add_imx2_wdt(0,NULL);

imx6q_add_dma();


imx6q_add_dvfs_core(&sabresd_dvfscore_data);

imx6q_add_device_buttons();


/* enable sensor3v3 and 1v8 */

gpio_request(SABRESD_SENSOR_EN,"sensor-en");

gpio_direction_output(SABRESD_SENSOR_EN,1);


/* enable ecompassintr */

gpio_request(SABRESD_eCOMPASS_INT,"ecompass-int");

gpio_direction_input(SABRESD_eCOMPASS_INT);

/* enable lightsensor intr */

gpio_request(SABRESD_ALS_INT,"als-int");

gpio_direction_input(SABRESD_ALS_INT);


imx6q_add_hdmi_soc();

imx6q_add_hdmi_soc_dai();


if (cpu_is_mx6dl()){

imx6dl_add_imx_pxp();

imx6dl_add_imx_pxp_client();

if (epdc_enabled){

mxc_register_device(&max17135_sensor_device,NULL);

imx6dl_add_imx_epdc(&epdc_data);

}

}

/*

ret =gpio_request_array(mx6q_sabresd_flexcan_gpios,

ARRAY_SIZE(mx6q_sabresd_flexcan_gpios));

if (ret)

pr_err("failedto request flexcan1-gpios: %d\n", ret);

else

imx6q_add_flexcan0(&mx6q_sabresd_flexcan0_pdata);

*/


clko2 =clk_get(NULL, "clko2_clk");

if (IS_ERR(clko2))

pr_err("can'tget CLKO2 clock.\n");


new_parent =clk_get(NULL, "osc_clk");

if(!IS_ERR(new_parent)) {

clk_set_parent(clko2,new_parent);

clk_put(new_parent);

}

rate =clk_round_rate(clko2, 24000000);

clk_set_rate(clko2,rate);

clk_enable(clko2);


/* Camera and audiouse osc clock */

clko =clk_get(NULL, "clko_clk");

if (!IS_ERR(clko))

clk_set_parent(clko,clko2);


/* Enable Aux_5V */

gpio_request(SABRESD_AUX_5V_EN,"aux_5v_en");

gpio_direction_output(SABRESD_AUX_5V_EN,1);

gpio_set_value(SABRESD_AUX_5V_EN,1);


#ifndefCONFIG_IMX_PCIE

/* enable pcie 3v3power without pcie driver */

pcie_3v3_power();

mdelay(10);

pcie_3v3_reset();

#endif


gps_power_on(true);

/* Register chargerchips */

platform_device_register(&sabresd_max8903_charger_1);

pm_power_off =mx6_snvs_poweroff;

imx6q_add_busfreq();


/* Add PCIe RCinterface support */

imx6q_add_pcie(&mx6_sabresd_pcie_data);

if (cpu_is_mx6dl()){

mxc_iomux_v3_setup_multiple_pads(mx6dl_arm2_elan_pads,

ARRAY_SIZE(mx6dl_arm2_elan_pads));


/* ELANTouchscreen */

gpio_request(SABRESD_ELAN_INT,"elan-interrupt");

gpio_direction_input(SABRESD_ELAN_INT);


gpio_request(SABRESD_ELAN_CE,"elan-cs");

gpio_direction_output(SABRESD_ELAN_CE,1);

gpio_direction_output(SABRESD_ELAN_CE,0);


gpio_request(SABRESD_ELAN_RST,"elan-rst");

gpio_direction_output(SABRESD_ELAN_RST,1);

gpio_direction_output(SABRESD_ELAN_RST,0);

mdelay(1);

gpio_direction_output(SABRESD_ELAN_RST,1);

gpio_direction_output(SABRESD_ELAN_CE,1);

}


imx6_add_armpmu();

imx6q_add_perfmon(0);

imx6q_add_perfmon(1);

imx6q_add_perfmon(2);

}

/////////////////////////////////////////////////////////////////////////////////////////////////////


Platforms will oftenabstract the "register SPI controller" operation,

maybe coupling itwith code to initialize pin configurations, so that

thearch/.../mach-*/board-*.cfiles for several boards can all share the

same basiccontroller setup code. This is because most SOCs have several

SPI-capablecontrollers, and only the ones actually usable on a given

board shouldnormally be set up and registered.


So for examplearch/.../mach-*/board-*.c files might have code like:


#include<mach/spi.h> /* for mysoc_spi_data */


/* if your mach-*infrastructure doesn't support kernels that can

* run on multipleboards, pdata wouldn't benefit from "__init".

*/

static structmysoc_spi_data pdata __initdata = { ... };


static __initboard_init(void)

{

...

/* this board onlyuses SPI controller #2 */

mysoc_register_spi(2,&pdata);

...

}


And SOC-specificutility code might look something like:


#include<mach/spi.h>


static structplatform_device spi2 = { ... };


voidmysoc_register_spi(unsigned n, struct mysoc_spi_data *pdata)

{

structmysoc_spi_data *pdata2;


pdata2 =kmalloc(sizeof *pdata2, GFP_KERNEL);

*pdata2 = pdata;

...

if (n == 2) {

spi2->dev.platform_data= pdata2;

register_platform_device(&spi2);


/* also: set uppin modes so the spi2 signals are

* visible on therelevant pins ... bootloaders on

* productionboards may already have done this, but

* developerboards will often need Linux to do it.

*/

}

...

}


Notice how theplatform_data for boards may be different, even if the

same SOC controlleris used. For example, on one board SPI might use

an external clock,where another derives the SPI clock from current

settings of somemaster clock.



声明spi外设,这个也得静态声明呀??真是麻烦!

如果能动态声明该多好呀.

static intmx6q_sabresd_spi_cs[] = {

SABRESD_ECSPI1_CS0,

};


static const structspi_imx_master mx6q_sabresd_spi_data __initconst = {

.chipselect = mx6q_sabresd_spi_cs,

.num_chipselect = ARRAY_SIZE(mx6q_sabresd_spi_cs),

};


#ifdefined(CONFIG_MTD_M25P80) || defined(CONFIG_MTD_M25P80_MODULE)

static structmtd_partition imx6_sabresd_spi_nor_partitions[] = {

{

.name ="bootloader",

.offset =0,

.size =0x00100000,

},

{

.name ="kernel",

.offset =MTDPART_OFS_APPEND,

.size =MTDPART_SIZ_FULL,

},

};

static structspi_board_info imx6_sabresd_spi_nor_device[] __initdata = {

#ifdefined(CONFIG_MTD_M25P80)

{

.modalias = "m25p80",

.max_speed_hz = 20000000, /* max spi clock (SCK) speed in HZ */

.bus_num = 0,

.chip_select = 0,

.platform_data = &imx6_sabresd__spi_flash_data,

},

#endif

};

///////////////////////////////////////////////////////////////////////////////////////////////////////////

DECLARE SLAVEDEVICES


Thesecond kind of information is a list of what SPI slave devices exist

onthe target board, often with some board-specific data needed for the

driverto work correctly.


Normally yourarch/.../mach-*/board-*.c files would provide a small table

listing the SPIdevices on each board. (This would typically be only a

small handful.) That might look like:


static structads7846_platform_data ads_info = {

.vref_delay_usecs =100,

.x_plate_ohms =580,

.y_plate_ohms =410,

};


static structspi_board_info spi_board_info[] __initdata = {

{

.modalias ="ads7846",

.platform_data =&ads_info,

.mode =SPI_MODE_0,

.irq =GPIO_IRQ(31),

.max_speed_hz =120000 /* max sample rate at 3V */ * 16,

.bus_num = 1,

.chip_select = 0,

},

};


Again, notice howboard-specific information is provided; each chip may need

several types. Thisexample shows generic constraints like the fastest SPI

clock to allow (afunction of board voltage in this case) or how an IRQ pin

is wired, pluschip-specific constraints like an important delay that's

changed by thecapacitance at one pin.


(There's also"controller_data", information that may be useful to the

controller driver. An example would be peripheral-specific DMA tuning

data or chipselectcallbacks. This is stored in spi_device later.)


The board_infoshould provide enough information to let the system work

without the chip'sdriver being loaded. The most troublesome aspect of

that is likely theSPI_CS_HIGH bit in the spi_device.mode field, since

sharing a bus with adevice that interprets chipselect "backwards" is

not possible untilthe infrastructure knows how to deselect it.


Then your boardinitialization code would register that table with the SPI

infrastructure, sothat it's available later when the SPI master controller

driver isregistered:

注册spi板级信息,里面就包括了spi外设。

spi_register_board_info(spi_board_info,ARRAY_SIZE(spi_board_info));


Like with otherstatic board-specific setup, you won't unregister those.


The widely used"card" style computers bundle memory, cpu, and little else

onto a card that'smaybe just thirty square centimeters. On such systems,

yourarch/.../mach-.../board-*.c file would primarily provide information

about the devices onthe mainboard into which such a card is plugged. That

certainly includesSPI devices hooked up through the card connectors!



非静态配置,难道可以动态配置??????

NON-STATICCONFIGURATIONS


Developer boardsoften play by different rules than product boards, and one

example is thepotential need to hotplug SPI devices and/or controllers.


For those cases youmight need to usespi_busnum_to_master()to look

up the spi busmaster, and will likely needspi_new_device()to provide the

board info based onthe board that was hotplugged. Of course, you'd later

call at leastspi_unregister_device() whenthat board is removed.


When Linux includessupport for MMC/SD/SDIO/DataFlash cards through SPI, those

configurations willalso be dynamic. Fortunately, such devices all support

basic deviceidentification probes, so they should hotplug normally.



这里是写协议驱动,不是板级的spi控制器驱动!

How do I write an"SPI Protocol Driver"?

----------------------------------------

Most SPI drivers arecurrently kernel drivers, but there's also support

for userspacedrivers. Here we talk only about kernel drivers.


SPI protocol driverssomewhat resemble platform device drivers:


static structspi_driver CHIP_driver = {

.driver = {

.name = "CHIP",

.owner =THIS_MODULE,

.pm =&CHIP_pm_ops,

},


.probe =CHIP_probe,

.remove =CHIP_remove,

};

根据namedriver会跟device进行绑定。

The driver core willautomatically attempt to bind this driver to any SPI

device whoseboard_info gave a modalias of "CHIP". Your probe() code

might look like thisunless you're creating a device which is managing

a bus (appearingunder /sys/class/spi_master).


static intCHIP_probe(struct spi_device *spi)

{

structCHIP *chip;

structCHIP_platform_data *pdata;

假设spi协议驱动需要板级数据,则可以从这里取到。

/* assuming thedriver requires board-specific data: */

pdata =&spi->dev.platform_data;

if (!pdata)

return -ENODEV;


/* get memory fordriver's per-chip state */

chip =kzalloc(sizeof *chip, GFP_KERNEL);

if (!chip)

return -ENOMEM;

spi_set_drvdata(spi,chip);


... etc

return 0;

}

一旦进入probe()函数,驱动就可以使用structspi_message进行收发消息了。

Assoon as it enters probe(), the driver may issue I/O requests to

theSPI device using "struct spi_message".Whenremove() returns,

or after probe()fails, the driver guarantees that it won't submit

any more suchmessages.


原子,顺序协议集合。

- An spi_messageis a sequence of protocol operations, executed

as one atomicsequence. SPI driver controls include:


+ whenbidirectional reads and writes start ... by how its

sequence ofspi_transfer requests is arranged;


+ which I/Obuffers are used ... each spi_transfer wraps a

buffer foreach transfer direction, supporting full duplex

(twopointers, maybe the same one in both cases) and half

duplex (onepointer is NULL) transfers;


+ optionallydefining short delays after transfers ... using

thespi_transfer.delay_usecs setting (this delay can be the

onlyprotocol effect, if the buffer length is zero);


+ whether thechipselect becomes inactive after a transfer and

any delay... by using the spi_transfer.cs_change flag;


+ hintingwhether the next message is likely to go to this same

device ...using the spi_transfer.cs_change flag on the last

transfer in thatatomic group, and potentially saving costs

for chip deselectand select operations.


- Follow standardkernel rules, and provide DMA-safe buffers in

your messages. That way controller drivers using DMA aren't forced

to make extracopies unless the hardware requires it (e.g. working

around hardwareerrata that force the use of bounce buffering).


If standarddma_map_single() handling of these buffers is inappropriate,

you can usespi_message.is_dma_mapped to tell the controller driver

that you'vealready provided the relevant DMA addresses.


异步同步

- The basic I/Oprimitive is spi_async(). Async requests may be

issued in anycontext (irq handler, task, etc) and completion

is reportedusing a callback provided with the message.

After anydetected error, the chip is deselected and processing

of thatspi_message is aborted.

同步封装器

- There are alsosynchronous wrappers like spi_sync(), and wrappers

like spi_read(),spi_write(), and spi_write_then_read(). These

may be issuedonly in contexts that may sleep, and they're all

clean (andsmall, and "optional") layers over spi_async().


- Thespi_write_then_read() call, and convenience wrappers around

it, should onlybe used with small amounts of data where the

cost of an extracopy may be ignored. It's designed to support

common RPC-stylerequests, such as writing an eight bit command

and reading asixteen bit response -- spi_w8r16() being one its

wrappers, doingexactly that.


Some drivers mayneed to modify spi_device characteristics like the

transfer mode,wordsize, or clock rate. This is done with spi_setup(),

which would normallybe called from probe() before the first I/O is

done to the device. However, that can also be called at any time

that no message ispending for that device.


While "spi_device"would be the bottom boundary of the driver, the

upper boundariesmight include sysfs (especially for sensor readings),

the input layer,ALSA, networking, MTD, the character device framework,

or other Linuxsubsystems.


Note that there aretwo types of memory your driver must manage as part

of interacting withSPI devices.


- I/O buffers usethe usual Linux rules, andmust beDMA-safe.

You'd normallyallocate them from the heap or free page pool.

Don'tuse the stack, or anything that's declared "static".


- The spi_messageand spi_transfer metadata used to glue those

I/O buffers intoa group of protocol transactions. These can

be allocatedanywhere it's convenient, including as part of

otherallocate-once driver data structures. Zero-init these.


If you like,spi_message_alloc() and spi_message_free() convenience

routines areavailable to allocate and zero-initialize an spi_message

with severaltransfers.



How do I write an"SPI Master Controller Driver"?

-------------------------------------------------

An SPI controllerwill probably be registered on the platform_bus; write

a driver to bind tothe device, whichever bus is involved.


The main task ofthis type of driver is to provide an "spi_master".

Usespi_alloc_master() to allocate the master, andspi_master_get_devdata()

to get thedriver-private data allocated for that device.


structspi_master *master;

structCONTROLLER *c;


master =spi_alloc_master(dev, sizeof *c);

if (!master)

return -ENODEV;


c =spi_master_get_devdata(master);


The driver willinitialize the fields of that spi_master, including the

bus number (maybethe same as the platform device ID) and three methods

used to interactwith the SPI core and SPI protocol drivers. It will

also initialize itsown internal state. (See below about bus numbering

and those methods.)


After you initializethe spi_master, then use spi_register_master() to

publish it to therest of the system. At that time, device nodes for the

controller and anypredeclared spi devices will be made available, and

the driver modelcore will take care of binding them to drivers.


If you need toremove your SPI controller driver, spi_unregister_master()

will reverse theeffect of spi_register_master().



BUS NUMBERING


Bus numbering isimportant, since that's how Linux identifies a given

SPI bus (shared SCK,MOSI, MISO). Valid bus numbers start at zero. On

SOC systems, the busnumbers should match the numbers defined by the chip

manufacturer. Forexample, hardware controller SPI2 would be bus number 2,

and spi_board_infofor devices connected to it would use that number.


If you don't havesuch hardware-assigned bus number, and for some reason

you can't justassign them, then provide a negative bus number. That will

then be replaced bya dynamically assigned number. You'd then need to treat

this as a non-staticconfiguration (see above).



SPI MASTER METHODS


master->setup(struct spi_device *spi)

This sets up thedevice clock rate, SPI mode, and word sizes.

Drivers may changethe defaults provided by board_info, and then

call spi_setup(spi)to invoke this routine. It may sleep.


Unless each SPIslave has its own configuration registers, don't

change them rightaway ... otherwise drivers could corrupt I/O

that's in progressfor other SPI devices.


** BUG ALERT: forsome reason the first version of

** many spi_masterdrivers seems to get this wrong.

** When you codesetup(), ASSUME that the controller

** is activelyprocessing transfers for another device.


master->cleanup(struct spi_device *spi)

Your controllerdriver may use spi_device.controller_state to hold

state itdynamically associates with that device. If you do that,

be sure to providethe cleanup() method to free that state.


master->prepare_transfer_hardware(struct spi_master *master)

This will be calledby the queue mechanism to signal to the driver

that a message iscoming in soon, so the subsystem requests the

driver to preparethe transfer hardware by issuing this call.

This may sleep.


master->unprepare_transfer_hardware(struct spi_master *master)

This will be calledby the queue mechanism to signal to the driver

that there are nomore messages pending in the queue and it may

relax the hardware(e.g. by power management calls). This may sleep.


master->transfer_one_message(struct spi_master *master,

structspi_message *mesg)

The subsystem callsthe driver to transfer a single message while

queuing transfersthat arrive in the meantime. When the driver is

finished with thismessage, it must call

spi_finalize_current_message()so the subsystem can issue the next

message. This maysleep.


master->transfer_one(struct spi_master *master, struct spi_device*spi,

structspi_transfer *transfer)

The subsystem callsthe driver to transfer a single transfer while

queuing transfersthat arrive in the meantime. When the driver is

finished with thistransfer, it must call

spi_finalize_current_transfer()so the subsystem can issue the next

transfer. This maysleep. Note: transfer_one and transfer_one_message

are mutuallyexclusive; when both are set, the generic subsystem does

not call yourtransfer_one callback.


Return values:

negative errno:error

0: transfer isfinished

1: transfer isstill in progress


DEPRECATEDMETHODS


master->transfer(struct spi_device *spi, struct spi_message*message)

This must notsleep. Its responsibility is to arrange that the

transfer happensand its complete() callback is issued. The two

will normallyhappen later, after other transfers complete, and

if the controlleris idle it will need to be kickstarted. This

method is not usedon queued controllers and must be NULL if

transfer_one_message()and (un)prepare_transfer_hardware() are

implemented.


消息队列

SPI MESSAGE QUEUE


If you are happywith the standard queueing mechanism provided by the

SPI subsystem, justimplement the queued methods specified above. Using

the message queuehas the upside of centralizing a lot of code and

providing pureprocess-context execution of methods. The message queue

can also be elevatedto realtime priority on high-priority SPI traffic.


Unless the queueingmechanism in the SPI subsystem is selected, the bulk

of the driver willbe managing the I/O queue fed by the now deprecated

function transfer().


That queue could bepurely conceptual. For example, a driver used only

for low-frequencysensor access might be fine using synchronous PIO.


But the queue willprobably be very real, using message->queue, PIO,

often DMA(especially if the root filesystem is in SPI flash), and

execution contextslike IRQ handlers, tasklets, or workqueues (such

as keventd). Yourdriver can be as fancy, or as simple, as you need.

Such a transfer()method would normally just add the message to a

queue, and thenstart some asynchronous transfer engine (unless it's

already running).



0 0
原创粉丝点击