import firmware for the blue-pill relay driver board (NCV7240)

this usd to be in a private fork of i2c-star.git, but let's
keep all osmocom related stm32f0/f1 firmware projects here.
This commit is contained in:
Harald Welte 2021-04-11 16:38:59 +02:00
parent 3af30f0a6a
commit b4781312f9
3 changed files with 638 additions and 0 deletions

View File

@ -0,0 +1,55 @@
##
## This file is part of the libopencm3 project.
##
## Copyright (C) 2009 Uwe Hermann <uwe@hermann-uwe.de>
##
## This library is free software: you can redistribute it and/or modify
## it under the terms of the GNU Lesser General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## This library 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 Lesser General Public License for more details.
##
## You should have received a copy of the GNU Lesser General Public License
## along with this library. If not, see <http://www.gnu.org/licenses/>.
##
BINARY = ncv7240-stm32f1-usb
OBJS = console.o console_stm32.o
# when building for M0
#LDSCRIPT = ./stm32f051-openblt.ld
#LIBNAME = opencm3_stm32f0
#DEFS += -DSTM32F0
#FP_FLAGS ?= -msoft-float
#ARCH_FLAGS = -mthumb -mcpu=cortex-m3 $(FP_FLAGS)
DFU_ADDRESS = 0x08002000
LDSCRIPT = ./lctech-f103-dfu.ld
LIBNAME = opencm3_stm32f1
DEFS += -DSTM32F1 -DF_CPU=72000000
FP_FLAGS ?= -msoft-float
ARCH_FLAGS = -mthumb -mcpu=cortex-m3 $(FP_FLAGS) -mfix-cortex-m3-ldrd
################################################################################
# OpenOCD specific variables
OOCD ?= openocd
OOCD_INTERFACE ?= stlink-v2
OOCD_TARGET ?= stm32f1x
ARCH_FLAGS += --specs=/usr/lib/picolibc/arm-none-eabi/picolibc.specs
# selective libcommon parts
OBJS += watchdog.o fault.o iob_stm32_nonblocking.o kill.o #microvty.o
all: elf bin srec
include ../../mk/libcommon.mk
include ../../mk/librfn.mk
include ../../mk/librfm3.rules.mk
include ../../mk/libopencm3.mk

View File

@ -0,0 +1,31 @@
/*
* This file is part of the libopencm3 project.
*
* Copyright (C) 2009 Uwe Hermann <uwe@hermann-uwe.de>
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/* Linker script for LCTech STM32F103C8T6 development board. 48 pin package,
* 128K flash (8K taken for DFU bootloader), 20K RAM. */
/* Define memory regions. */
MEMORY
{
rom (rx) : ORIGIN = 0x08002000, LENGTH = 120K
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}
/* Include the common ld script. */
INCLUDE cortex-m-generic.ld

View File

@ -0,0 +1,552 @@
/*
* This file is part of the i2c-star project.
*
* Copyright (C) 2014 Daniel Thompson <daniel@redfelineninja.org.uk>
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libopencm3/usb/usbd.h>
#include <libopencm3/cm3/scb.h>
#include <libopencm3/cm3/nvic.h>
#include <libopencm3/cm3/systick.h>
#include <libopencm3/stm32/desig.h>
#include <libopencm3/stm32/i2c.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/spi.h>
#include <librfn/console.h>
#include <librfn/fibre.h>
#include <librfn/time.h>
#include <librfn/util.h>
#include <librfm3/i2c_ctx.h>
static const struct usb_device_descriptor dev = {
.bLength = USB_DT_DEVICE_SIZE,
.bDescriptorType = USB_DT_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = 0xff,
.bDeviceSubClass = 0,
.bDeviceProtocol = 0,
.bMaxPacketSize0 = 64,
.idVendor = 0x04b4,
.idProduct = 0xfb11,
.bcdDevice = 0x0205,
.iManufacturer = 1,
.iProduct = 2,
.iSerialNumber = 0,
.bNumConfigurations = 1,
};
const struct usb_endpoint_descriptor i2c_endpoint = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = 0x81,
.bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT,
.wMaxPacketSize = 4,
.bInterval = 0x20,
};
const struct usb_interface_descriptor i2c_iface = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = 0,
.bInterfaceSubClass = 0,
.bInterfaceProtocol = 0,
.iInterface = 0,
.endpoint = &i2c_endpoint,
};
const struct usb_interface ifaces[] = {{
.num_altsetting = 1,
.altsetting = &i2c_iface,
}};
static const struct usb_config_descriptor config = {
.bLength = USB_DT_CONFIGURATION_SIZE,
.bDescriptorType = USB_DT_CONFIGURATION,
.wTotalLength = 0, /* ?automatically calculated? */
.bNumInterfaces = 1,
.bConfigurationValue = 1,
.iConfiguration = 0,
.bmAttributes = 0x80, /* bus powered */
.bMaxPower = 0x32,
.interface = ifaces,
};
static const char *usb_strings[] = {
"redfelineninja.org.uk",
"i2c-stm32f1-usb",
};
/* Buffer to be used for control requests. */
uint8_t usbd_control_buffer[128];
/* commands from USB, must e.g. match command ids in kernel driver */
#define CMD_ECHO 0
#define CMD_GET_FUNC 1
#define CMD_SET_DELAY 2
#define CMD_GET_STATUS 3
#define CMD_I2C_IO 4
#define CMD_I2C_BEGIN 1 // flag fo I2C_IO
#define CMD_I2C_END 2 // flag fo I2C_IO
/* linux kernel flags */
#define I2C_M_TEN 0x10 /* we have a ten bit chip address */
#define I2C_M_RD 0x01
#define I2C_M_NOSTART 0x4000
#define I2C_M_REV_DIR_ADDR 0x2000
#define I2C_M_IGNORE_NAK 0x1000
#define I2C_M_NO_RD_ACK 0x0800
/* To determine what functionality is present */
#define I2C_FUNC_I2C 0x00000001
#define I2C_FUNC_10BIT_ADDR 0x00000002
#define I2C_FUNC_PROTOCOL_MANGLING 0x00000004 /* I2C_M_{REV_DIR_ADDR,NOSTART,..} */
#define I2C_FUNC_SMBUS_HWPEC_CALC 0x00000008 /* SMBus 2.0 */
#define I2C_FUNC_NOSTART 0x00000010 /* I2C_M_NOSTART */
#define I2C_FUNC_SMBUS_READ_WORD_DATA_PEC 0x00000800 /* SMBus 2.0 */
#define I2C_FUNC_SMBUS_WRITE_WORD_DATA_PEC 0x00001000 /* SMBus 2.0 */
#define I2C_FUNC_SMBUS_PROC_CALL_PEC 0x00002000 /* SMBus 2.0 */
#define I2C_FUNC_SMBUS_BLOCK_PROC_CALL_PEC 0x00004000 /* SMBus 2.0 */
#define I2C_FUNC_SMBUS_BLOCK_PROC_CALL 0x00008000 /* SMBus 2.0 */
#define I2C_FUNC_SMBUS_QUICK 0x00010000
#define I2C_FUNC_SMBUS_READ_BYTE 0x00020000
#define I2C_FUNC_SMBUS_WRITE_BYTE 0x00040000
#define I2C_FUNC_SMBUS_READ_BYTE_DATA 0x00080000
#define I2C_FUNC_SMBUS_WRITE_BYTE_DATA 0x00100000
#define I2C_FUNC_SMBUS_READ_WORD_DATA 0x00200000
#define I2C_FUNC_SMBUS_WRITE_WORD_DATA 0x00400000
#define I2C_FUNC_SMBUS_PROC_CALL 0x00800000
#define I2C_FUNC_SMBUS_READ_BLOCK_DATA 0x01000000
#define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA 0x02000000
#define I2C_FUNC_SMBUS_READ_I2C_BLOCK 0x04000000 /* I2C-like block xfer */
#define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK 0x08000000 /* w/ 1-byte reg. addr. */
#define I2C_FUNC_SMBUS_READ_I2C_BLOCK_2 0x10000000 /* I2C-like block xfer */
#define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK_2 0x20000000 /* w/ 2-byte reg. addr. */
#define I2C_FUNC_SMBUS_READ_BLOCK_DATA_PEC 0x40000000 /* SMBus 2.0 */
#define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA_PEC 0x80000000 /* SMBus 2.0 */
#define I2C_FUNC_SMBUS_BYTE I2C_FUNC_SMBUS_READ_BYTE | \
I2C_FUNC_SMBUS_WRITE_BYTE
#define I2C_FUNC_SMBUS_BYTE_DATA I2C_FUNC_SMBUS_READ_BYTE_DATA | \
I2C_FUNC_SMBUS_WRITE_BYTE_DATA
#define I2C_FUNC_SMBUS_WORD_DATA I2C_FUNC_SMBUS_READ_WORD_DATA | \
I2C_FUNC_SMBUS_WRITE_WORD_DATA
#define I2C_FUNC_SMBUS_BLOCK_DATA I2C_FUNC_SMBUS_READ_BLOCK_DATA | \
I2C_FUNC_SMBUS_WRITE_BLOCK_DATA
#define I2C_FUNC_SMBUS_I2C_BLOCK I2C_FUNC_SMBUS_READ_I2C_BLOCK | \
I2C_FUNC_SMBUS_WRITE_I2C_BLOCK
#define I2C_FUNC_SMBUS_EMUL I2C_FUNC_SMBUS_QUICK | \
I2C_FUNC_SMBUS_BYTE | \
I2C_FUNC_SMBUS_BYTE_DATA | \
I2C_FUNC_SMBUS_WORD_DATA | \
I2C_FUNC_SMBUS_PROC_CALL | \
I2C_FUNC_SMBUS_WRITE_BLOCK_DATA | \
I2C_FUNC_SMBUS_WRITE_BLOCK_DATA_PEC | \
I2C_FUNC_SMBUS_I2C_BLOCK
/* the currently support capability is quite limited */
const unsigned long func = I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_NOSTART;
#define STATUS_IDLE 0
#define STATUS_ADDRESS_ACK 1
#define STATUS_ADDRESS_NACK 2
static uint8_t status = STATUS_IDLE;
uint32_t i2c = I2C1;
/*!
* \brief Handle I2C I/O request.
*
* \todo There is no bus error checking at all...
*/
static int usb_i2c_io(struct usb_setup_data *req, uint8_t *buf, uint16_t *len)
{
uint32_t reg32 __attribute__((unused));
/* Interpret the request */
uint8_t cmd = req->bRequest;
uint8_t address = req->wIndex;
uint8_t is_read = req->wValue & I2C_M_RD;
uint8_t size = req->wLength;
i2c_ctx_t ctx;
i2c_ctx_init(&ctx, I2C1);
if (!(req->wValue & I2C_M_NOSTART)) {
/* We can ignore CMD_I2C_BEGIN, the hardware will work out which
* type of start condition to generate.
*/
PT_CALL(&ctx.leaf, i2c_ctx_start(&ctx));
if (ctx.err)
goto err;
/* Send the address */
PT_CALL(&ctx.leaf,
i2c_ctx_sendaddr(&ctx, address, (is_read ? size : 0)));
if (ctx.err)
goto err;
}
/* Perform the transaction */
for (int i=0; i<size; i++) {
PT_CALL(&ctx.leaf, is_read ? i2c_ctx_getdata(&ctx, buf + i)
: i2c_ctx_senddata(&ctx, buf[i]));
if (ctx.err)
goto err;
}
/* Stop the transaction if requested and this is a write transaction
* (reads are stopped automatically)
*/
if (cmd & CMD_I2C_END && !is_read) {
PT_CALL(&ctx.leaf, i2c_ctx_stop(&ctx));
if (ctx.err)
goto err;
}
status = STATUS_ADDRESS_ACK;
*len = (is_read ? size : 0);
return USBD_REQ_HANDLED;
err:
i2c_ctx_reset(&ctx);
status = STATUS_ADDRESS_NACK;
*len = 0;
return USBD_REQ_HANDLED;
}
static enum usbd_request_return_codes usb_control_request(
usbd_device *usbd_dev, struct usb_setup_data *req, uint8_t **buf,
uint16_t *len,
void (**complete)(usbd_device *usbd_dev, struct usb_setup_data *req))
{
static uint8_t reply_buf[64];
(void)usbd_dev;
(void)complete;
switch (req->bRequest) {
case CMD_ECHO:
memcpy(reply_buf, &req->wValue, sizeof(req->wValue));
*buf = reply_buf;
*len = sizeof(req->wValue);
return USBD_REQ_HANDLED;
case CMD_GET_FUNC:
/* Report our capabilities */
memcpy(reply_buf, &func, sizeof(func));
*buf = reply_buf;
*len = sizeof(func);
return USBD_REQ_HANDLED;
case CMD_SET_DELAY:
/* This was used in i2c-tiny-usb to choose the clock
* frequency by specifying the shortest time between
* clock edges.
*
* This implementation silently ignores delay requests. We
* run the hardware as fast as we are permitted.
*/
*buf = reply_buf;
*len = 0;
return USBD_REQ_HANDLED;
case CMD_I2C_IO:
case CMD_I2C_IO | CMD_I2C_BEGIN:
case CMD_I2C_IO | CMD_I2C_END:
case CMD_I2C_IO | CMD_I2C_BEGIN | CMD_I2C_END:
if (req->wValue & I2C_M_RD)
*buf = reply_buf;
return usb_i2c_io(req, *buf, len);
break;
case CMD_GET_STATUS:
memcpy(reply_buf, &status, sizeof(status));
*buf = reply_buf;
*len = sizeof(status);
return USBD_REQ_HANDLED;
default:
break;
}
return USBD_REQ_NEXT_CALLBACK;
}
static void usb_set_config(usbd_device *usbd_dev, uint16_t wValue)
{
(void)wValue;
usbd_ep_setup(usbd_dev, 0x81, USB_ENDPOINT_ATTR_INTERRUPT, 4, NULL);
usbd_register_control_callback(
usbd_dev,
USB_REQ_TYPE_VENDOR | USB_REQ_TYPE_INTERFACE,
USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT,
usb_control_request);
}
static usbd_device *usbd_dev;
static int usb_fibre(fibre_t *fibre)
{
static uint32_t t;
PT_BEGIN_FIBRE(fibre);
rcc_periph_clock_enable(RCC_GPIOA);
/* lower hotplug and leave enough time for the host to notice */
gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL,
GPIO11 | GPIO12);
gpio_clear(GPIOA, GPIO11 | GPIO12);
t = time_now() + 10000;
PT_WAIT_UNTIL(fibre_timeout(t));
usbd_dev = usbd_init(&st_usbfs_v1_usb_driver, &dev, &config,
usb_strings, 2,
usbd_control_buffer, sizeof(usbd_control_buffer));
usbd_register_set_config_callback(usbd_dev, usb_set_config);
while (true) {
usbd_poll(usbd_dev);
PT_YIELD();
}
PT_END();
}
static fibre_t usb_task = FIBRE_VAR_INIT(usb_fibre);
static console_t uart_console;
static void i2c_init(void)
{
/* clocks */
rcc_periph_clock_enable(RCC_GPIOB);
rcc_periph_clock_enable(RCC_I2C1);
rcc_periph_clock_enable(RCC_AFIO);
/* initialize the peripheral */
i2c_ctx_t ctx;
i2c_ctx_init(&ctx, I2C1);
i2c_ctx_reset(&ctx);
/* GPIO for I2C1 */
gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ,
GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO6 | GPIO7);
/* low-level pin for pulling !RESET of USB hub low */
gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO8);
gpio_clear(GPIOB, GPIO8);
}
static void jump_to_bootloader(void)
{
char *const marker = (char *)0x20004800; /* RAM@18K */
const char key[] = "remain-in-loader";
memcpy(marker, key, sizeof(key));
scb_reset_system(); /* Will never return. */
}
static pt_state_t do_id(console_t *c)
{
char serial_no[25];
desig_get_unique_id_as_string(serial_no, sizeof(serial_no));
fprintf(c->out, "%s\n", serial_no);
return PT_EXITED;
}
static pt_state_t do_reboot(console_t *c)
{
(void)c;
jump_to_bootloader();
return PT_EXITED;
}
static uint16_t ncn7240_state = 0xffff;
static void ncn7240_set_output(uint8_t chan, bool enable)
{
uint8_t nbits = (chan-1)*2;
ncn7240_state &= ~(3 << nbits);
if (enable)
ncn7240_state |= 2 << nbits;
else
ncn7240_state |= 3 << nbits;
spi_enable(SPI1);
int i;
for (i = 0; i < 0x8000; i++)
__asm__("nop");
spi_write(SPI1, ncn7240_state);
for (i = 0; i < 0x8000; i++)
__asm__("nop");
spi_disable(SPI1);
}
static bool ncn7240_get_output(uint8_t chan)
{
uint8_t nbits = (chan-1)*2;
uint16_t st = (ncn7240_state >> nbits) & 3;
if (st == 2)
return true;
else
return false;
}
static pt_state_t do_on(console_t *c)
{
int chan;
if (c->argc < 2) {
fprintf(c->out, "You have to specify the channel (0..7)\r\n");
return PT_EXITED;
}
chan = atoi(c->argv[1]);
if (chan < 1 || chan > 8) {
fprintf(c->out, "Chanel must be (1..8)\r\n");
return PT_EXITED;
}
ncn7240_set_output((uint8_t)chan, true);
fprintf(c->out, "Enabled %d\r\n", chan);
return PT_EXITED;
}
static pt_state_t do_off(console_t *c)
{
int chan;
if (c->argc < 2) {
fprintf(c->out, "You have to specify the channel (0..7)\r\n");
return PT_EXITED;
}
chan = atoi(c->argv[1]);
if (chan < 1 || chan > 8) {
fprintf(c->out, "Chanel must be (1..8)\r\n");
return PT_EXITED;
}
ncn7240_set_output((uint8_t)chan, false);
fprintf(c->out, "Disabled %d\r\n", chan);
return PT_EXITED;
}
static pt_state_t do_get(console_t *c)
{
bool st;
int chan;
if (c->argc >= 2) {
chan = atoi(c->argv[1]);
if (chan < 1 || chan > 8) {
fprintf(c->out, "Chanel must be (1..8)\r\n");
return PT_EXITED;
}
st = ncn7240_get_output(chan);
fprintf(c->out, "%u: %s\r\n", chan, st ? "ON" : "OFF");
} else {
for (chan = 1; chan <= 8; chan++) {
st = ncn7240_get_output(chan);
fprintf(c->out, "%u: %s\r\n", chan, st ? "ON" : "OFF");
}
}
return PT_EXITED;
}
static const console_cmd_t cmds[] = {
CONSOLE_CMD_VAR_INIT("id", do_id),
CONSOLE_CMD_VAR_INIT("reboot", do_reboot),
CONSOLE_CMD_VAR_INIT("on", do_on),
CONSOLE_CMD_VAR_INIT("off", do_off),
CONSOLE_CMD_VAR_INIT("get", do_get),
};
static void spi_init(void)
{
rcc_periph_clock_enable(RCC_SPI1);
/* configure !SS, SCK, MISO and MOSI as "SPI alternate function */
gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL,
GPIO4 | GPIO5 | GPIO6 | GPIO7);
//gpio_set_af(GPIOA, GPIO_AF1, GPIO4 | GPIO5 | GPIO6 | GPIO7);
/* configure NCV_EN as output, high */
gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO0);
gpio_set(GPIOB, GPIO0);
spi_reset(SPI1);
spi_init_master(SPI1, SPI_CR1_BAUDRATE_FPCLK_DIV_64, SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE,
SPI_CR1_CPHA_CLK_TRANSITION_2, SPI_CR1_DFF_16BIT, SPI_CR1_MSBFIRST);
spi_enable_ss_output(SPI1);
}
int main(void)
{
unsigned int i;
rcc_clock_setup_in_hse_8mhz_out_72mhz();
rcc_periph_clock_enable(RCC_GPIOA);
rcc_periph_clock_enable(RCC_GPIOB);
console_init(&uart_console, stdout);
for (i=0; i<lengthof(cmds); i++)
console_register(&cmds[i]);
i2c_init();
time_init();
spi_init();
printf("Booted OK\n");
for (i = 0; i < 0x800000; i++)
__asm__("nop");
/* prepare the scheduler */
fibre_run(&usb_task);
ncn7240_set_output(1, true);
fibre_scheduler_main_loop();
return 0;
}