MT7688内核的SPI驱动位于:
build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_mt7688/linux-3.18.45/drivers/spi/spi-mt7621.c
在用户态打开spi设置,测试transfer时发现驱动对数据长度有限制,阅读驱动代码发现半双工写入的限制为36bytes:
在mt7621_spi_transfer_half_duplex函数中,有如下判断:
if (WARN_ON(len + rlen > 36))
{
status = -EIO;
goto msg_done;
}
初步分析其原因:该SPI驱动主要是为了
配合MT7688的SPI Controller控制外挂的SPI FLASH而设计的。
通过阅读MT7688的datasheet并结合现有驱动代码,可知SPI控制器的数据缓存由SPI_OP_ADDR(10000B04)以及SPI_DIDO_0(10000B08)-SPI_DIDO_7(10000B24)共计9个32bit位宽(36bytes)的寄存器构成。
通过改造mt7621_spi_transfer_half_duplex函数->mt7621_spi_transfer_half_duplex_cs1,使其支持多字节读写:
static int mt7621_spi_transfer_one_message(struct spi_master *master,
struct spi_message *m)
{
struct spi_device *spi = m->spi;
int cs = spi->chip_select;
if(cs == 0)
{
return mt7621_spi_transfer_half_duplex(master, m);
}
else if(cs == 1)
{
return mt7621_spi_transfer_half_duplex_cs1(master, m);
}
return 0;
}
static int mt7621_spi_transfer_half_duplex_cs1(struct spi_master *master,
struct spi_message *m)
{
struct mt7621_spi *rs = spi_master_get_devdata(master);
struct spi_device *spi = m->spi;
unsigned int speed = spi->max_speed_hz;
struct spi_transfer *t = NULL;
int status = 0;
int i=0;
u8* tbuf;
u8 *rbuf;
u32 data[9];
u32 val=0;
u32 r_index=0;
u8 n_xfer=0;
int tmp =0 ;
u32 n_byte_to_write=0;
u32 n_byte_to_read=0;
u32 rw_left=0;
u32 tdata=0;
tbuf = (u8*)kbuf;
tmp = mt7621_spi_wait_till_ready(spi);
if(tmp != 0)
{
status = -EIO;
goto msg_done;
}
list_for_each_entry(t, &m->transfers, transfer_list)
{
const u8 *buf = t->tx_buf; // send buffer
int rlen = t->len; // length to read or write.
if (t->rx_buf)
{
n_byte_to_read += rlen;
rbuf = t->rx_buf;
}
if (!buf) // send buffer
continue;
//update speed
if (t->speed_hz < speed)
speed = t->speed_hz;
if (WARN_ON((n_byte_to_write + rlen) > KBUF_SIZE))
{
status = -EIO;
goto msg_done;
}
//buffering tx data
for(i=0;i (KBUF_SIZE-4)))
{
status = -EIO;
goto msg_done;
}
//config transfer speed and polarity and etc.
if (mt7621_spi_prepare(spi, speed))
{
status = -EIO;
goto msg_done;
}
//Enable CS1
mt7621_spi_set_cs(spi, 1);
if(n_byte_to_write)
{
//process WRITE operation
rw_left = n_byte_to_write;
r_index = 0;
while(rw_left)
{
if(rw_left>=36)
{
n_xfer = 36;
}
else
{
n_xfer = rw_left;
}
for(i=0;i<9;i++)
{
data[i]=0;
}
tmp = 0;
for(i=0;i>= (4 - n_xfer) * 8;
// n_xfer max is 36 , so max loop count here is 9
for (i = 0; i < n_xfer; i += 4)
{
//here we load data to SPI_OP_ADDR(10000B04) - SPI_DIDO_7(10000B24), nine 32-bit registers to hold maxmium 36 bytes data in total.
mt7621_spi_write(rs, MT7621_SPI_OPCODE + i, data[i / 4]);
}
val = (min_t(int, n_xfer, 4) * 8) << 24; //calc cmd_bit_cnt
if (n_xfer > 4)
val |= (n_xfer - 4) * 8; // calc mosi_bit_cnt
//miso_bit_cnt is 0 in writing operation
//val |= (0 * 8) << 12;
mt7621_spi_write(rs, MT7621_SPI_MOREBUF, val); // write to the SPI more buf control register @ 0x10000B2C
// initiating transfer.
val = mt7621_spi_read(rs, MT7621_SPI_TRANS);
val |= SPI_CTL_START;
mt7621_spi_write(rs, MT7621_SPI_TRANS, val);
//wait until transfer is done.
tmp = mt7621_spi_wait_till_ready(spi);
if(tmp!=0)
{
printk("spi_wait_till_ready error:%d,line=%d\n",tmp,__LINE__);
}
rw_left -= n_xfer;
}
}
if(n_byte_to_read)
{
//process READ operation
rw_left = n_byte_to_read;
while(rw_left)
{
if(rw_left>=32)
{
n_xfer = 32;
}
else
{
n_xfer = rw_left;
}
val = 0; //cmd_bit_cnt & mosi_bit_cnt is 0 in READ operation
val |= (n_xfer * 8) << 12; // calc miso_bit_cnt
mt7621_spi_write(rs, MT7621_SPI_MOREBUF, val); // write to the SPI more buf control register @ 0x10000B2C
// initiating transfer.
val = mt7621_spi_read(rs, MT7621_SPI_TRANS);
val |= SPI_CTL_START;
mt7621_spi_write(rs, MT7621_SPI_TRANS, val);
//wait until transfer is done.
tmp = mt7621_spi_wait_till_ready(spi);
if(tmp!=0)
{
printk("spi_wait_till_ready error:%d,line=%d\n",tmp,__LINE__);
}
for (i = 0; i < n_xfer; i += 4)
{
data[i / 4] = mt7621_spi_read(rs, MT7621_SPI_DATA0 + i);
}
//copy to user-space buffer.
tmp=0;
for (i = 0; i < n_xfer; i++, tmp++)
{
rbuf[i] = data[tmp / 4] >> (8 * (tmp & 3));
}
rw_left -= n_xfer;
}
}
//Disable CS1
mt7621_spi_set_cs(spi, 0);
m->actual_length = n_byte_to_write+n_byte_to_read;
msg_done:
m->status = status;
spi_finalize_current_message(master);
return 0;
}
应用层部分代码:
头文件:
/*
* include/linux/spi/spidev.h
*
* Copyright (C) 2006 SWAPP
* Andrea Paterniani
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef SPIDEV_H
#define SPIDEV_H
#include
/* User space versions of kernel symbols for SPI clocking modes,
* matching
*/
#define SPI_CPHA 0x01
#define SPI_CPOL 0x02
#define SPI_MODE_0 (0|0)
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04
#define SPI_LSB_FIRST 0x08
#define SPI_3WIRE 0x10
#define SPI_LOOP 0x20
#define SPI_NO_CS 0x40
#define SPI_READY 0x80
/*---------------------------------------------------------------------------*/
/* IOCTL commands */
#define SPI_IOC_MAGIC 'k'
/**
* struct spi_ioc_transfer - describes a single SPI transfer
* @tx_buf: Holds pointer to userspace buffer with transmit data, or null.
* If no data is provided, zeroes are shifted out.
* @rx_buf: Holds pointer to userspace buffer for receive data, or null.
* @len: Length of tx and rx buffers, in bytes.
* @speed_hz: Temporary override of the device's bitrate.
* @bits_per_word: Temporary override of the device's wordsize.
* @delay_usecs: If nonzero, how long to delay after the last bit transfer
* before optionally deselecting the device before the next transfer.
* @cs_change: True to deselect device before starting the next transfer.
*
* This structure is mapped directly to the kernel spi_transfer structure;
* the fields have the same meanings, except of course that the pointers
* are in a different address space (and may be of different sizes in some
* cases, such as 32-bit i386 userspace over a 64-bit x86_64 kernel).
* Zero-initialize the structure, including currently unused fields, to
* accomodate potential future updates.
*
* SPI_IOC_MESSAGE gives userspace the equivalent of kernel spi_sync().
* Pass it an array of related transfers, they'll execute together.
* Each transfer may be half duplex (either direction) or full duplex.
*
* struct spi_ioc_transfer mesg[4];
* ...
* status = ioctl(fd, SPI_IOC_MESSAGE(4), mesg);
*
* So for example one transfer might send a nine bit command (right aligned
* in a 16-bit word), the next could read a block of 8-bit data before
* terminating that command by temporarily deselecting the chip; the next
* could send a different nine bit command (re-selecting the chip), and the
* last transfer might write some register values.
*/
struct spi_ioc_transfer {
__u64 tx_buf;
__u64 rx_buf;
__u32 len;
__u32 speed_hz;
__u16 delay_usecs;
__u8 bits_per_word;
__u8 cs_change;
__u32 pad;
/* If the contents of 'struct spi_ioc_transfer' ever change
* incompatibly, then the ioctl number (currently 0) must change;
* ioctls with constant size fields get a bit more in the way of
* error checking than ones (like this) where that field varies.
*
* NOTE: struct layout is the same in 64bit and 32bit userspace.
*/
};
/* not all platforms use or _IOC_TYPECHECK() ... */
#define SPI_MSGSIZE(N) \
((((N)*(sizeof (struct spi_ioc_transfer))) < (1 << _IOC_SIZEBITS)) \
? ((N)*(sizeof (struct spi_ioc_transfer))) : 0)
#define SPI_IOC_MESSAGE(N) _IOW(SPI_IOC_MAGIC, 0, char[SPI_MSGSIZE(N)])
/* Read / Write of SPI mode (SPI_MODE_0..SPI_MODE_3) */
#define SPI_IOC_RD_MODE _IOR(SPI_IOC_MAGIC, 1, __u8)
#define SPI_IOC_WR_MODE _IOW(SPI_IOC_MAGIC, 1, __u8)
/* Read / Write SPI bit justification */
#define SPI_IOC_RD_LSB_FIRST _IOR(SPI_IOC_MAGIC, 2, __u8)
#define SPI_IOC_WR_LSB_FIRST _IOW(SPI_IOC_MAGIC, 2, __u8)
/* Read / Write SPI device word length (1..N) */
#define SPI_IOC_RD_BITS_PER_WORD _IOR(SPI_IOC_MAGIC, 3, __u8)
#define SPI_IOC_WR_BITS_PER_WORD _IOW(SPI_IOC_MAGIC, 3, __u8)
/* Read / Write SPI device default max speed hz */
#define SPI_IOC_RD_MAX_SPEED_HZ _IOR(SPI_IOC_MAGIC, 4, __u32)
#define SPI_IOC_WR_MAX_SPEED_HZ _IOW(SPI_IOC_MAGIC, 4, __u32)
#endif /* SPIDEV_H */
主程序:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "spidev.h"
static const char *device = "/dev/spidev32766.1";
void dev_init(int fd)
{
fd = open(device, O_RDWR);
if (fd < 0)
pabort("can't open device");
else
printf("open:%s success, fd=%d\r\n",device,fd);
ret = ioctl(fd, SPI_IOC_RD_MODE, &mode);