int spi_acquire(spi_t bus, spi_cs_t cs, spi_mode_t mode, spi_clk_t clk) { /* get exclusive access to the device */ mutex_lock(&locks[bus]); /* power on the device */ poweron(bus); /* configure bus clock, in synchronous mode its calculated from * BAUD.reg = (f_ref / (2 * f_bus) - 1) * with f_ref := CLOCK_CORECLOCK as defined by the board */ dev(bus)->BAUD.reg = (uint8_t)(((uint32_t)CLOCK_CORECLOCK) / (2 * clk) - 1); /* configure device to be master and set mode and pads, * * NOTE: we could configure the pads already during spi_init, but for * efficiency reason we do that here, so we can do all in one single write * to the CTRLA register */ dev(bus)->CTRLA.reg = (SERCOM_SPI_CTRLA_MODE(0x3) | /* 0x3 -> master */ SERCOM_SPI_CTRLA_DOPO(spi_config[bus].mosi_pad) | SERCOM_SPI_CTRLA_DIPO(spi_config[bus].miso_pad) | (mode << SERCOM_SPI_CTRLA_CPOL_Pos)); /* also no synchronization needed here, as CTRLA is write-synchronized */ /* finally enable the device */ dev(bus)->CTRLA.reg |= SERCOM_SPI_CTRLA_ENABLE; while (dev(bus)->SYNCBUSY.reg & SERCOM_SPI_SYNCBUSY_ENABLE) {} return SPI_OK; }
int spi_init_master(spi_t dev, spi_conf_t conf, spi_speed_t speed) { SercomSpi* spi_dev = spi[dev].dev; uint8_t dopo = 0; uint8_t dipo = 0; uint8_t cpha = 0; uint8_t cpol = 0; uint32_t f_baud = 0; switch(speed) { case SPI_SPEED_100KHZ: f_baud = 100000; break; case SPI_SPEED_400KHZ: f_baud = 400000; break; case SPI_SPEED_1MHZ: f_baud = 1000000; break; case SPI_SPEED_5MHZ: return -1; case SPI_SPEED_10MHZ: return -1; } switch(conf) { case SPI_CONF_FIRST_RISING: /**< first data bit is transacted on the first rising SCK edge */ cpha = 0; cpol = 0; break; case SPI_CONF_SECOND_RISING:/**< first data bit is transacted on the second rising SCK edge */ cpha = 1; cpol = 0; break; case SPI_CONF_FIRST_FALLING:/**< first data bit is transacted on the first falling SCK edge */ cpha = 0; cpol = 1; break; case SPI_CONF_SECOND_FALLING:/**< first data bit is transacted on the second falling SCK edge */ cpha = 1; cpol = 1; break; } /* Enable sercom4 in power manager */ MCLK->APBCMASK.reg |= spi[dev].mclk; /* Setup clock */ GCLK->PCHCTRL[ spi[dev].gclk_id ].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN_GCLK0; while (!(GCLK->PCHCTRL[spi[dev].gclk_id].reg & GCLK_PCHCTRL_CHEN)); /* SCLK+MOSI = output */ gpio_init(spi[dev].sclk.pin, GPIO_DIR_OUT, GPIO_NOPULL); gpio_init(spi[dev].mosi.pin, GPIO_DIR_OUT, GPIO_NOPULL); /* MISO = input */ gpio_init(spi[dev].miso.pin, GPIO_DIR_IN, GPIO_PULLUP); /* * Set alternate funcion (PMUX) for our ports. */ gpio_init_mux(spi[dev].sclk.pin, spi[dev].sclk.pmux); gpio_init_mux(spi[dev].miso.pin, spi[dev].miso.pmux); gpio_init_mux(spi[dev].mosi.pin, spi[dev].mosi.pmux); /* pin pad mapping */ dipo = spi[dev].dipo; dopo = spi[dev].dopo; /* Disable spi to write config */ spi_dev->CTRLA.bit.ENABLE = 0; while (spi_dev->SYNCBUSY.reg); /* setup baud */ spi_dev->BAUD.bit.BAUD = (uint8_t) (((uint32_t) GCLK_REF) / (2 * f_baud) - 1); /* Syncronous mode*/ spi_dev->CTRLA.reg |= SERCOM_SPI_CTRLA_MODE(0x3) /* 0x2 = slave 0x3 = master */ | (SERCOM_SPI_CTRLA_DOPO(dopo)) | (SERCOM_SPI_CTRLA_DIPO(dipo)) | (cpha << SERCOM_SPI_CTRLA_CPHA_Pos) | (cpol << SERCOM_SPI_CTRLA_CPOL_Pos); while (spi_dev->SYNCBUSY.reg); spi_dev->CTRLB.reg = (SERCOM_SPI_CTRLB_CHSIZE(0) | SERCOM_SPI_CTRLB_RXEN); while(spi_dev->SYNCBUSY.reg); spi_poweron(dev); return 0; }
/** * \brief Initialize hardware and driver instance * * This function configures the clock system for the specified SERCOM module, * sets up the related pins and their MUX, initializes the SERCOM in SPI master * mode, and prepares the driver instance for operation. * * \pre \ref system_init() must have been called prior to this function. * * The SERCOM SPI module is left disabled after initialization, and must be * enabled with \ref spi_master_vec_enable() before a transfer can be done. * * \param[out] module Driver instance to initialize. * \param[in,out] sercom SERCOM module to initialize and associate driver * instance with. * \param[in] config Driver configuration to use. * * \return Status of initialization. * \retval STATUS_OK if initialization succeeded. * \retval STATUS_ERR_INVALID_ARG if driver has been misconfigured. */ enum status_code spi_master_vec_init(struct spi_master_vec_module *const module, Sercom *const sercom, const struct spi_master_vec_config *const config) { Assert(module); Assert(sercom); Assert(config); enum status_code status; SercomSpi *const spi_hw = &(sercom->SPI); struct system_gclk_chan_config gclk_chan_conf; uint16_t tmp_baud; uint32_t sercom_index = _sercom_get_sercom_inst_index((Sercom *)spi_hw); #if (SAML21) || (SAML22) || (SAMC20) || (SAMC21) || (SAMR30) uint32_t pm_index = sercom_index + MCLK_APBCMASK_SERCOM0_Pos; #else uint32_t pm_index = sercom_index + PM_APBCMASK_SERCOM0_Pos; #endif uint32_t gclk_index = sercom_index + SERCOM0_GCLK_ID_CORE; uint32_t gclk_hz; module->sercom = sercom; /* Enable clock for the module interface */ system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBC, 1 << pm_index); /* Set up the GCLK for the module */ system_gclk_chan_get_config_defaults(&gclk_chan_conf); gclk_chan_conf.source_generator = config->gclk_generator; system_gclk_chan_set_config(gclk_index, &gclk_chan_conf); system_gclk_chan_enable(gclk_index); sercom_set_gclk_generator(config->gclk_generator, false); _spi_master_vec_wait_for_sync(spi_hw); /* Set up the SERCOM SPI module as master */ spi_hw->CTRLA.reg = SERCOM_SPI_CTRLA_MODE(0x3); spi_hw->CTRLA.reg |= (uint32_t)config->mux_setting | config->transfer_mode | config->data_order | ((config->run_in_standby || system_is_debugger_present()) ? SERCOM_SPI_CTRLA_RUNSTDBY : 0); /* Get baud value from configured baudrate and internal clock rate */ gclk_hz = system_gclk_chan_get_hz(gclk_index); status = _sercom_get_sync_baud_val(config->baudrate, gclk_hz, &tmp_baud); if (status != STATUS_OK) { /* Baud rate calculation error! */ return STATUS_ERR_INVALID_ARG; } spi_hw->BAUD.reg = (uint8_t)tmp_baud; /* Configure the pin multiplexers */ _spi_master_vec_pinmux_helper(config->pinmux_pad0, sercom, 0); _spi_master_vec_pinmux_helper(config->pinmux_pad3, sercom, 3); /* SERCOM PAD1 and PAD2 are used for slave SS. * This is a SPI master driver, so control of slave SS must be left to * the PORT module, i.e., peripheral MUX should not be set for that pin. * DOPO controls which PAD is used for slave SS: * If DOPO is odd, SERCOM_PAD1 is SS: SERCOM_PAD2 can be MUXed. * If DOPO is even, SERCOM_PAD2 is SS: SERCOM_PAD1 can be MUXed. */ if (config->mux_setting & (1 << SERCOM_SPI_CTRLA_DOPO_Pos)) { _spi_master_vec_pinmux_helper(config->pinmux_pad2, sercom, 2); } else { _spi_master_vec_pinmux_helper(config->pinmux_pad1, sercom, 1); } /* Initialize our instance and register interrupt handler + data */ module->rx_bufdesc_ptr = NULL; module->tx_bufdesc_ptr = NULL; module->direction = SPI_MASTER_VEC_DIRECTION_IDLE; module->status = STATUS_OK; #ifdef CONF_SPI_MASTER_VEC_OS_SUPPORT CONF_SPI_MASTER_VEC_CREATE_SEMAPHORE(module->busy_semaphore); #endif _sercom_set_handler(sercom_index, _spi_master_vec_int_handler); _sercom_instances[sercom_index] = module; return STATUS_OK; }
/** Initialize the SPI peripheral * * Configures the pins used by SPI, sets a default format and frequency, and enables the peripheral * @param[out] obj The SPI object to initialize * @param[in] mosi The pin to use for MOSI * @param[in] miso The pin to use for MISO * @param[in] sclk The pin to use for SCLK * @param[in] ssel The pin to use for SSEL */ void spi_init(spi_t *obj, PinName mosi, PinName miso, PinName sclk, PinName ssel) { uint16_t baud = 0; uint32_t ctrla = 0; uint32_t ctrlb = 0; enum status_code error_code; if (g_sys_init == 0) { system_init(); g_sys_init = 1; } /* TODO: Calculate SERCOM instance from pins */ /* TEMP: Giving external SPI module value of SAMR21 for now */ pSPI_SERCOM(obj) = EXT1_SPI_MODULE; /* Disable SPI */ spi_disable(obj); /* Check if reset is in progress. */ if (_SPI(obj).CTRLA.reg & SERCOM_SPI_CTRLA_SWRST) { return; } uint32_t sercom_index = _sercom_get_sercom_inst_index(pSPI_SERCOM(obj)); uint32_t pm_index, gclk_index; #if (SAML21) if (sercom_index == 5) { pm_index = MCLK_APBDMASK_SERCOM5_Pos; gclk_index = SERCOM5_GCLK_ID_CORE; } else { pm_index = sercom_index + MCLK_APBCMASK_SERCOM0_Pos; gclk_index = sercom_index + SERCOM0_GCLK_ID_CORE; } #else pm_index = sercom_index + PM_APBCMASK_SERCOM0_Pos; gclk_index = sercom_index + SERCOM0_GCLK_ID_CORE; #endif /* Turn on module in PM */ #if (SAML21) if (sercom_index == 5) { system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBD, 1 << pm_index); } else { system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBC, 1 << pm_index); } #else system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBC, 1 << pm_index); #endif /* Set up the GCLK for the module */ struct system_gclk_chan_config gclk_chan_conf; system_gclk_chan_get_config_defaults(&gclk_chan_conf); gclk_chan_conf.source_generator = GCLK_GENERATOR_0; system_gclk_chan_set_config(gclk_index, &gclk_chan_conf); system_gclk_chan_enable(gclk_index); sercom_set_gclk_generator(GCLK_GENERATOR_0, false); #if DEVICE_SPI_ASYNCH /* Save the object */ _sercom_instances[sercom_index] = obj; /* Configure interrupt handler */ NVIC_SetVector((SERCOM0_IRQn + sercom_index), (uint32_t)_sercom_handlers[sercom_index]); #endif /* Set the SERCOM in SPI master mode */ _SPI(obj).CTRLA.reg |= SERCOM_SPI_CTRLA_MODE(0x3); pSPI_S(obj)->mode = SPI_MODE_MASTER; /* TODO: Do pin muxing here */ struct system_pinmux_config pin_conf; system_pinmux_get_config_defaults(&pin_conf); pin_conf.direction = SYSTEM_PINMUX_PIN_DIR_INPUT; uint32_t pad_pinmuxes[] = { EXT1_SPI_SERCOM_PINMUX_PAD0, EXT1_SPI_SERCOM_PINMUX_PAD1, EXT1_SPI_SERCOM_PINMUX_PAD2, EXT1_SPI_SERCOM_PINMUX_PAD3 }; /* Configure the SERCOM pins according to the user configuration */ for (uint8_t pad = 0; pad < 4; pad++) { uint32_t current_pinmux = pad_pinmuxes[pad]; if (current_pinmux != PINMUX_UNUSED) { pin_conf.mux_position = current_pinmux & 0xFFFF; system_pinmux_pin_set_config(current_pinmux >> 16, &pin_conf); } }