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多种设备,包括I2C、SPI等。
这个函数会在启动时被调用。
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,
};
根据name,driver会跟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).
- kernel spi framework details
- linux spi framework
- spi driver framework
- Overview of Linux kernel SPI support
- 新路程------hi35516a kernel导入spi flash
- SPI和service provider framework介绍
- Android Uevent 分析,从kernel到framework
- Android Uevent 分析,从kernel到framework
- Android Uevent 分析,从kernel到framework
- Android Uevent 分析,从kernel到framework
- Android Uevent 分析,从kernel到framework
- Android Uevent 分析,从kernel到framework
- Android Uevent 分析,从kernel到framework
- Android Uevent 分析,从kernel到framework
- Android 关机流程 从kernel到framework
- SystemUI之USB1(kernel->Framework UEvent)
- Android 关机流程 从kernel到framework
- Android Uevent 分析,从kernel到framework
- false sharing
- javascript实现队列
- 大型高并发高负载网站的系统架构剖析
- 成功编译Dx11.1.6 & Xaf11.1.6的方法
- android 学习资源收藏备份
- kernel spi framework details
- java基础整理01
- Xcode中使用Git进行源码版本控制
- 使用STM32CubeMX开发二:按键实验
- HDU 4612 Warm up 边双连通+树的直径
- SDWebImage和AFNetworking的缓存机制
- MySQL的优化总结
- 陈怡暖:新屋开工营建许可大幅好于预期黄金刷新两周低点
- 解决连不上dl.google.com和dl-ssl.google.com