blob: 6030480079fe67732ec392938bce51641c2de3e7 [file] [log] [blame]
/**
* Copyright (c) 2015 Runtime Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <errno.h>
#include "syscfg/syscfg.h"
#include "hal/hal_spi.h"
#include "compiler.h"
#include "port.h"
#include "mcu/hal_spi.h"
#include "mcu/samd21.h"
#include "sercom.h"
#include "spi.h"
#include "spi_interrupt.h"
#include "samd21_priv.h"
#define SAMD21_SPI_FLAG_MASTER (0x1)
#define SAMD21_SPI_FLAG_ENABLED (0x2)
#define SAMD21_SPI_FLAG_XFER (0x4)
struct samd21_hal_spi {
struct spi_module module;
const struct samd21_spi_config *pconfig;
uint8_t flags;
hal_spi_txrx_cb txrx_cb;
void *txrx_cb_arg;
};
#define HAL_SAMD21_SPI_MAX (6)
#if MYNEWT_VAL(SPI_0)
static struct samd21_hal_spi samd21_hal_spi0;
#endif
#if MYNEWT_VAL(SPI_1)
static struct samd21_hal_spi samd21_hal_spi1;
#endif
#if MYNEWT_VAL(SPI_2)
static struct samd21_hal_spi samd21_hal_spi2;
#endif
#if MYNEWT_VAL(SPI_3)
static struct samd21_hal_spi samd21_hal_spi3;
#endif
#if MYNEWT_VAL(SPI_4)
static struct samd21_hal_spi samd21_hal_spi4;
#endif
#if MYNEWT_VAL(SPI_5)
static struct samd21_hal_spi samd21_hal_spi5;
#endif
static struct samd21_hal_spi *samd21_hal_spis[HAL_SAMD21_SPI_MAX] = {
#if MYNEWT_VAL(SPI_0)
&samd21_hal_spi0,
#else
NULL,
#endif
#if MYNEWT_VAL(SPI_1)
&samd21_hal_spi1,
#else
NULL,
#endif
#if MYNEWT_VAL(SPI_2)
&samd21_hal_spi2,
#else
NULL,
#endif
#if MYNEWT_VAL(SPI_3)
&samd21_hal_spi3,
#else
NULL,
#endif
#if MYNEWT_VAL(SPI_4)
&samd21_hal_spi4,
#else
NULL,
#endif
#if MYNEWT_VAL(SPI_5)
&samd21_hal_spi5,
#else
NULL,
#endif
};
static int
samd21_hal_spi_rc_from_status(enum status_code status)
{
switch (status) {
case STATUS_OK:
return 0;
case STATUS_ERR_INVALID_ARG:
return EINVAL;
case STATUS_ERR_TIMEOUT:
case STATUS_ERR_DENIED:
case STATUS_ERR_OVERFLOW:
default:
return EIO;
}
}
static struct samd21_hal_spi *
samd21_hal_spi_resolve(int spi_num)
{
struct samd21_hal_spi *spi;
if (spi_num >= HAL_SAMD21_SPI_MAX) {
spi = NULL;
} else {
spi = samd21_hal_spis[spi_num];
}
return spi;
}
static struct samd21_hal_spi *
samd21_hal_spi_resolve_module(const struct spi_module *module)
{
struct samd21_hal_spi *spi;
int i;
for (i = 0; i < HAL_SAMD21_SPI_MAX; i++) {
spi = samd21_hal_spis[i];
if (&spi->module == module) {
return spi;
}
}
return NULL;
}
int
hal_spi_init(int spi_num, void *cfg, uint8_t spi_type)
{
struct samd21_hal_spi *spi;
if (cfg == NULL) {
return EINVAL;
}
spi = samd21_hal_spi_resolve(spi_num);
if (spi == NULL) {
return EINVAL;
}
memset(spi, 0, sizeof *spi);
spi->module.hw = samd21_sercom(spi_num);
if (spi->module.hw == NULL) {
return EINVAL;
}
switch (spi_type) {
case HAL_SPI_TYPE_MASTER:
spi->flags |= SAMD21_SPI_FLAG_MASTER;
break;
case HAL_SPI_TYPE_SLAVE:
break;
default:
return EINVAL;
}
spi->pconfig = cfg;
return 0;
}
static int
samd21_spi_config(struct samd21_hal_spi *spi,
struct hal_spi_settings *settings)
{
struct spi_config cfg;
int rc;
spi_get_config_defaults(&cfg);
cfg.pinmux_pad0 = spi->pconfig->pad0_pinmux;
cfg.pinmux_pad1 = spi->pconfig->pad1_pinmux;
cfg.pinmux_pad2 = spi->pconfig->pad2_pinmux;
cfg.pinmux_pad3 = spi->pconfig->pad3_pinmux;
cfg.mux_setting = spi->pconfig->dopo << SERCOM_SPI_CTRLA_DOPO_Pos |
spi->pconfig->dipo << SERCOM_SPI_CTRLA_DIPO_Pos;
/* apply the hal_settings */
switch (settings->word_size) {
case HAL_SPI_WORD_SIZE_8BIT:
cfg.character_size = SPI_CHARACTER_SIZE_8BIT;
break;
case HAL_SPI_WORD_SIZE_9BIT:
cfg.character_size = SPI_CHARACTER_SIZE_9BIT;
break;
default:
return EINVAL;
}
switch (settings->data_order) {
case HAL_SPI_LSB_FIRST:
cfg.data_order = SPI_DATA_ORDER_LSB;
break;
case HAL_SPI_MSB_FIRST:
cfg.data_order = SPI_DATA_ORDER_MSB;
break;
default:
return EINVAL;
}
switch (settings->data_mode) {
case HAL_SPI_MODE0:
cfg.transfer_mode = SPI_TRANSFER_MODE_0;
break;
case HAL_SPI_MODE1:
cfg.transfer_mode = SPI_TRANSFER_MODE_1;
break;
case HAL_SPI_MODE2:
cfg.transfer_mode = SPI_TRANSFER_MODE_2;
break;
case HAL_SPI_MODE3:
cfg.transfer_mode = SPI_TRANSFER_MODE_3;
break;
default:
return EINVAL;
}
if (spi->flags & SAMD21_SPI_FLAG_MASTER) {
cfg.mode = SPI_MODE_MASTER;
cfg.mode_specific.master.baudrate = settings->baudrate;
} else {
cfg.mode = SPI_MODE_SLAVE;
cfg.mode_specific.slave.frame_format = SPI_FRAME_FORMAT_SPI_FRAME;
cfg.mode_specific.slave.preload_enable = true;
}
rc = spi_init(&spi->module, spi->module.hw, &cfg);
if (rc != STATUS_OK) {
return EIO;
}
return 0;
}
int
hal_spi_config(int spi_num, struct hal_spi_settings *settings)
{
struct samd21_hal_spi *spi;
int rc;
spi = samd21_hal_spi_resolve(spi_num);
if (spi == NULL) {
return EINVAL;
}
if (spi->flags & SAMD21_SPI_FLAG_ENABLED) {
return EACCES;
}
rc = samd21_spi_config(spi, settings);
if (rc != 0) {
return rc;
}
return 0;
}
int
hal_spi_enable(int spi_num)
{
struct samd21_hal_spi *spi;
spi = samd21_hal_spi_resolve(spi_num);
if (spi == NULL) {
return EINVAL;
}
/* Configure and initialize software device instance of peripheral slave */
spi_enable(&spi->module);
spi->flags |= SAMD21_SPI_FLAG_ENABLED;
return 0;
}
int
hal_spi_disable(int spi_num)
{
struct samd21_hal_spi *spi;
spi = samd21_hal_spi_resolve(spi_num);
if (spi == NULL) {
return EINVAL;
}
spi_disable(&spi->module);
spi->flags &= ~SAMD21_SPI_FLAG_ENABLED;
return 0;
}
uint16_t
hal_spi_tx_val(int spi_num, uint16_t tx)
{
struct samd21_hal_spi *spi;
enum status_code status;
uint16_t rx;
spi = samd21_hal_spi_resolve(spi_num);
assert(spi != NULL);
assert(!(spi->flags & SAMD21_SPI_FLAG_XFER));
status = spi_transceive_wait(&spi->module, tx, &rx);
assert(status == 0);
return rx;
}
static void
samd21_hal_spi_cb(struct spi_module *module, enum spi_callback callback_type,
uint16_t xfr_count)
{
struct samd21_hal_spi *spi;
spi = samd21_hal_spi_resolve_module(module);
if (spi == NULL) {
return;
}
if (spi->flags & SAMD21_SPI_FLAG_XFER) {
spi->flags &= ~SAMD21_SPI_FLAG_XFER;
assert(spi->txrx_cb != NULL);
spi->txrx_cb(spi->txrx_cb_arg, xfr_count);
}
}
int
hal_spi_set_txrx_cb(int spi_num, hal_spi_txrx_cb txrx_cb, void *arg)
{
struct samd21_hal_spi *spi;
enum spi_callback type;
spi = samd21_hal_spi_resolve(spi_num);
if (spi == NULL) {
return EINVAL;
}
if (spi->flags & SAMD21_SPI_FLAG_XFER) {
return EACCES;
}
for (type = SPI_CALLBACK_BUFFER_TRANSMITTED;
type != SPI_CALLBACK_N;
type++) {
if (txrx_cb == NULL) {
spi_disable_callback(&spi->module, type);
} else {
spi_register_callback(&spi->module, samd21_hal_spi_cb, type);
spi_enable_callback(&spi->module, type);
}
}
spi->txrx_cb = txrx_cb;
spi->txrx_cb_arg = arg;
return 0;
}
static int
samd21_hal_spi_txrx_blocking(struct samd21_hal_spi *spi, void *txbuf,
void *rxbuf, uint16_t len)
{
enum status_code status;
spi->flags |= SAMD21_SPI_FLAG_XFER;
status = spi_transceive_buffer_wait(&spi->module, txbuf, rxbuf, len);
spi->flags &= ~SAMD21_SPI_FLAG_XFER;
return samd21_hal_spi_rc_from_status(status);
}
static int
samd21_hal_spi_txrx_nonblocking(struct samd21_hal_spi *spi, void *txbuf,
void *rxbuf, uint16_t len)
{
enum status_code status;
spi->flags |= SAMD21_SPI_FLAG_XFER;
status = spi_transceive_buffer_job(&spi->module, txbuf, rxbuf, len);
return samd21_hal_spi_rc_from_status(status);
}
int
hal_spi_txrx(int spi_num, void *txbuf, void *rxbuf, int len)
{
struct samd21_hal_spi *spi;
int rc;
spi = samd21_hal_spi_resolve(spi_num);
if (spi == NULL) {
return EINVAL;
}
if (len <= 0 || len > UINT16_MAX) {
return EINVAL;
}
if (spi->flags & SAMD21_SPI_FLAG_XFER) {
return EALREADY;
}
if (spi->flags & SAMD21_SPI_FLAG_MASTER) {
if (txbuf == NULL) {
return EINVAL;
}
} else {
if (txbuf == NULL && rxbuf == NULL) {
return EINVAL;
}
}
if (spi->txrx_cb == NULL) {
rc = samd21_hal_spi_txrx_blocking(spi, txbuf, rxbuf, len);
} else {
rc = samd21_hal_spi_txrx_nonblocking(spi, txbuf, rxbuf, len);
}
if (rc != 0) {
return rc;
}
return 0;
}
/**
* Not supported by the Atmel SPI API.
*/
int
hal_spi_slave_set_def_tx_val(int spi_num, uint16_t val)
{
struct samd21_hal_spi *spi;
spi = samd21_hal_spi_resolve(spi_num);
if (spi == NULL) {
return EINVAL;
}
spi_set_dummy(&spi->module, val);
return 0;
}
int
hal_spi_abort(int spi_num)
{
struct samd21_hal_spi *spi;
spi = samd21_hal_spi_resolve(spi_num);
if (spi == NULL) {
return EINVAL;
}
spi_abort_job(&spi->module);
spi->flags &= ~SAMD21_SPI_FLAG_XFER;
return 0;
}