forked from Imagelibrary/rtems
443 lines
15 KiB
C
443 lines
15 KiB
C
/*===============================================================*\
|
|
| Project: SPI driver for spi memory devices |
|
|
+-----------------------------------------------------------------+
|
|
| Copyright (c) 2008 |
|
|
| Embedded Brains GmbH |
|
|
| Obere Lagerstr. 30 |
|
|
| D-82178 Puchheim |
|
|
| Germany |
|
|
| rtems@embedded-brains.de |
|
|
+-----------------------------------------------------------------+
|
|
| The license and distribution terms for this file may be |
|
|
| found in the file LICENSE in this distribution or at |
|
|
| |
|
|
| http://www.rtems.org/license/LICENSE. |
|
|
| |
|
|
+-----------------------------------------------------------------+
|
|
\*===============================================================*/
|
|
/*
|
|
* FIXME: currently, this driver only supports read/write accesses
|
|
* erase accesses are to be completed
|
|
*/
|
|
|
|
|
|
#include <rtems.h>
|
|
#include <rtems/libi2c.h>
|
|
|
|
#include <libchip/spi-memdrv.h>
|
|
#include <rtems/libio.h>
|
|
|
|
#define SPI_MEM_CMD_WREN 0x06
|
|
#define SPI_MEM_CMD_WRDIS 0x04
|
|
#define SPI_MEM_CMD_RDID 0x9F
|
|
#define SPI_MEM_CMD_RDSR 0x05
|
|
#define SPI_MEM_CMD_WRSR 0x01
|
|
#define SPI_MEM_CMD_READ 0x03
|
|
#define SPI_MEM_CMD_PP 0x02 /* page program */
|
|
#define SPI_MEM_CMD_SE 0xD8 /* sector erase */
|
|
#define SPI_MEM_CMD_BE 0xC7 /* bulk erase */
|
|
#define SPI_MEM_CMD_DP 0xB9 /* deep power down */
|
|
#define SPI_MEM_CMD_RES 0xAB /* release from deep power down */
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static rtems_status_code spi_memdrv_minor2param_ptr
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| translate given minor device number to param pointer |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
rtems_device_minor_number minor, /* minor number of device */
|
|
spi_memdrv_param_t **param_ptr /* ptr to param ptr */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| o = ok or error code |
|
|
\*=========================================================================*/
|
|
{
|
|
rtems_status_code rc = RTEMS_SUCCESSFUL;
|
|
spi_memdrv_t *drv_ptr;
|
|
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
rc = -rtems_libi2c_ioctl(minor,
|
|
RTEMS_LIBI2C_IOCTL_GET_DRV_T,
|
|
&drv_ptr);
|
|
}
|
|
if ((rc == RTEMS_SUCCESSFUL) &&
|
|
(drv_ptr->libi2c_drv_entry.size != sizeof(spi_memdrv_t))) {
|
|
rc = RTEMS_INVALID_SIZE;
|
|
}
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
*param_ptr = &(drv_ptr->spi_memdrv_param);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static rtems_status_code spi_memdrv_wait_ms
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| wait a certain interval given in ms |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
int ms /* time to wait in milliseconds */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| o = ok or error code |
|
|
\*=========================================================================*/
|
|
{
|
|
rtems_interval ticks_per_second;
|
|
|
|
ticks_per_second = rtems_clock_get_ticks_per_second();
|
|
(void) rtems_task_wake_after(ticks_per_second * ms / 1000);
|
|
return 0;
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
rtems_status_code spi_memdrv_write
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| write a block of data to flash |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
rtems_device_major_number major, /* major device number */
|
|
rtems_device_minor_number minor, /* minor device number */
|
|
void *arg /* ptr to write argument struct */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| o = ok or error code |
|
|
\*=========================================================================*/
|
|
{
|
|
rtems_status_code rc = RTEMS_SUCCESSFUL;
|
|
rtems_libio_rw_args_t *rwargs = arg;
|
|
off_t off = rwargs->offset;
|
|
int cnt = rwargs->count;
|
|
unsigned char *buf = (unsigned char *)rwargs->buffer;
|
|
int bytes_sent = 0;
|
|
int curr_cnt;
|
|
unsigned char cmdbuf[4];
|
|
int ret_cnt = 0;
|
|
int cmd_size;
|
|
spi_memdrv_param_t *mem_param_ptr;
|
|
rtems_libi2c_tfr_mode_t tfr_mode = {
|
|
.baudrate = 20000000, /* maximum bits per second */
|
|
.bits_per_char = 8, /* how many bits per byte/word/longword? */
|
|
.lsb_first = FALSE, /* FALSE: send MSB first */
|
|
.clock_inv = FALSE, /* FALSE: non-inverted clock (high active) */
|
|
.clock_phs = FALSE /* FALSE: clock starts in middle of data tfr */
|
|
} ;
|
|
|
|
/*
|
|
* get mem parameters
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
rc = spi_memdrv_minor2param_ptr(minor,&mem_param_ptr);
|
|
}
|
|
/*
|
|
* check arguments
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
if ((cnt <= 0) ||
|
|
(cnt > mem_param_ptr->mem_size) ||
|
|
(off > (mem_param_ptr->mem_size-cnt))) {
|
|
rc = RTEMS_INVALID_SIZE;
|
|
}
|
|
else if (buf == NULL) {
|
|
rc = RTEMS_INVALID_ADDRESS;
|
|
}
|
|
}
|
|
while ((rc == RTEMS_SUCCESSFUL) &&
|
|
(cnt > bytes_sent)) {
|
|
curr_cnt = cnt - bytes_sent;
|
|
if ((mem_param_ptr->page_size > 0) &&
|
|
(off / mem_param_ptr->page_size) !=
|
|
((off+curr_cnt+1) / mem_param_ptr->page_size)) {
|
|
curr_cnt = mem_param_ptr->page_size - (off % mem_param_ptr->page_size);
|
|
}
|
|
/*
|
|
* select device, set transfer mode, address device
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
rc = rtems_libi2c_send_start(minor);
|
|
}
|
|
/*
|
|
* set transfer mode
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
tfr_mode.baudrate = mem_param_ptr->baudrate;
|
|
rc = -rtems_libi2c_ioctl(minor,
|
|
RTEMS_LIBI2C_IOCTL_SET_TFRMODE,
|
|
&tfr_mode);
|
|
}
|
|
|
|
/*
|
|
* address device
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
rc = rtems_libi2c_send_addr(minor,TRUE);
|
|
}
|
|
|
|
/*
|
|
* send write_enable command
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
cmdbuf[0] = SPI_MEM_CMD_WREN;
|
|
ret_cnt = rtems_libi2c_write_bytes(minor,cmdbuf,1);
|
|
if (ret_cnt < 0) {
|
|
rc = -ret_cnt;
|
|
}
|
|
}
|
|
/*
|
|
* terminate transfer
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
rc = rtems_libi2c_send_stop(minor);
|
|
}
|
|
/*
|
|
* select device, set transfer mode
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
rc = rtems_libi2c_send_start(minor);
|
|
}
|
|
|
|
/*
|
|
* address device
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
rc = rtems_libi2c_send_addr(minor,TRUE);
|
|
}
|
|
|
|
/*
|
|
* set transfer mode
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
rc = -rtems_libi2c_ioctl(minor,
|
|
RTEMS_LIBI2C_IOCTL_SET_TFRMODE,
|
|
&tfr_mode);
|
|
}
|
|
/*
|
|
* send "page program" command and address
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
cmdbuf[0] = SPI_MEM_CMD_PP;
|
|
if (mem_param_ptr->mem_size > 0x10000 /* 256*256 */) {
|
|
cmdbuf[1] = (off >> 16) & 0xff;
|
|
cmdbuf[2] = (off >> 8) & 0xff;
|
|
cmdbuf[3] = (off >> 0) & 0xff;
|
|
cmd_size = 4;
|
|
}
|
|
else if (mem_param_ptr->mem_size > 256) {
|
|
cmdbuf[1] = (off >> 8) & 0xff;
|
|
cmdbuf[2] = (off >> 0) & 0xff;
|
|
cmd_size = 3;
|
|
}
|
|
else {
|
|
cmdbuf[1] = (off >> 0) & 0xff;
|
|
cmd_size = 1;
|
|
}
|
|
|
|
ret_cnt = rtems_libi2c_write_bytes(minor,cmdbuf,cmd_size);
|
|
if (ret_cnt < 0) {
|
|
rc = -ret_cnt;
|
|
}
|
|
}
|
|
/*
|
|
* send write data
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
ret_cnt = rtems_libi2c_write_bytes(minor,buf,curr_cnt);
|
|
if (ret_cnt < 0) {
|
|
rc = -ret_cnt;
|
|
}
|
|
}
|
|
/*
|
|
* terminate transfer
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
rc = rtems_libi2c_send_stop(minor);
|
|
}
|
|
/*
|
|
* wait proper time for data to store: 5ms
|
|
* FIXME: select proper interval or poll, until device is finished
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
rc = spi_memdrv_wait_ms(5);
|
|
}
|
|
/*
|
|
* adjust bytecount to be sent and pointers
|
|
*/
|
|
bytes_sent += curr_cnt;
|
|
off += curr_cnt;
|
|
buf += curr_cnt;
|
|
}
|
|
rwargs->bytes_moved = bytes_sent;
|
|
return rc;
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
rtems_status_code spi_memdrv_read
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| read a block of data from flash |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
rtems_device_major_number major, /* major device number */
|
|
rtems_device_minor_number minor, /* minor device number */
|
|
void *arg /* ptr to read argument struct */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| o = ok or error code |
|
|
\*=========================================================================*/
|
|
{
|
|
rtems_status_code rc = RTEMS_SUCCESSFUL;
|
|
rtems_libio_rw_args_t *rwargs = arg;
|
|
off_t off = rwargs->offset;
|
|
int cnt = rwargs->count;
|
|
unsigned char *buf = (unsigned char *)rwargs->buffer;
|
|
unsigned char cmdbuf[4];
|
|
int ret_cnt = 0;
|
|
int cmd_size;
|
|
spi_memdrv_param_t *mem_param_ptr;
|
|
rtems_libi2c_tfr_mode_t tfr_mode = {
|
|
.baudrate = 20000000, /* maximum bits per second */
|
|
.bits_per_char = 8, /* how many bits per byte/word/longword? */
|
|
.lsb_first = FALSE, /* FALSE: send MSB first */
|
|
.clock_inv = FALSE, /* FALSE: non-inverted clock (high active) */
|
|
.clock_phs = FALSE /* FALSE: clock starts in middle of data tfr */
|
|
};
|
|
|
|
/*
|
|
* get mem parameters
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
rc = spi_memdrv_minor2param_ptr(minor,&mem_param_ptr);
|
|
}
|
|
/*
|
|
* check arguments
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
if ((cnt <= 0) ||
|
|
(cnt > mem_param_ptr->mem_size) ||
|
|
(off > (mem_param_ptr->mem_size-cnt))) {
|
|
rc = RTEMS_INVALID_SIZE;
|
|
}
|
|
else if (buf == NULL) {
|
|
rc = RTEMS_INVALID_ADDRESS;
|
|
}
|
|
}
|
|
/*
|
|
* select device, set transfer mode, address device
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
rc = rtems_libi2c_send_start(minor);
|
|
}
|
|
/*
|
|
* set transfer mode
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
tfr_mode.baudrate = mem_param_ptr->baudrate;
|
|
rc = -rtems_libi2c_ioctl(minor,
|
|
RTEMS_LIBI2C_IOCTL_SET_TFRMODE,
|
|
&tfr_mode);
|
|
}
|
|
/*
|
|
* address device
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
rc = rtems_libi2c_send_addr(minor,TRUE);
|
|
}
|
|
|
|
if (off >= mem_param_ptr->mem_size) {
|
|
/*
|
|
* HACK: beyond size of memory array? then read status register instead
|
|
*/
|
|
/*
|
|
* send read status register command
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
cmdbuf[0] = SPI_MEM_CMD_RDSR;
|
|
ret_cnt = rtems_libi2c_write_bytes(minor,cmdbuf,1);
|
|
if (ret_cnt < 0) {
|
|
rc = -ret_cnt;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/*
|
|
* send read command and address
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
cmdbuf[0] = SPI_MEM_CMD_READ;
|
|
if (mem_param_ptr->mem_size > 0x10000 /* 256*256 */) {
|
|
cmdbuf[1] = (off >> 16) & 0xff;
|
|
cmdbuf[2] = (off >> 8) & 0xff;
|
|
cmdbuf[3] = (off >> 0) & 0xff;
|
|
cmd_size = 4;
|
|
}
|
|
else if (mem_param_ptr->mem_size > 256) {
|
|
cmdbuf[1] = (off >> 8) & 0xff;
|
|
cmdbuf[2] = (off >> 0) & 0xff;
|
|
cmd_size = 3;
|
|
}
|
|
else {
|
|
cmdbuf[1] = (off >> 0) & 0xff;
|
|
cmd_size = 1;
|
|
}
|
|
ret_cnt = rtems_libi2c_write_bytes(minor,cmdbuf,cmd_size);
|
|
if (ret_cnt < 0) {
|
|
rc = -ret_cnt;
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* fetch read data
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
ret_cnt = rtems_libi2c_read_bytes (minor,buf,cnt);
|
|
if (ret_cnt < 0) {
|
|
rc = -ret_cnt;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* terminate transfer
|
|
*/
|
|
if (rc == RTEMS_SUCCESSFUL) {
|
|
rc = rtems_libi2c_send_stop(minor);
|
|
}
|
|
rwargs->bytes_moved = (rc == RTEMS_SUCCESSFUL) ? ret_cnt : 0;
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* driver operation tables
|
|
*/
|
|
rtems_driver_address_table spi_memdrv_rw_ops = {
|
|
.read_entry = spi_memdrv_read,
|
|
.write_entry = spi_memdrv_write
|
|
};
|
|
|
|
rtems_driver_address_table spi_memdrv_ro_ops = {
|
|
.read_entry = spi_memdrv_read,
|
|
};
|
|
|