调试MT7688的SPI:改造内核SPI驱动

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);
if (ret == -1)
pabort("can't get spi mode"); ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
xfer[0].tx_buf = (unsigned long)tx_buf;xfer[0].rx_buf = 0;xfer[0].len = 0;xfer[0].delay_usecs = delay;xfer[0].speed_hz = trans_speed;xfer[0].bits_per_word = bits;xfer[1].tx_buf = 0;xfer[1].rx_buf = (unsigned long)rx_buf;xfer[1].len = 0;xfer[1].delay_usecs = delay;xfer[1].speed_hz = trans_speed;xfer[1].bits_per_word = bits;ret = ioctl(spi_fd, SPI_IOC_MESSAGE(2), &xfer[0]);if (ret == 1) {printf("can't revieve spi message\n");}}
 
  




你可能感兴趣的:(Linux,MT7688)