summaryrefslogblamecommitdiffstats
path: root/bsps/arm/lpc24xx/spi/ssp.c
blob: 59fbf509db6c26a75cb43d50261a97a3ac3d5d88 (plain) (tree)
1
2
3
4
5
6
7


        
                                  


   











                                                                          
  










                                                                              

   
                    
                
                   

                        
 
                               
 
                        

                
               
                             












                              

                



                             
 


                                             
 



                                                             

 




                                
 



                             
 

                                                           

   

                                             

   


                                                 
 

                           
 






                                                           
     

                         

   
                                                                       
 

                               

   


                               
 





                                
 







                                                       
   
 

                                                                           
 





                                                                
 

                          
 






                                    
 






                                     
 
              

 




                                     
 























                                                 
     

                  

   



                                                 
 






                         
 






                                          

   


                                      

 


                              

 




                                          
 

                       
 



                                                           
 



                                                         
 
                      
 


                                 
       
 



                     
     
   
 
 



                                            
 

                   
 


                                                        
 

                              
 










                                                    

 



                                

 
                       
 

                       

   




                                         
 

                        
 



                                              
 
                                 
 






                                      
 

                          
 


                                           
 

                                            

 
                                           
 
                       
 
                                 
 

                                     

   





                                                                       
 


                                                                       
 

                          
 








                                       
   
 
                                                
 







                                           

 


                                        

 

                       
 















                                                                 

   




                                                

 


                                            
                                                    












                                            
                                                    


                                   
 




                                      
 














                                                    
/**
 * @file
 *
 * @ingroup RTEMSBSPsARMLPC24XXSSP
 */

/*
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (C) 2008, 2019 embedded brains GmbH
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <bsp/ssp.h>
#include <bsp.h>
#include <bsp/io.h>
#include <bsp/irq.h>
#include <bsp/lpc24xx.h>

#include <rtems/score/assert.h>

#include <dev/spi/spi.h>

typedef struct {
  spi_bus base;
  volatile lpc24xx_ssp *regs;
  size_t tx_todo;
  const uint8_t *tx_buf;
  size_t tx_inc;
  size_t rx_todo;
  uint8_t *rx_buf;
  size_t rx_inc;
  const spi_ioc_transfer *msg;
  uint32_t msg_todo;
  int msg_error;
  rtems_binary_semaphore sem;
  lpc24xx_module module;
  rtems_vector_number irq;
} lpc24xx_ssp_bus;

typedef struct {
  volatile lpc24xx_ssp *regs;
  lpc24xx_module module;
  rtems_vector_number irq;
} lpc24xx_ssp_config;

static uint8_t lpc24xx_ssp_trash;

static const uint8_t lpc24xx_ssp_idle = 0xff;

static void lpc24xx_ssp_done(lpc24xx_ssp_bus *bus, int error)
{
  bus->msg_error = error;
  rtems_binary_semaphore_post(&bus->sem);
}

static int lpc24xx_ssp_do_setup(
  lpc24xx_ssp_bus *bus,
  uint32_t speed_hz,
  uint32_t mode
)
{
  volatile lpc24xx_ssp *regs;
  uint32_t clk;
  uint32_t scr_plus_one;
  uint32_t cr0;

  if (speed_hz > bus->base.max_speed_hz || speed_hz == 0) {
    return -EINVAL;
  }

  if ((mode & ~(SPI_CPOL | SPI_CPHA)) != 0) {
    return -EINVAL;
  }

  regs = bus->regs;
  clk = bus->base.max_speed_hz;
  scr_plus_one = (clk + speed_hz - 1) / speed_hz;

  if (scr_plus_one > 256) {
    uint32_t pre;

    pre = (scr_plus_one + 255) / 256;

    if (pre <= 127) {
      scr_plus_one = (clk / pre + speed_hz - 1) / speed_hz;
    } else {
      pre = 127;
      scr_plus_one = 256;
    }

    regs->cpsr = 2 * pre;
  }

  cr0 = SET_SSP_CR0_DSS(0, 0x7) | SET_SSP_CR0_SCR(0, scr_plus_one - 1);

  if ((mode & SPI_CPOL) != 0) {
    cr0 |= SSP_CR0_CPOL;
  }

  if ((mode & SPI_CPHA) != 0) {
    cr0 |= SSP_CR0_CPHA;
  }

  regs->cr0 = cr0;

  bus->base.speed_hz = speed_hz;
  bus->base.mode = mode;
  return 0;
}

static bool lpc24xx_ssp_msg_setup(
  lpc24xx_ssp_bus *bus,
  const spi_ioc_transfer *msg
)
{
  if (msg->cs_change == 0 || msg->bits_per_word != 8) {
    lpc24xx_ssp_done(bus, -EINVAL);
    return false;
  }

  if (msg->speed_hz != bus->base.speed_hz || msg->mode != bus->base.mode) {
    int error;

    error = lpc24xx_ssp_do_setup(bus, msg->speed_hz, msg->mode);
    if (error != 0) {
      lpc24xx_ssp_done(bus, error);
      return false;
    }
  }

  bus->tx_todo = msg->len;
  bus->rx_todo = msg->len;

  if (msg->tx_buf != NULL) {
    bus->tx_buf = msg->tx_buf;
    bus->tx_inc = 1;
  } else {
    bus->tx_buf = &lpc24xx_ssp_idle;
    bus->tx_inc = 0;
  }

  if (msg->rx_buf != NULL) {
    bus->rx_buf = msg->rx_buf;
    bus->rx_inc = 1;
  } else {
    bus->rx_buf = &lpc24xx_ssp_trash;
    bus->rx_inc = 0;
  }

  return true;
}

static bool lpc24xx_ssp_do_tx_and_rx(
  lpc24xx_ssp_bus *bus,
  volatile lpc24xx_ssp *regs,
  uint32_t sr
)
{
  size_t tx_todo;
  const uint8_t *tx_buf;
  size_t tx_inc;
  size_t rx_todo;
  uint8_t *rx_buf;
  size_t rx_inc;
  uint32_t imsc;

  tx_todo = bus->tx_todo;
  tx_buf = bus->tx_buf;
  tx_inc = bus->tx_inc;
  rx_todo = bus->rx_todo;
  rx_buf = bus->rx_buf;
  rx_inc = bus->rx_inc;

  while (tx_todo > 0 && (sr & SSP_SR_TNF) != 0) {
    regs->dr = *tx_buf;
    --tx_todo;
    tx_buf += tx_inc;

    if (rx_todo > 0 && (sr & SSP_SR_RNE) != 0) {
      *rx_buf = regs->dr;
      --rx_todo;
      rx_buf += rx_inc;
    }

    sr = regs->sr;
  }

  while (rx_todo > 0 && (sr & SSP_SR_RNE) != 0) {
    *rx_buf = regs->dr;
    --rx_todo;
    rx_buf += rx_inc;

    sr = regs->sr;
  }

  bus->tx_todo = tx_todo;
  bus->tx_buf = tx_buf;
  bus->rx_todo = rx_todo;
  bus->rx_buf = rx_buf;

  imsc = 0;

  if (tx_todo > 0) {
    imsc |= SSP_IMSC_TXIM;
  } else if (rx_todo > 0) {
    imsc |= SSP_IMSC_RXIM | SSP_IMSC_RTIM;
    regs->icr = SSP_ICR_RTRIS;
  }

  regs->imsc = imsc;

  return tx_todo == 0 && rx_todo == 0;
}

static void lpc24xx_ssp_start(
  lpc24xx_ssp_bus *bus,
  const spi_ioc_transfer *msg
)
{
  while (true) {
    if (lpc24xx_ssp_msg_setup(bus, msg)) {
      volatile lpc24xx_ssp *regs;
      uint32_t sr;
      bool next_msg;

      regs = bus->regs;
      sr = regs->sr;

      if ((sr & (SSP_SR_RNE | SSP_SR_TFE)) != SSP_SR_TFE) {
        lpc24xx_ssp_done(bus, -EIO);
        break;
      }

      next_msg = lpc24xx_ssp_do_tx_and_rx(bus, regs, sr);
      if (!next_msg) {
        break;
      }

      --bus->msg_todo;

      if (bus->msg_todo == 0) {
        lpc24xx_ssp_done(bus, 0);
        break;
      }

      ++msg;
      bus->msg = msg;
    } else {
      break;
    }
  }
}

static void lpc24xx_ssp_interrupt(void *arg)
{
  lpc24xx_ssp_bus *bus;
  volatile lpc24xx_ssp *regs;

  bus = arg;
  regs = bus->regs;

  while (true) {
    if (lpc24xx_ssp_do_tx_and_rx(bus, regs, regs->sr)) {
      --bus->msg_todo;

      if (bus->msg_todo > 0) {
        ++bus->msg;

        if (!lpc24xx_ssp_msg_setup(bus, bus->msg)) {
          break;
        }
      } else {
        lpc24xx_ssp_done(bus, 0);
        break;
      }
    } else {
      break;
    }
  }
}

static int lpc24xx_ssp_transfer(
  spi_bus *base,
  const spi_ioc_transfer *msgs,
  uint32_t msg_count
)
{
  lpc24xx_ssp_bus *bus;

  if (msg_count == 0) {
    return 0;
  }

  bus = (lpc24xx_ssp_bus *) base;
  bus->msg = msgs;
  bus->msg_todo = msg_count;
  lpc24xx_ssp_start(bus, msgs);
  rtems_binary_semaphore_wait(&bus->sem);

  return bus->msg_error;
}

static void lpc24xx_ssp_destroy(spi_bus *base)
{
  lpc24xx_ssp_bus *bus;
  rtems_status_code sc;

  bus = (lpc24xx_ssp_bus *) base;

  sc = rtems_interrupt_handler_remove(
    bus->irq,
    lpc24xx_ssp_interrupt,
    bus
  );
  _Assert(sc == RTEMS_SUCCESSFUL);
  (void) sc;

  /* Disable SSP module */
  bus->regs->cr1 = 0;

  sc = lpc24xx_module_disable(bus->module);
  _Assert(sc == RTEMS_SUCCESSFUL);
  (void) sc;

  rtems_binary_semaphore_destroy(&bus->sem);
  spi_bus_destroy_and_free(&bus->base);
}

static int lpc24xx_ssp_setup(spi_bus *base)
{
  lpc24xx_ssp_bus *bus;

  bus = (lpc24xx_ssp_bus *) base;

  if (bus->base.bits_per_word != 8) {
    return -EINVAL;
  }

  return lpc24xx_ssp_do_setup(bus, bus->base.speed_hz, bus->base.mode);
}

static int lpc24xx_ssp_init(lpc24xx_ssp_bus *bus)
{
  rtems_status_code sc;

  sc = lpc24xx_module_enable(bus->module, LPC24XX_MODULE_PCLK_DEFAULT);
  _Assert(sc == RTEMS_SUCCESSFUL);
  (void) sc;

  /* Disable SSP module */
  bus->regs->cr1 = 0;

  sc = rtems_interrupt_handler_install(
    bus->irq,
    "SSP",
    RTEMS_INTERRUPT_UNIQUE,
    lpc24xx_ssp_interrupt,
    bus
  );
  if (sc != RTEMS_SUCCESSFUL) {
    return EAGAIN;
  }

  rtems_binary_semaphore_init(&bus->sem, "SSP");

  /* Initialize SSP module */
  bus->regs->dmacr = 0;
  bus->regs->imsc = 0;
  bus->regs->cpsr = 2;
  bus->regs->cr0 = SET_SSP_CR0_DSS(0, 0x7);
  bus->regs->cr1 = SSP_CR1_SSE;

  return 0;
}

static int spi_bus_register_lpc24xx_ssp(
  const char *bus_path,
  const lpc24xx_ssp_config *config
)
{
  lpc24xx_ssp_bus *bus;
  int eno;

  bus = (lpc24xx_ssp_bus *) spi_bus_alloc_and_init(sizeof(*bus));
  if (bus == NULL) {
    return -1;
  }

  bus->base.max_speed_hz = LPC24XX_PCLK / 2;
  bus->base.bits_per_word = 8;
  bus->base.speed_hz = bus->base.max_speed_hz;
  bus->regs = config->regs;
  bus->module = config->module;
  bus->irq = config->irq;

  eno = lpc24xx_ssp_init(bus);
  if (eno != 0) {
    (*bus->base.destroy)(&bus->base);
    rtems_set_errno_and_return_minus_one(eno);
  }

  bus->base.transfer = lpc24xx_ssp_transfer;
  bus->base.destroy = lpc24xx_ssp_destroy;
  bus->base.setup = lpc24xx_ssp_setup;

  return spi_bus_register(&bus->base, bus_path);
}

int lpc24xx_register_ssp_0(void)
{
  static const lpc24xx_ssp_config config = {
    .regs = (volatile lpc24xx_ssp *) SSP0_BASE_ADDR,
    .module = LPC24XX_MODULE_SSP_0,
    .irq = LPC24XX_IRQ_SPI_SSP_0
  };

  return spi_bus_register_lpc24xx_ssp(
    LPC24XX_SSP_0_BUS_PATH,
    &config
  );
}

int lpc24xx_register_ssp_1(void)
{
  static const lpc24xx_ssp_config config = {
    .regs = (volatile lpc24xx_ssp *) SSP1_BASE_ADDR,
    .module = LPC24XX_MODULE_SSP_1,
    .irq = LPC24XX_IRQ_SSP_1
  };

  return spi_bus_register_lpc24xx_ssp(
    LPC24XX_SSP_2_BUS_PATH,
    &config
  );
}

#ifdef ARM_MULTILIB_ARCH_V7M
int lpc24xx_register_ssp_2(void)
{
  static const lpc24xx_ssp_config config = {
    .regs = (volatile lpc24xx_ssp *) SSP2_BASE_ADDR,
    .module = LPC24XX_MODULE_SSP_2,
    .irq = LPC24XX_IRQ_SSP_2
  };

  return spi_bus_register_lpc24xx_ssp(
    LPC24XX_SSP_2_BUS_PATH,
    &config
  );
}
#endif