mirror of
https://github.com/dougg3/mac-rom-simm-programmer.git
synced 2024-11-28 00:50:17 +00:00
8b1cd63210
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.
199 lines
5.1 KiB
C
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;
|
|
}
|