mac-rom-simm-programmer/hal/at90usb646/spi.c
Doug Brown 8b1cd63210 Change license to GPLv3
I can't use GPLv2 as soon as I need to start using the Nuvoton sample
code which is licensed with an Apache 2.0 license.
2023-09-10 05:02:44 -07:00

199 lines
5.1 KiB
C

/*
* spi.c
*
* Created on: Nov 14, 2020
* Author: Doug
*
* Copyright (C) 2011-2023 Doug Brown
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "../spi.h"
#include "gpio_hw.h"
#include <stddef.h>
#include <avr/io.h>
/// Keep a struct of available dividers, calculate something at runtime.
typedef struct SPIDividerInfo
{
/// The divider
uint8_t divider;
/// Bit 0 = SPR0, Bit 1 = SPR1, Bit 2 = SPI2X (in SPSR)
uint8_t configBits;
} SPIDividerInfo;
/// List of possible SPI dividers. Must be in ascending order.
static const SPIDividerInfo dividers[] = {
{2, 0b100},
{4, 0b000},
{8, 0b101},
{16, 0b001},
{32, 0b110},
{64, 0b010},
{128, 0b011}
};
/// The lone SPI controller available on this AVR
static SPIController controller =
{
.sckPin = {GPIOB, 1},
.mosiPin = {GPIOB, 2},
.misoPin = {GPIOB, 3}
};
/** Gets the SPI hardware controller at the specified index
*
* @param index The index of the controller. Only 0 is valid on this AVR.
* @return The SPI controller, or NULL if an invalid index is supplied
*/
SPIController *SPI_Controller(uint8_t index)
{
// The AVR only has one SPI controller
return (index == 0) ? &controller : NULL;
}
/** Initializes the supplied SPI controller
*
* @param c The controller
*/
void SPI_InitController(SPIController *c)
{
GPIO_SetDirection(c->sckPin, true);
GPIO_SetDirection(c->mosiPin, true);
GPIO_SetDirection(c->misoPin, false);
// Don't do anything with the rest of the registers.
// SPI_RequestController will handle it.
}
/** Initializes the supplied SPI device
*
* @param spi The device
* @param maxClock The maximum clock rate supported by the device in Hz
* @param mode The SPI mode (see the SPI_MODE_*, SPI_CPHA, and SPI_CPOL defines)
* @return True on success, false on failure
*/
bool SPI_InitDevice(SPIDevice *spi, uint32_t maxClock, uint8_t mode)
{
GPIO_SetDirection(spi->csPin, true);
SPI_Deassert(spi);
// Calculate which SPI clock divider to use
int8_t dividerIndex = -1;
for (uint8_t i = 0; dividerIndex < 0 && i < sizeof(dividers)/sizeof(dividers[0]); i++)
{
if (F_CPU / (uint32_t)dividers[i].divider <= maxClock)
{
dividerIndex = (int8_t)i;
}
}
// Fill in the SPI config registers according to the requested clock
if (dividerIndex >= 0)
{
uint8_t dividerBits = dividers[dividerIndex].configBits;
spi->private.spcr =
(0 << SPIE) | // No SPI interrupts
(1 << SPE) | // Enable SPI
(0 << DORD) | // MSB first
(1 << MSTR) | // Master mode
(((mode & SPI_CPOL) ? 1 : 0) << CPOL) |
(((mode & SPI_CPHA) ? 1 : 0) << CPHA) |
((dividerBits & 3) << SPR0);
// The only writable bit in SPSR is the SPI2X bit.
spi->private.spsr =
((dividerBits >> 2) << SPI2X);
return true;
}
else
{
// This SPI device requires a clock slower than what we can divide down to
return true;
}
}
/** Allows an SPI device to request control of the bus.
*
* @param spi The SPI device
*/
void SPI_RequestBus(SPIDevice *spi)
{
(void)spi;
// Set up the controller with the correct speed/mode config for this device
SPCR = spi->private.spcr;
SPSR = spi->private.spsr;
}
/** Allows an SPI device to relinquish control of the bus.
*
* @param spi The SPI device
*/
void SPI_ReleaseBus(SPIDevice *spi)
{
(void)spi;
}
/** Asserts an SPI device's chip select pin
*
* @param spi The SPI device
*/
void SPI_Assert(SPIDevice *spi)
{
GPIO_SetOff(spi->csPin);
// Due to the optimization we do in ParallelBus talking directly to the
// SPI hardware without going through this driver, we need to make sure
// that the SPIF flag is cleared here. Otherwise we may think we're done
// too early, which would cause us to screw up the next SPI transfer.
// This happens because the optimized code doesn't look at SPSR, so the
// SPIF flag never gets cleared from the previous SPI operation.
if (SPSR & (1 << SPIF))
{
// Reading the data register clears the flag if it's set
(void)SPDR;
}
}
/** Deasserts an SPI device's chip select pin
*
* @param spi The SPI device
*/
void SPI_Deassert(SPIDevice *spi)
{
GPIO_SetOn(spi->csPin);
}
/** Transfers a single byte to/from an SPI device
*
* @param spi The SPI device
* @param b The byte to send
* @return The byte that was simultaneously received
*/
uint8_t SPI_RWByte(SPIDevice *spi, uint8_t b)
{
// Since there's only one controller on this AVR, we don't actually care
// about the SPI device pointer here
(void)spi;
// Write the byte, wait for the write to complete, read back the result
SPDR = b;
while (!(SPSR & (1 << SPIF)));
return SPDR;
}