/*
drv_spi.c
LP2378 SPI driver
Paul Lever
Dec. 2007
*/
#include "includes.h" // OS includes
#include "driver.h"
#include "drv_spi.h"
#include "init.h"
#define SPI_DEFAULT_CLOCK 1000000
// we are supporting two SSP devices
#define SPI_DEVICE_COUNT 2
// define data for each supported device
typedef struct _SPI_DEVICE
{
INT32U Clock; // clock rate
INT8U OpenCount; // track opens handles to this device
INT16U* pSSPnCR0; // SSP register set
INT8U* pSSPnCR1;
INT16U* pSSPnDR;
INT8U* pSSPnSR;
INT8U* pSSPnCPSR;
INT8U* pSSPnIMSC;
INT8U* pSSPnRIS;
INT8U* pSSPnMIS;
INT8U* pSSPnICR;
INT16U* pSSPnDMACR;
INT32U PinSck; // I/O pins
INT32U PinMISO;
INT32U PinMOSI;
} SPI_DEVICE, *PSPI_DEVICE;
// define a data extension to hold data required by this driver
typedef struct _SPI_EXT
{
SPI_DEVICE Device[SPI_DEVICE_COUNT]; // each supported device
} SPI_EXT, *PSPI_EXT;
#define SPI0_SCK (1<<15) /* - sck0 P0.15*/
#define SPI0_MISO (1<<17) /* - MISO0 P0.17*/
#define SPI0_MOSI (1<<18) /* - MOSI0 P0.18*/
#define SPI1_SCK (1<<7) /* UEXT9 - sck P0.7*/
#define SPI1_MISO (1<<8) /* UEXT7 - MISO P0.8*/
#define SPI1_MOSI (1<<9) /* UEXT8 - MOSI P0.9*/
//status register
#define SSP_TNF 0x2
#define SSP_RNE 0x4
//control register0
#define SSP_SPIMODE 0x07
//control register1
#define SSP_ENABLE 0x02
// prototypes
INT8U Spi_Open(INT8U DeviceIndex, PVOID pContext, INT8U Flags);
INT8U Spi_Close(INT8U DeviceIndex, PVOID pContext);
INT8U Spi_Write(INT8U DeviceIndex, PVOID pContext, PVOID pBuffer,
INT32U* pCount);
INT8U Spi_Ioctl(INT8U DeviceIndex, PVOID pContext, INT8U Control,
PVOID pBuffer, INT32U* pCount);
static void SpiSetClock(PSPI_DEVICE pDevice, INT32U ClockRate);
static INT8U SpiInit(PSPI_DEVICE pDevice, INT32U ClockRate);
/*
Spi_Init - SPI driver initialization routine
*/
INT8U Spi_Init(POSDRV_DISPATCH pDispatch, PVOID *ppContext,
POSDRV_CONFIG pConfig)
{
PSPI_DEVICE pDevice;
// allocate the data extension for use to save data for this driver
*ppContext = DRV_MEMALLOC(sizeof(SPI_EXT));
if (*ppContext == NULL)
{
return OS_DRV_BUFFER_SIZE;
}
// zero the data extension
memset(*ppContext, 0, sizeof(SPI_EXT));
// initialize the addresses for the supported devices
pDevice = &((PSPI_EXT) *ppContext)->Device[0];
pDevice->pSSPnCR0 = (INT16U*) SSP0CR0;
pDevice->pSSPnCR1 = (INT8U*) SSP0CR1;
pDevice->pSSPnDR = (INT16U*) SSP0DR;
pDevice->pSSPnSR = (INT8U*) SSP0SR;
pDevice->pSSPnCPSR = (INT8U*) SSP0CPSR;
pDevice->pSSPnIMSC = (INT8U*) SSP0IMSC;
pDevice->pSSPnRIS = (INT8U*) SSP0RIS;
pDevice->pSSPnMIS = (INT8U*) SSP0MIS;
pDevice->pSSPnICR = (INT8U*) SSP0ICR;
pDevice->pSSPnDMACR = (INT16U*) SSP0DMACR;
//set the pins
pDevice->PinSck = SPI0_SCK;
pDevice->PinMISO = SPI0_MISO;
pDevice->PinMOSI = SPI0_MOSI;
// create a name for each device that we support
OSDRV_AddName(pConfig, "SPI0");
// now do the second device
pDevice = &((PSPI_EXT) *ppContext)->Device[1];
pDevice->pSSPnCR0 = (INT16U*) SSP1CR0;
pDevice->pSSPnCR1 = (INT8U*) SSP1CR1;
pDevice->pSSPnDR = (INT16U*) SSP1DR;
pDevice->pSSPnSR = (INT8U*) SSP1SR;
pDevice->pSSPnCPSR = (INT8U*) SSP1CPSR;
pDevice->pSSPnIMSC = (INT8U*) SSP1IMSC;
pDevice->pSSPnRIS = (INT8U*) SSP1RIS;
pDevice->pSSPnMIS = (INT8U*) SSP1MIS;
pDevice->pSSPnICR = (INT8U*) SSP1ICR;
pDevice->pSSPnDMACR = (INT16U*) SSP1DMACR;
//set the pins
pDevice->PinSck = SPI1_SCK;
pDevice->PinMISO = SPI1_MISO;
pDevice->PinMOSI = SPI1_MOSI;
// create a name for each device that we support
OSDRV_AddName(pConfig, "SPI1");
// setup the supported entry points for this driver
pDispatch->pOpen = Spi_Open; // required
pDispatch->pClose = Spi_Close; // required
pDispatch->pRead = NULL; // optional, not currently supported in this driver
pDispatch->pWrite = Spi_Write; // optional
pDispatch->pIoctl = Spi_Ioctl; // optional
// set this driver to accept non-serialized requests on each handle
// any synchronization needs to be handled by the driver or caller above us
pConfig->Exclusive = FALSE;
// return success
return OS_DRV_NO_ERR;
}
/*
Spi_Open - open function for driver
*/
INT8U Spi_Open(INT8U DeviceIndex, PVOID pContext, INT8U Flags)
{
PSPI_DEVICE pDevice = &((PSPI_EXT) pContext)->Device[DeviceIndex];
INT8U err;
if (DeviceIndex >= SPI_DEVICE_COUNT)
{
// someone passed us a bogus device
DEBUGMSG(TRUE, ("SPI_DRV attempt to open bad device index: 0x%X\n\r", DeviceIndex));
return OS_DRV_NO_DEVICE;
}
// we only allow one handle to the device at a time in this driver
if (((PSPI_EXT) pContext)->Device[DeviceIndex].OpenCount > 0)
{
DEBUGMSG(TRUE, ("SPI_DRV attempt to open too many handles\n\r"));
return OS_DRV_NO_DEVICE;
}
//initialize the SPI port
err = SpiInit(pDevice, SPI_DEFAULT_CLOCK);
if (err != OS_DRV_NO_ERR)
{
return err;
}
// count the open handles
((PSPI_EXT) pContext)->Device[DeviceIndex].OpenCount++;
// return success
return OS_DRV_NO_ERR;
}
/*
Spi_Close - close function for driver
*/
INT8U Spi_Close(INT8U DeviceIndex, PVOID pContext)
{
if (DeviceIndex >= SPI_DEVICE_COUNT)
{
// someone passed us a bogus device
DEBUGMSG(TRUE, ("SPI_DRV attempt to close bad device index: 0x%X\n\r", DeviceIndex));
return OS_DRV_NO_DEVICE;
}
// keep count of the open handles
((PSPI_EXT) pContext)->Device[DeviceIndex].OpenCount--;
// return success
return OS_DRV_NO_ERR;
}
/*
Spi_Write - write function for driver
*/
INT8U Spi_Write(INT8U DeviceIndex, PVOID pContext, PVOID pBuffer,
INT32U* pCount)
{
INT32U length = *pCount;
INT32U ii;
PSPI_DEVICE pDevice = &((PSPI_EXT) pContext)->Device[DeviceIndex];
INT8U tmp;
*pCount = 0;
if (DeviceIndex >= SPI_DEVICE_COUNT)
{
// someone passed us a bogus device
DEBUGMSG(TRUE, ("SPI_DRV attempt to Write bad device index: 0x%X\n\r", DeviceIndex));
return OS_DRV_NO_DEVICE;
}
//write out the bytes
for (ii = 0; ii < length; ii++)
{
while (!((tmp = READREG8(pDevice->pSSPnSR)) & SSP_TNF))
{
DEBUGMSG(TRUE, ("SPI_DRV SPSR: 0x%X\n\r", tmp));
}
WRITEREG16(pDevice->pSSPnDR, (INT16U)((INT8U*)pBuffer)[ii]);
while (!((tmp = READREG8(pDevice->pSSPnSR)) & SSP_RNE))
{
DEBUGMSG(TRUE, ("SPI_DRV SPSR: 0x%X\n\r", tmp));
}
//read back any input data
((INT8U*) pBuffer)[ii] = (INT8U) READREG16(pDevice->pSSPnDR);
}
*pCount = length;
return OS_DRV_NO_ERR;
}
/*
Spi_Ioctl - Ioctl function for driver
*/
INT8U Spi_Ioctl(INT8U DeviceIndex, PVOID pContext, INT8U Control,
PVOID pBuffer, INT32U* pCount)
{
PSPI_DEVICE pDevice = &((PSPI_EXT) pContext)->Device[DeviceIndex];
if (DeviceIndex >= SPI_DEVICE_COUNT)
{
// someone passed us a bogus device
DEBUGMSG(TRUE, ("SPI_DRV attempt to Ioctl bad device index: 0x%X\n\r", DeviceIndex));
return OS_DRV_NO_DEVICE;
}
// determine which Control code is being used
switch (Control)
{
// set master SPI clock
case IOCTL_SPI_SET_CLOCK:
// make sure we have enough room in the return buffer
if (*pCount != sizeof(INT32U))
{
return OS_DRV_BUFFER_SIZE;
}
SpiSetClock(pDevice, *((INT32U *) pBuffer));
// set the return length equal to the amount of data returned
if (pCount != NULL)
{
*pCount = 0;
}
break;
// return the current master clock
case IOCTL_SPI_GET_CLOCK:
// make sure we have enough room in the return buffer
if (*pCount != sizeof(INT32U))
{
return OS_DRV_BUFFER_SIZE;
}
// return the current open count for this device
*((INT32U *) pBuffer) = (long) pDevice->Clock;
// set the return length equal to the amount of data returned
if (pCount != NULL)
{
*pCount = sizeof(INT32U);
}
break;
default:
// we don't know this control code
// set the return length equal to the amount of data returned
if (pCount != NULL)
{
*pCount = 0;
}
return OS_DRV_UNSUPPORTED;
}
return OS_DRV_NO_ERR;
}
/*
SpiInit - initialize the SPI port
*/
static INT8U SpiInit(PSPI_DEVICE pDevice, INT32U ClockRate)
{
// pinsel0_P0 must be previously setup for SSP use.
// set their direction
WRITEREG32(FIO0DIR, (READREG32(FIO0DIR) | pDevice->PinSck | pDevice->PinMOSI) & ~pDevice->PinMISO);
// set master mode, 8-bits, sample on first clock edge, no ints
WRITEREG16(pDevice->pSSPnCR0, SSP_SPIMODE);
SpiSetClock(pDevice, SPI_DEFAULT_CLOCK);
//enable
WRITEREG16(pDevice->pSSPnCR1, SSP_ENABLE);
return OS_DRV_NO_ERR;
}
/*
SpiSetClock - set the SPI master clock
*/
static void SpiSetClock(PSPI_DEVICE pDevice, INT32U ClockRate)
{
//assumes SPI PCLK set to divide by 1
INT32U clk = ((getFcclk()) / ClockRate);
clk = (clk < 2) ? 2 : clk;
WRITEREG8(pDevice->pSSPnCPSR, (clk & 0xFE));
pDevice->Clock = ClockRate;
}