firmware: Import the main firmware
This is the state that was used for the battery testing, so import as-is to keep a record of changes. Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
This commit is contained in:
parent
c2260dc7dd
commit
bbd3486f8c
|
@ -0,0 +1,71 @@
|
||||||
|
BOARD ?= xmas-snoopy
|
||||||
|
CROSS ?= riscv-none-embed-
|
||||||
|
CC = $(CROSS)gcc
|
||||||
|
OBJCOPY = $(CROSS)objcopy
|
||||||
|
ICEPROG = iceprog
|
||||||
|
DFU_UTIL = dfu-util
|
||||||
|
|
||||||
|
BOARD_DEFINE=BOARD_$(shell echo $(BOARD) | tr a-z\- A-Z_)
|
||||||
|
CFLAGS=-Wall -Wextra -Wno-unused-parameter -Os -march=rv32im -mno-div -mabi=ilp32 -ffreestanding -flto -nostartfiles -fomit-frame-pointer -Wl,--gc-section --specs=nano.specs -D$(BOARD_DEFINE) -I. -I../common
|
||||||
|
|
||||||
|
NO2USB_FW_VERSION=0
|
||||||
|
include ../../gateware/cores/no2usb/fw/fw.mk
|
||||||
|
CFLAGS += $(INC_no2usb)
|
||||||
|
|
||||||
|
LNK=lnk-app.lds
|
||||||
|
|
||||||
|
HEADERS=\
|
||||||
|
config.h \
|
||||||
|
console.h \
|
||||||
|
i2c.h \
|
||||||
|
led_ctrl.h \
|
||||||
|
pmu.h \
|
||||||
|
spi.h \
|
||||||
|
usb_dev.h \
|
||||||
|
usb_desc_ids.h \
|
||||||
|
usb_str_app.gen.h \
|
||||||
|
utils.h \
|
||||||
|
$(NULL)
|
||||||
|
|
||||||
|
SOURCES=\
|
||||||
|
start.S \
|
||||||
|
console_dummy.c \
|
||||||
|
i2c.c \
|
||||||
|
led_ctrl.c \
|
||||||
|
main.c \
|
||||||
|
pmu.c \
|
||||||
|
spi.c \
|
||||||
|
usb_dev.c \
|
||||||
|
usb_desc_app.c \
|
||||||
|
utils.c \
|
||||||
|
$(NULL)
|
||||||
|
|
||||||
|
HEADERS += $(HEADERS_no2usb)
|
||||||
|
SOURCES += $(SOURCES_no2usb)
|
||||||
|
|
||||||
|
|
||||||
|
all: xmas-snoopy.bin
|
||||||
|
|
||||||
|
|
||||||
|
xmas-snoopy.elf: $(LNK) $(HEADERS) $(SOURCES)
|
||||||
|
$(CC) $(CFLAGS) -Wl,-Bstatic,-T,$(LNK),--strip-debug -o $@ $(SOURCES)
|
||||||
|
|
||||||
|
|
||||||
|
%.bin: %.elf
|
||||||
|
$(OBJCOPY) -O binary $< $@
|
||||||
|
|
||||||
|
prog: xmas-snoopy.bin
|
||||||
|
$(ICEPROG) -o 640k $<
|
||||||
|
|
||||||
|
dfuprog: xmas-snoopy.bin
|
||||||
|
ifeq ($(DFU_SERIAL),)
|
||||||
|
$(DFU_UTIL) -R -d 1d50:6146 -a 1 -D $<
|
||||||
|
else
|
||||||
|
$(DFU_UTIL) -R -S $(DFU_SERIAL) -a 1 -D $<
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f *.bin *.hex *.elf *.o *.gen.h
|
||||||
|
|
||||||
|
.PHONY: prog dfuprog clean
|
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
* config.h
|
||||||
|
*
|
||||||
|
* Copyright (C) 2023 Sylvain Munaut <tnt@246tNt.com>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define PMU_BASE 0x80000000
|
||||||
|
#define SPI_BASE 0x81000000
|
||||||
|
#define I2C_BASE 0x82000000
|
||||||
|
#define USB_DATA_BASE 0x83000000
|
||||||
|
#define USB_CORE_BASE 0x84000000
|
||||||
|
#define LED_CTRL_BASE 0x85000000
|
||||||
|
|
||||||
|
#define SYS_CLK_FREQ 24000000
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* console.h
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2023 Sylvain Munaut
|
||||||
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
void console_init(void);
|
||||||
|
|
||||||
|
char getchar(void);
|
||||||
|
int getchar_nowait(void);
|
||||||
|
void putchar(char c);
|
||||||
|
void puts(const char *p);
|
||||||
|
int printf(const char *fmt, ...);
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* console_dummy.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2023 Sylvain Munaut
|
||||||
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
|
||||||
|
void console_init(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
char getchar(void)
|
||||||
|
{
|
||||||
|
while (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getchar_nowait(void)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void putchar(char c)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void puts(const char *p)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int printf(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
/*
|
||||||
|
* i2c.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2021-2023 Sylvain Munaut <tnt@246tNt.com>
|
||||||
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "console.h"
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
|
||||||
|
struct i2c {
|
||||||
|
uint32_t csr;
|
||||||
|
} __attribute__((packed,aligned(4)));
|
||||||
|
|
||||||
|
#define I2C_CMD_START (0 << 12)
|
||||||
|
#define I2C_CMD_STOP (1 << 12)
|
||||||
|
#define I2C_CMD_WRITE (2 << 12)
|
||||||
|
#define I2C_CMD_READ (3 << 12)
|
||||||
|
|
||||||
|
#define I2C_GET_RESP (1 << 15)
|
||||||
|
#define I2C_ACK (0 << 8) /* ack bit value = 0 means ACK */
|
||||||
|
#define I2C_NAK (1 << 8) /* ack bit value = 1 means NAK */
|
||||||
|
|
||||||
|
#define I2C_VALID (1 << 31)
|
||||||
|
|
||||||
|
|
||||||
|
static volatile struct i2c * const i2c_regs = (void*)(I2C_BASE);
|
||||||
|
|
||||||
|
|
||||||
|
static inline uint32_t
|
||||||
|
_i2c_wait(void)
|
||||||
|
{
|
||||||
|
uint32_t v;
|
||||||
|
|
||||||
|
do {
|
||||||
|
v = i2c_regs->csr;
|
||||||
|
} while (!(v & I2C_VALID));
|
||||||
|
|
||||||
|
return v & 0x1ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
i2c_ready(void)
|
||||||
|
{
|
||||||
|
return i2c_regs->csr & I2C_VALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
i2c_start(void)
|
||||||
|
{
|
||||||
|
i2c_regs->csr = I2C_CMD_START;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
i2c_stop(void)
|
||||||
|
{
|
||||||
|
i2c_regs->csr = I2C_CMD_STOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
i2c_write(uint8_t data)
|
||||||
|
{
|
||||||
|
i2c_regs->csr = I2C_CMD_WRITE | data;
|
||||||
|
return (_i2c_wait() & (I2C_ACK | I2C_NAK)) == I2C_ACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t
|
||||||
|
i2c_read(bool ack)
|
||||||
|
{
|
||||||
|
i2c_regs->csr = I2C_CMD_READ | I2C_GET_RESP | (ack ? I2C_ACK : I2C_NAK);
|
||||||
|
return _i2c_wait() & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
i2c_xfer(uint8_t dev, const uint8_t *wr, int wr_size, uint8_t *rd, int rd_size)
|
||||||
|
{
|
||||||
|
bool rv = true;
|
||||||
|
|
||||||
|
if (wr_size) {
|
||||||
|
i2c_start();
|
||||||
|
rv = rv && i2c_write(dev);
|
||||||
|
while (rv && wr_size--)
|
||||||
|
rv = rv && i2c_write(*wr++);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rv)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
if (rd_size) {
|
||||||
|
i2c_start();
|
||||||
|
rv = rv && i2c_write(dev|1);
|
||||||
|
while (rv && rd_size--)
|
||||||
|
*rd++ = i2c_read(rd_size != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
err:
|
||||||
|
i2c_stop();
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
i2c_write_reg(uint8_t dev, uint8_t reg, uint8_t val)
|
||||||
|
{
|
||||||
|
bool rv = true;
|
||||||
|
i2c_start();
|
||||||
|
rv = rv && i2c_write(dev);
|
||||||
|
rv = rv && i2c_write(reg);
|
||||||
|
rv = rv && i2c_write(val);
|
||||||
|
i2c_stop();
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
i2c_read_reg(uint8_t dev, uint8_t reg, uint8_t *val)
|
||||||
|
{
|
||||||
|
bool rv = true;
|
||||||
|
i2c_start();
|
||||||
|
rv = rv && i2c_write(dev);
|
||||||
|
rv = rv && i2c_write(reg);
|
||||||
|
if (rv)
|
||||||
|
i2c_start();
|
||||||
|
rv = rv && i2c_write(dev|1);
|
||||||
|
*val = rv ? i2c_read(false) : 0x00; // NAK
|
||||||
|
i2c_stop();
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
i2c_probe(uint8_t dev)
|
||||||
|
{
|
||||||
|
bool rv;
|
||||||
|
i2c_start();
|
||||||
|
rv = i2c_write(dev);
|
||||||
|
i2c_stop();
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
i2c_scan(void)
|
||||||
|
{
|
||||||
|
for (uint8_t addr=0; addr<128; addr++) {
|
||||||
|
if (i2c_probe(addr << 1))
|
||||||
|
printf("I2C @ %08x\n", addr << 1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* i2c.h
|
||||||
|
*
|
||||||
|
* Copyright (C) 2021-2023 Sylvain Munaut <tnt@246tNt.com>
|
||||||
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void i2c_start(void);
|
||||||
|
void i2c_stop(void);
|
||||||
|
bool i2c_write(uint8_t data);
|
||||||
|
uint8_t i2c_read(bool ack);
|
||||||
|
|
||||||
|
bool i2c_xfer(uint8_t dev, const uint8_t *wr, int wr_size, uint8_t *rd, int rd_size);
|
||||||
|
|
||||||
|
bool i2c_write_reg(uint8_t dev, uint8_t reg, uint8_t val);
|
||||||
|
bool i2c_read_reg (uint8_t dev, uint8_t reg, uint8_t *val);
|
||||||
|
|
||||||
|
bool i2c_probe(uint8_t dev);
|
||||||
|
void i2c_scan(void);
|
|
@ -0,0 +1,402 @@
|
||||||
|
/*
|
||||||
|
* led_ctrl.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2023 Sylvain Munaut <tnt@246tNt.com>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <sys/param.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "pmu.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 31 24 23 16 15 8 7 0
|
||||||
|
* --------------------------------------------------------------------------------
|
||||||
|
* | stop[0] | start[0] | / | anode_sel |
|
||||||
|
* --------------------------------------------------------------------------------
|
||||||
|
* | stop[2] | start[2] | stop[1] | start[1] |
|
||||||
|
* --------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define LED_N_FRAMES 16
|
||||||
|
#define LED_N_SUBFRAMES 16
|
||||||
|
|
||||||
|
struct led_subframe {
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
uint32_t w[2];
|
||||||
|
};
|
||||||
|
struct {
|
||||||
|
uint8_t anode;
|
||||||
|
uint8_t _rsvd;
|
||||||
|
struct {
|
||||||
|
uint8_t start;
|
||||||
|
uint8_t stop;
|
||||||
|
} cathode[3];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} __attribute__((packed,aligned(4)));
|
||||||
|
|
||||||
|
|
||||||
|
struct wb_led_ctrl {
|
||||||
|
uint32_t csr;
|
||||||
|
uint32_t _rsvd[511];
|
||||||
|
struct {
|
||||||
|
struct led_subframe subframe[LED_N_SUBFRAMES];
|
||||||
|
} frame[LED_N_FRAMES];
|
||||||
|
} __attribute__((packed,aligned(4)));
|
||||||
|
|
||||||
|
#define LED_CSR_TRIG_CLR (1 << 31)
|
||||||
|
#define LED_CSR_TRIG_ENA (1 << 30)
|
||||||
|
#define LED_CSR_TRIG_FRAME(n) (((n) & 0xf) << 24)
|
||||||
|
#define LED_CSR_GET_FRAME(v) (((v) >> 16) & (LED_N_FRAMES-1))
|
||||||
|
#define LED_CSR_DRV_CURREN (1 << 2)
|
||||||
|
#define LED_CSR_DRV_RGBLEDEN (1 << 1)
|
||||||
|
#define LED_CSR_DRV_SCAN_ENA (1 << 0)
|
||||||
|
|
||||||
|
|
||||||
|
static volatile struct wb_led_ctrl * const led_regs = (void*)(LED_CTRL_BASE);
|
||||||
|
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
uint8_t frame_nxt;
|
||||||
|
uint32_t time;
|
||||||
|
uint16_t leds[14*3];
|
||||||
|
struct led_subframe subframe[LED_N_SUBFRAMES+1];
|
||||||
|
} g_led;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static uint32_t rng = 1234;
|
||||||
|
static uint16_t rand16()
|
||||||
|
{
|
||||||
|
rng = (rng * 22695477) + 1;
|
||||||
|
return rng >> 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const uint16_t cie[] = {
|
||||||
|
1156, 3468, 5703, 7651, 9253, 10628, 11843, 12938,
|
||||||
|
13938, 14863, 15724, 16533, 17295, 18018, 18705, 19362,
|
||||||
|
19991, 20595, 21176, 21737, 22279, 22804, 23312, 23806,
|
||||||
|
24285, 24752, 25207, 25651, 26084, 26507, 26920, 27325,
|
||||||
|
27721, 28109, 28489, 28862, 29228, 29587, 29941, 30288,
|
||||||
|
30629, 30964, 31295, 31620, 31940, 32255, 32566, 32873,
|
||||||
|
33175, 33473, 33767, 34058, 34344, 34627, 34906, 35182,
|
||||||
|
35455, 35724, 35990, 36254, 36514, 36772, 37027, 37279,
|
||||||
|
37528, 37775, 38019, 38261, 38501, 38738, 38973, 39206,
|
||||||
|
39437, 39665, 39891, 40116, 40338, 40559, 40777, 40994,
|
||||||
|
41209, 41422, 41634, 41843, 42051, 42258, 42463, 42666,
|
||||||
|
42868, 43068, 43267, 43464, 43660, 43854, 44047, 44239,
|
||||||
|
44429, 44618, 44806, 44993, 45178, 45362, 45545, 45726,
|
||||||
|
45907, 46086, 46264, 46441, 46617, 46792, 46966, 47139,
|
||||||
|
47310, 47481, 47651, 47820, 47988, 48155, 48320, 48485,
|
||||||
|
48649, 48812, 48975, 49136, 49297, 49456, 49615, 49773,
|
||||||
|
49930, 50087, 50242, 50397, 50551, 50704, 50856, 51008,
|
||||||
|
51159, 51309, 51458, 51607, 51755, 51902, 52049, 52195,
|
||||||
|
52340, 52485, 52629, 52772, 52915, 53057, 53198, 53339,
|
||||||
|
53479, 53618, 53757, 53896, 54033, 54170, 54307, 54443,
|
||||||
|
54578, 54713, 54848, 54981, 55114, 55247, 55379, 55511,
|
||||||
|
55642, 55773, 55903, 56032, 56161, 56290, 56418, 56545,
|
||||||
|
56672, 56799, 56925, 57051, 57176, 57301, 57425, 57549,
|
||||||
|
57672, 57795, 57918, 58040, 58161, 58282, 58403, 58524,
|
||||||
|
58643, 58763, 58882, 59001, 59119, 59237, 59354, 59471,
|
||||||
|
59588, 59704, 59820, 59936, 60051, 60166, 60280, 60394,
|
||||||
|
60508, 60621, 60734, 60847, 60959, 61071, 61182, 61293,
|
||||||
|
61404, 61515, 61625, 61735, 61844, 61953, 62062, 62171,
|
||||||
|
62279, 62387, 62495, 62602, 62709, 62816, 62922, 63028,
|
||||||
|
63134, 63239, 63344, 63449, 63553, 63658, 63762, 63865,
|
||||||
|
63968, 64071, 64174, 64277, 64379, 64481, 64582, 64684,
|
||||||
|
64785, 64886, 64986, 65087, 65187, 65287, 65386, 65485,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int k_scale[] = {
|
||||||
|
189, /* Emerald */
|
||||||
|
256, /* Pink */
|
||||||
|
236, /* Blue */
|
||||||
|
};
|
||||||
|
|
||||||
|
#define K0 1 /* Pink */
|
||||||
|
#define K1 0 /* Emerald */
|
||||||
|
#define K2 2 /* Blue */
|
||||||
|
|
||||||
|
static uint16_t
|
||||||
|
cie_lum2pwm(uint16_t lum)
|
||||||
|
{
|
||||||
|
int min = 0;
|
||||||
|
int max = 256;
|
||||||
|
|
||||||
|
while (max > min) {
|
||||||
|
int idx = (max + min) >> 1;
|
||||||
|
if (cie[idx] > lum)
|
||||||
|
max = idx;
|
||||||
|
else if (cie[idx] < lum)
|
||||||
|
min = idx + 1;
|
||||||
|
else
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
_led_render(uint32_t frame, uint16_t *leds)
|
||||||
|
{
|
||||||
|
|
||||||
|
#if 1
|
||||||
|
/* Decrease luminosoty so they all fade out in 15 second */
|
||||||
|
for (int i=0; i<42; i++)
|
||||||
|
{
|
||||||
|
if (leds[i] > 40000)
|
||||||
|
leds[i] -= 150 + ((leds[i] - 40000) >> 4);
|
||||||
|
if (leds[i] > 500)
|
||||||
|
leds[i] -= 150;
|
||||||
|
else
|
||||||
|
leds[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Every second, pick a led and make it bright */
|
||||||
|
if ((frame & 15) == 0) {
|
||||||
|
int i;
|
||||||
|
while (leds[i = (rand16() % 42)] > 10000);
|
||||||
|
leds[i] = 65535;
|
||||||
|
}
|
||||||
|
else if ((frame & 7) == 0) {
|
||||||
|
int i;
|
||||||
|
i = (rand16() % 42);
|
||||||
|
leds[i] = 35000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
for (int i=0; i<42; i+=3)
|
||||||
|
{
|
||||||
|
leds[i+0] = 35000; /* Emerald */
|
||||||
|
leds[i+1] = 35000; /* Pink */
|
||||||
|
leds[i+2] = 35000; /* Blue */
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
_led_map_one(int sf, int anode, uint16_t *pwm)
|
||||||
|
{
|
||||||
|
/* Select anode */
|
||||||
|
g_led.subframe[sf].anode = anode;
|
||||||
|
|
||||||
|
/* Check various cases */
|
||||||
|
if (pwm[0] + pwm[1] + pwm[2] <= 128)
|
||||||
|
{
|
||||||
|
/* Full serial case */
|
||||||
|
int s = (128 - (pwm[0] + pwm[1] + pwm[2])) >> 1;
|
||||||
|
int o = 0;
|
||||||
|
|
||||||
|
for (int k=0; k<3; k++) {
|
||||||
|
if (pwm[k] > 0) {
|
||||||
|
g_led.subframe[sf].cathode[k].start = o;
|
||||||
|
g_led.subframe[sf].cathode[k].stop = o + pwm[k] - 1;
|
||||||
|
o += pwm[k] + s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((pwm[K0] + MAX(pwm[K1], pwm[K2])) <= 128)
|
||||||
|
{
|
||||||
|
int o = 0;
|
||||||
|
|
||||||
|
/* K0 split, K1 & K2 overlap */
|
||||||
|
/* K0 starting at 0 */
|
||||||
|
if (pwm[K0] > 0) {
|
||||||
|
g_led.subframe[sf].cathode[K0].start = o;
|
||||||
|
g_led.subframe[sf].cathode[K0].stop = o + pwm[K0] - 1;
|
||||||
|
o += pwm[K0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* K1 starts right after K0 */
|
||||||
|
if (pwm[K1] > 0) {
|
||||||
|
g_led.subframe[sf].cathode[K1].start = o;
|
||||||
|
g_led.subframe[sf].cathode[K1].stop = o + pwm[K1] - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* K2 aligned to the end */
|
||||||
|
if (pwm[K2] > 0) {
|
||||||
|
g_led.subframe[sf].cathode[K2].start = 0x7f - pwm[K2] + 1;
|
||||||
|
g_led.subframe[sf].cathode[K2].stop = 0x7f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Default, K0 on one side, then K1 & K2 full overlap on the other */
|
||||||
|
/* K0 aligned at the end */
|
||||||
|
if (pwm[K0] > 0) {
|
||||||
|
g_led.subframe[sf].cathode[K0].start = 0x7f - pwm[K0] + 1;
|
||||||
|
g_led.subframe[sf].cathode[K0].stop = 0x7f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* K1 & K2 overlapping at beginning */
|
||||||
|
if (pwm[K1] > 0) {
|
||||||
|
g_led.subframe[sf].cathode[K1].start = 0;
|
||||||
|
g_led.subframe[sf].cathode[K1].stop = pwm[K1] - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pwm[K2] > 0) {
|
||||||
|
g_led.subframe[sf].cathode[K2].start = 0;
|
||||||
|
g_led.subframe[sf].cathode[K2].stop = pwm[K2] - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
_led_map(void)
|
||||||
|
{
|
||||||
|
uint16_t pwm[42];
|
||||||
|
int k, a, l;
|
||||||
|
int sf_l, sf_h;
|
||||||
|
|
||||||
|
/* Pre-clear */
|
||||||
|
for (int sf=0; sf<16; sf++) {
|
||||||
|
g_led.subframe[sf].w[0] = 0x007f0000;
|
||||||
|
g_led.subframe[sf].w[1] = 0x007f007f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert all linear brightness to pwm times */
|
||||||
|
for (a=0, l=0; a<14; a++)
|
||||||
|
for (k=0; k<3; k++, l++)
|
||||||
|
pwm[l] = (cie_lum2pwm(g_led.leds[l]) * k_scale[k]) >> 8;
|
||||||
|
|
||||||
|
/* Attempt packing */
|
||||||
|
sf_l = 0; /* Low boundary */
|
||||||
|
sf_h = 16; /* High boundary */
|
||||||
|
|
||||||
|
for (a=0, l=0; a<14; a++, l+=3)
|
||||||
|
{
|
||||||
|
/* Check if we're overbright */
|
||||||
|
if ((pwm[l+0] > 128) ||
|
||||||
|
(pwm[l+1] > 128) ||
|
||||||
|
(pwm[l+2] > 128))
|
||||||
|
{
|
||||||
|
uint16_t p0[3], p1[3];
|
||||||
|
|
||||||
|
/* Split all brightness in two and map in two subframes */
|
||||||
|
for (int i=0; i<3; i++) {
|
||||||
|
p0[i] = (pwm[l+i] + 0) >> 1;
|
||||||
|
p1[i] = (pwm[l+i] + 1) >> 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_led_map_one(sf_l++, a, p0);
|
||||||
|
_led_map_one(--sf_h, a, p1);
|
||||||
|
}
|
||||||
|
else if ((pwm[l+0] > 0) ||
|
||||||
|
(pwm[l+1] > 0) ||
|
||||||
|
(pwm[l+2] > 0))
|
||||||
|
{
|
||||||
|
/* Map all in one subframe */
|
||||||
|
_led_map_one(sf_l++, a, &pwm[l]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
_led_fill(void)
|
||||||
|
{
|
||||||
|
int frame_limit;
|
||||||
|
bool work = false;
|
||||||
|
|
||||||
|
int f0, f1;
|
||||||
|
do {
|
||||||
|
f0 = LED_CSR_GET_FRAME(led_regs->csr);
|
||||||
|
f1 = LED_CSR_GET_FRAME(led_regs->csr);
|
||||||
|
} while (f0 != f1);
|
||||||
|
|
||||||
|
frame_limit = (f0 - 1) & (LED_N_FRAMES - 1);
|
||||||
|
|
||||||
|
/* Fill to catch up */
|
||||||
|
while (g_led.frame_nxt != frame_limit)
|
||||||
|
{
|
||||||
|
/* Render frame */
|
||||||
|
_led_render(g_led.time++, g_led.leds);
|
||||||
|
|
||||||
|
/* Convert to subframe */
|
||||||
|
_led_map();
|
||||||
|
|
||||||
|
/* Copy to hardware */
|
||||||
|
for (int subframe=0; subframe<LED_N_SUBFRAMES; subframe++) {
|
||||||
|
led_regs->frame[g_led.frame_nxt].subframe[subframe].w[0] = g_led.subframe[subframe].w[0];
|
||||||
|
led_regs->frame[g_led.frame_nxt].subframe[subframe].w[1] = g_led.subframe[subframe].w[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Next frame */
|
||||||
|
g_led.frame_nxt = (g_led.frame_nxt + 1) & (LED_N_FRAMES - 1);
|
||||||
|
work = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return work;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
led_init(void)
|
||||||
|
{
|
||||||
|
/* Clear internal state */
|
||||||
|
memset(&g_led, 0x00, sizeof(g_led));
|
||||||
|
|
||||||
|
/* Clear frame memory */
|
||||||
|
for (int frame=0; frame<LED_N_FRAMES; frame++) {
|
||||||
|
for (int subframe=0; subframe<LED_N_SUBFRAMES; subframe++) {
|
||||||
|
led_regs->frame[frame].subframe[subframe].w[0] = 0x007f0000;
|
||||||
|
led_regs->frame[frame].subframe[subframe].w[1] = 0x007f007f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test frames */
|
||||||
|
for (int frame=0; frame<LED_N_FRAMES; frame++) {
|
||||||
|
led_regs->frame[frame].subframe[0].w[0] = 0x007f0001;
|
||||||
|
led_regs->frame[frame].subframe[0].w[1] = 0x007f007f;
|
||||||
|
|
||||||
|
led_regs->frame[frame].subframe[1].w[0] = 0x7f000006;
|
||||||
|
led_regs->frame[frame].subframe[1].w[1] = 0x7f007f00;
|
||||||
|
|
||||||
|
led_regs->frame[frame].subframe[2].w[0] = 0x007f0001;
|
||||||
|
led_regs->frame[frame].subframe[2].w[1] = 0x007f007f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
led_start(void)
|
||||||
|
{
|
||||||
|
/* Reset frame number */
|
||||||
|
g_led.frame_nxt = 0;
|
||||||
|
|
||||||
|
/* Fill the full buffer */
|
||||||
|
_led_fill();
|
||||||
|
|
||||||
|
/* Enable HW */
|
||||||
|
led_regs->csr |= LED_CSR_DRV_CURREN | LED_CSR_DRV_RGBLEDEN | LED_CSR_DRV_SCAN_ENA;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
led_stop(void)
|
||||||
|
{
|
||||||
|
led_regs->csr &= ~(LED_CSR_DRV_CURREN | LED_CSR_DRV_RGBLEDEN | LED_CSR_DRV_SCAN_ENA);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
led_poll(bool suspend)
|
||||||
|
{
|
||||||
|
/* Refill anything we can */
|
||||||
|
_led_fill();
|
||||||
|
|
||||||
|
/* Schedule wake up 8 frames before expiry */
|
||||||
|
if (suspend) {
|
||||||
|
uint8_t frame_trig = (g_led.frame_nxt - 8) & (LED_N_FRAMES - 1);
|
||||||
|
led_regs->csr = (led_regs->csr & 0xffff) | LED_CSR_TRIG_CLR | LED_CSR_TRIG_ENA | LED_CSR_TRIG_FRAME(frame_trig);
|
||||||
|
pmu_sys_suspend();
|
||||||
|
led_regs->csr = (led_regs->csr & 0xffff) | LED_CSR_TRIG_CLR;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* led_ctrl.h
|
||||||
|
*
|
||||||
|
* Copyright (C) 2023 Sylvain Munaut <tnt@246tNt.com>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
void led_init(void);
|
||||||
|
void led_start(void);
|
||||||
|
void led_stop(void);
|
||||||
|
|
||||||
|
void led_poll(bool suspend);
|
|
@ -0,0 +1,60 @@
|
||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
SPRAM (xrw) : ORIGIN = 0x00020000, LENGTH = 0x10000
|
||||||
|
BRAM (xrw) : ORIGIN = 0x00000010, LENGTH = 0x03f0
|
||||||
|
}
|
||||||
|
ENTRY(_start)
|
||||||
|
SECTIONS {
|
||||||
|
.text :
|
||||||
|
{
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(.text.start)
|
||||||
|
*(.text)
|
||||||
|
*(.text*)
|
||||||
|
*(.rodata)
|
||||||
|
*(.rodata*)
|
||||||
|
*(.srodata)
|
||||||
|
*(.srodata*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
_etext = .;
|
||||||
|
_sidata = _etext;
|
||||||
|
} >SPRAM
|
||||||
|
.data : AT ( _sidata )
|
||||||
|
{
|
||||||
|
. = ALIGN(4);
|
||||||
|
_sdata = .;
|
||||||
|
_ram_start = .;
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(.data)
|
||||||
|
*(.data*)
|
||||||
|
*(.sdata)
|
||||||
|
*(.sdata*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
_edata = .;
|
||||||
|
} >SPRAM
|
||||||
|
.bss :
|
||||||
|
{
|
||||||
|
. = ALIGN(4);
|
||||||
|
_sbss = .;
|
||||||
|
*(.bss)
|
||||||
|
*(.bss*)
|
||||||
|
*(.sbss)
|
||||||
|
*(.sbss*)
|
||||||
|
*(COMMON)
|
||||||
|
. = ALIGN(4);
|
||||||
|
_ebss = .;
|
||||||
|
} >SPRAM
|
||||||
|
.heap :
|
||||||
|
{
|
||||||
|
. = ALIGN(4);
|
||||||
|
_heap_start = .;
|
||||||
|
} >SPRAM
|
||||||
|
.stack (NOLOAD):
|
||||||
|
{
|
||||||
|
. = ORIGIN(SPRAM) + LENGTH(SPRAM) - 8192;
|
||||||
|
_sstack = .;
|
||||||
|
. = . + 8192;
|
||||||
|
. = ALIGN(4);
|
||||||
|
_estack = .;
|
||||||
|
} > SPRAM
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* main.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2022-2023 Sylvain Munaut <tnt@246tNt.com>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <no2usb/usb.h>
|
||||||
|
#include <no2usb/usb_dfu_rt.h>
|
||||||
|
|
||||||
|
#include "led_ctrl.h"
|
||||||
|
#include "pmu.h"
|
||||||
|
#include "spi.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "usb_dev.h"
|
||||||
|
|
||||||
|
extern const struct usb_stack_descriptors app_stack_desc;
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
serial_no_init()
|
||||||
|
{
|
||||||
|
uint8_t buf[8];
|
||||||
|
char *id, *desc;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* Get flash ID */
|
||||||
|
flash_unique_id(buf);
|
||||||
|
|
||||||
|
/* Shutdown flash chip */
|
||||||
|
flash_deep_power_down();
|
||||||
|
|
||||||
|
/* Overwrite descriptor string */
|
||||||
|
/* In theory in rodata ... but nothing is ro here */
|
||||||
|
id = hexstr(buf, 8, false);
|
||||||
|
desc = (char*)app_stack_desc.str[1];
|
||||||
|
for (i=0; i<16; i++)
|
||||||
|
desc[2 + (i << 1)] = id[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
usb_dfu_rt_cb_reboot(void)
|
||||||
|
{
|
||||||
|
/* Force re-enumeration */
|
||||||
|
usb_disconnect();
|
||||||
|
|
||||||
|
/* Boot firmware */
|
||||||
|
pmu_sys_reboot(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//#define USB 1
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
/* SPI */
|
||||||
|
spi_init();
|
||||||
|
serial_no_init();
|
||||||
|
|
||||||
|
/* Enable LEDs */
|
||||||
|
led_init();
|
||||||
|
led_start();
|
||||||
|
|
||||||
|
/* Enable USB directly */
|
||||||
|
//#define USB
|
||||||
|
#ifdef USB
|
||||||
|
pmu_usb_enable();
|
||||||
|
usb_init(&app_stack_desc);
|
||||||
|
usb_dfu_rt_init();
|
||||||
|
usb_dev_init();
|
||||||
|
usb_connect();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Main loop */
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
#ifdef USB
|
||||||
|
/* USB poll */
|
||||||
|
usb_poll();
|
||||||
|
led_poll(false);
|
||||||
|
#else
|
||||||
|
led_poll(true);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* pmu.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2022 Sylvain Munaut <tnt@246tNt.com>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "pmu.h"
|
||||||
|
|
||||||
|
|
||||||
|
struct wb_pmu {
|
||||||
|
uint32_t ctrl;
|
||||||
|
uint32_t stat;
|
||||||
|
} __attribute__((packed,aligned(4)));
|
||||||
|
|
||||||
|
#define PMU_CTRL_SYS_SHUTDOWN (1 << 7)
|
||||||
|
#define PMU_CTRL_SYS_SUSPEND (1 << 6)
|
||||||
|
#define PMU_CTRL_USB_OFF (1 << 5)
|
||||||
|
#define PMU_CTRL_USB_ON (1 << 4)
|
||||||
|
#define PMU_CTRL_BOOT_EXEC (1 << 2)
|
||||||
|
#define PMU_CTRL_BOOT_SEL(n) (((n) & 3) << 0)
|
||||||
|
|
||||||
|
#define PMU_STAT_USB (1 << 4)
|
||||||
|
#define PMU_STAT_CHARGING (1 << 3)
|
||||||
|
#define PMU_STAT_VBUS (1 << 2)
|
||||||
|
#define PMU_STAT_BTN_MSK (3 << 0)
|
||||||
|
|
||||||
|
|
||||||
|
static volatile struct wb_pmu * const pmu_regs= (void*)(PMU_BASE);
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
pmu_sys_suspend(void)
|
||||||
|
{
|
||||||
|
pmu_regs->ctrl = PMU_CTRL_SYS_SUSPEND;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
pmu_sys_shutdown(void)
|
||||||
|
{
|
||||||
|
pmu_regs->ctrl = PMU_CTRL_SYS_SHUTDOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
pmu_sys_reboot(int n)
|
||||||
|
{
|
||||||
|
pmu_regs->ctrl = PMU_CTRL_BOOT_SEL(n) | PMU_CTRL_BOOT_EXEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
pmu_usb_enable(void)
|
||||||
|
{
|
||||||
|
pmu_regs->ctrl = PMU_CTRL_USB_ON;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
pmu_usb_disable(void)
|
||||||
|
{
|
||||||
|
pmu_regs->ctrl = PMU_CTRL_USB_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
pmu_usb_set_state(bool state)
|
||||||
|
{
|
||||||
|
pmu_regs->ctrl = state ? PMU_CTRL_USB_ON : PMU_CTRL_USB_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
pmu_usb_state(void)
|
||||||
|
{
|
||||||
|
return !!(pmu_regs->stat & PMU_STAT_USB);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
pmu_is_charging(void)
|
||||||
|
{
|
||||||
|
return !!(pmu_regs->stat & PMU_STAT_CHARGING);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
pmu_is_vbus_present(void)
|
||||||
|
{
|
||||||
|
return !!(pmu_regs->stat & PMU_STAT_VBUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t
|
||||||
|
pmu_get_buttons(void)
|
||||||
|
{
|
||||||
|
return pmu_regs->stat & PMU_STAT_BTN_MSK;
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* pmu.h
|
||||||
|
*
|
||||||
|
* Copyright (C) 2022 Sylvain Munaut <tnt@246tNt.com>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void pmu_sys_suspend(void);
|
||||||
|
void pmu_sys_shutdown(void);
|
||||||
|
void pmu_sys_reboot(int n);
|
||||||
|
|
||||||
|
void pmu_usb_enable(void);
|
||||||
|
void pmu_usb_disable(void);
|
||||||
|
void pmu_usb_set_state(bool state);
|
||||||
|
bool pmu_usb_state(void);
|
||||||
|
|
||||||
|
bool pmu_is_charging(void);
|
||||||
|
bool pmu_is_vbus_present(void);
|
||||||
|
|
||||||
|
uint8_t pmu_get_buttons(void);
|
|
@ -0,0 +1,223 @@
|
||||||
|
/*
|
||||||
|
* spi.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2023 Sylvain Munaut <tnt@246tNt.com>
|
||||||
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "spi.h"
|
||||||
|
|
||||||
|
|
||||||
|
struct spi {
|
||||||
|
uint32_t _rsvd0[6];
|
||||||
|
uint32_t irq; /* 0110 - SPIIRQ - Interrupt Status Register */
|
||||||
|
uint32_t irqen; /* 0111 - SPIIRQEN - Interrupt Control Register */
|
||||||
|
uint32_t cr0; /* 1000 - CR0 - Control Register 0 */
|
||||||
|
uint32_t cr1; /* 1001 - CR1 - Control Register 1 */
|
||||||
|
uint32_t cr2; /* 1010 - CR2 - Control Register 2 */
|
||||||
|
uint32_t br; /* 1011 - BR - Baud Rate Register */
|
||||||
|
uint32_t sr; /* 1100 - SR - Status Register */
|
||||||
|
uint32_t txdr; /* 1101 - TXDR - Transmit Data Register */
|
||||||
|
uint32_t rxdr; /* 1110 - RXDR - Receive Data Register */
|
||||||
|
uint32_t csr; /* 1111 - CSR - Chip Select Register */
|
||||||
|
} __attribute__((packed,aligned(4)));
|
||||||
|
|
||||||
|
#define SPI_CR0_TIDLE(xcnt) (((xcnt) & 3) << 6)
|
||||||
|
#define SPI_CR0_TTRAIL(xcnt) (((xcnt) & 7) << 3)
|
||||||
|
#define SPI_CR0_TLEAD(xcnt) (((xcnt) & 7) << 0)
|
||||||
|
|
||||||
|
#define SPI_CR1_ENABLE (1 << 7)
|
||||||
|
#define SPI_CR1_WKUPEN_USER (1 << 6)
|
||||||
|
#define SPI_CR1_TXEDGE (1 << 4)
|
||||||
|
|
||||||
|
#define SPI_CR2_MASTER (1 << 7)
|
||||||
|
#define SPI_CR2_MCSH (1 << 6)
|
||||||
|
#define SPI_CR2_SDBRE (1 << 5)
|
||||||
|
#define SPI_CR2_CPOL (1 << 2)
|
||||||
|
#define SPI_CR2_CPHA (1 << 1)
|
||||||
|
#define SPI_CR2_LSBF (1 << 0)
|
||||||
|
|
||||||
|
#define SPI_SR_TIP (1 << 7)
|
||||||
|
#define SPI_SR_BUSY (1 << 6)
|
||||||
|
#define SPI_SR_TRDY (1 << 4)
|
||||||
|
#define SPI_SR_RRDY (1 << 3)
|
||||||
|
#define SPI_SR_TOE (1 << 2)
|
||||||
|
#define SPI_SR_ROE (1 << 1)
|
||||||
|
#define SPI_SR_MDF (1 << 0)
|
||||||
|
|
||||||
|
|
||||||
|
static volatile struct spi * const spi_regs = (void*)(SPI_BASE);
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
spi_init(void)
|
||||||
|
{
|
||||||
|
spi_regs->cr0 = SPI_CR0_TIDLE(3) |
|
||||||
|
SPI_CR0_TTRAIL(7) |
|
||||||
|
SPI_CR0_TLEAD(7);
|
||||||
|
spi_regs->cr1 = SPI_CR1_ENABLE;
|
||||||
|
spi_regs->cr2 = SPI_CR2_MASTER | SPI_CR2_MCSH;
|
||||||
|
spi_regs->br = 3;
|
||||||
|
spi_regs->csr = 0xf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
spi_xfer(unsigned cs, struct spi_xfer_chunk *xfer, unsigned n)
|
||||||
|
{
|
||||||
|
/* Setup CS */
|
||||||
|
spi_regs->csr = 0xf ^ (1 << cs);
|
||||||
|
|
||||||
|
/* Run the chunks */
|
||||||
|
while (n--) {
|
||||||
|
for (unsigned int i=0; i<xfer->len; i++)
|
||||||
|
{
|
||||||
|
spi_regs->txdr = xfer->write ? xfer->data[i] : 0x00;
|
||||||
|
while (!(spi_regs->sr & SPI_SR_RRDY));
|
||||||
|
if (xfer->read)
|
||||||
|
xfer->data[i] = spi_regs->rxdr;
|
||||||
|
}
|
||||||
|
xfer++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clear CS */
|
||||||
|
spi_regs->csr = 0xf ^ (1 << cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#define FLASH_CMD_DEEP_POWER_DOWN 0xb9
|
||||||
|
#define FLASH_CMD_WAKE_UP 0xab
|
||||||
|
#define FLASH_CMD_WRITE_ENABLE 0x06
|
||||||
|
#define FLASH_CMD_WRITE_ENABLE_VOLATILE 0x50
|
||||||
|
#define FLASH_CMD_WRITE_DISABLE 0x04
|
||||||
|
|
||||||
|
#define FLASH_CMD_READ_MANUF_ID 0x9f
|
||||||
|
#define FLASH_CMD_READ_UNIQUE_ID 0x4b
|
||||||
|
|
||||||
|
#define FLASH_CMD_READ_SR1 0x05
|
||||||
|
#define FLASH_CMD_WRITE_SR1 0x01
|
||||||
|
|
||||||
|
#define FLASH_CMD_READ_DATA 0x03
|
||||||
|
#define FLASH_CMD_PAGE_PROGRAM 0x02
|
||||||
|
#define FLASH_CMD_CHIP_ERASE 0x60
|
||||||
|
#define FLASH_CMD_SECTOR_ERASE 0x20
|
||||||
|
|
||||||
|
void
|
||||||
|
flash_cmd(uint8_t cmd)
|
||||||
|
{
|
||||||
|
struct spi_xfer_chunk xfer[1] = {
|
||||||
|
{ .data = (void*)&cmd, .len = 1, .read = false, .write = true, },
|
||||||
|
};
|
||||||
|
spi_xfer(SPI_CS_FLASH, xfer, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
flash_deep_power_down(void)
|
||||||
|
{
|
||||||
|
flash_cmd(FLASH_CMD_DEEP_POWER_DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
flash_wake_up(void)
|
||||||
|
{
|
||||||
|
flash_cmd(FLASH_CMD_WAKE_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
flash_write_enable(void)
|
||||||
|
{
|
||||||
|
flash_cmd(FLASH_CMD_WRITE_ENABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
flash_write_enable_volatile(void)
|
||||||
|
{
|
||||||
|
flash_cmd(FLASH_CMD_WRITE_ENABLE_VOLATILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
flash_write_disable(void)
|
||||||
|
{
|
||||||
|
flash_cmd(FLASH_CMD_WRITE_DISABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
flash_manuf_id(void *manuf)
|
||||||
|
{
|
||||||
|
uint8_t cmd = FLASH_CMD_READ_MANUF_ID;
|
||||||
|
struct spi_xfer_chunk xfer[2] = {
|
||||||
|
{ .data = (void*)&cmd, .len = 1, .read = false, .write = true, },
|
||||||
|
{ .data = (void*)manuf, .len = 3, .read = true, .write = false, },
|
||||||
|
};
|
||||||
|
spi_xfer(SPI_CS_FLASH, xfer, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
flash_unique_id(void *id)
|
||||||
|
{
|
||||||
|
uint8_t cmd = FLASH_CMD_READ_UNIQUE_ID;
|
||||||
|
struct spi_xfer_chunk xfer[3] = {
|
||||||
|
{ .data = (void*)&cmd, .len = 1, .read = false, .write = true, },
|
||||||
|
{ .data = (void*)0, .len = 4, .read = false, .write = false, },
|
||||||
|
{ .data = (void*)id, .len = 8, .read = true, .write = false, },
|
||||||
|
};
|
||||||
|
spi_xfer(SPI_CS_FLASH, xfer, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t
|
||||||
|
flash_read_sr(void)
|
||||||
|
{
|
||||||
|
uint8_t cmd = FLASH_CMD_READ_SR1;
|
||||||
|
uint8_t rv;
|
||||||
|
struct spi_xfer_chunk xfer[2] = {
|
||||||
|
{ .data = (void*)&cmd, .len = 1, .read = false, .write = true, },
|
||||||
|
{ .data = (void*)&rv, .len = 1, .read = true, .write = false, },
|
||||||
|
};
|
||||||
|
spi_xfer(SPI_CS_FLASH, xfer, 2);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
flash_write_sr(uint8_t sr)
|
||||||
|
{
|
||||||
|
uint8_t cmd[2] = { FLASH_CMD_WRITE_SR1, sr };
|
||||||
|
struct spi_xfer_chunk xfer[1] = {
|
||||||
|
{ .data = (void*)cmd, .len = 2, .read = false, .write = true, },
|
||||||
|
};
|
||||||
|
spi_xfer(SPI_CS_FLASH, xfer, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
flash_read(void *dst, uint32_t addr, unsigned len)
|
||||||
|
{
|
||||||
|
uint8_t cmd[4] = { FLASH_CMD_READ_DATA, ((addr >> 16) & 0xff), ((addr >> 8) & 0xff), (addr & 0xff) };
|
||||||
|
struct spi_xfer_chunk xfer[2] = {
|
||||||
|
{ .data = (void*)cmd, .len = 4, .read = false, .write = true, },
|
||||||
|
{ .data = (void*)dst, .len = len, .read = true, .write = false, },
|
||||||
|
};
|
||||||
|
spi_xfer(SPI_CS_FLASH, xfer, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
flash_page_program(void *src, uint32_t addr, unsigned len)
|
||||||
|
{
|
||||||
|
uint8_t cmd[4] = { FLASH_CMD_PAGE_PROGRAM, ((addr >> 16) & 0xff), ((addr >> 8) & 0xff), (addr & 0xff) };
|
||||||
|
struct spi_xfer_chunk xfer[2] = {
|
||||||
|
{ .data = (void*)cmd, .len = 4, .read = false, .write = true, },
|
||||||
|
{ .data = (void*)src, .len = len, .read = false, .write = true, },
|
||||||
|
};
|
||||||
|
spi_xfer(SPI_CS_FLASH, xfer, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
flash_sector_erase(uint32_t addr)
|
||||||
|
{
|
||||||
|
uint8_t cmd[4] = { FLASH_CMD_SECTOR_ERASE, ((addr >> 16) & 0xff), ((addr >> 8) & 0xff), (addr & 0xff) };
|
||||||
|
struct spi_xfer_chunk xfer[1] = {
|
||||||
|
{ .data = (void*)cmd, .len = 4, .read = false, .write = true, },
|
||||||
|
};
|
||||||
|
spi_xfer(SPI_CS_FLASH, xfer, 1);
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* spi.h
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2023 Sylvain Munaut <tnt@246tNt.com>
|
||||||
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
struct spi_xfer_chunk {
|
||||||
|
uint8_t *data;
|
||||||
|
unsigned len;
|
||||||
|
bool write;
|
||||||
|
bool read;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SPI_CS_FLASH 0
|
||||||
|
#define SPI_CS_SRAM 1
|
||||||
|
|
||||||
|
void spi_init(void);
|
||||||
|
void spi_xfer(unsigned cs, struct spi_xfer_chunk *xfer, unsigned n);
|
||||||
|
|
||||||
|
void flash_cmd(uint8_t cmd);
|
||||||
|
void flash_deep_power_down(void);
|
||||||
|
void flash_wake_up(void);
|
||||||
|
void flash_write_enable(void);
|
||||||
|
void flash_write_disable(void);
|
||||||
|
void flash_manuf_id(void *manuf);
|
||||||
|
void flash_unique_id(void *id);
|
||||||
|
uint8_t flash_read_sr(void);
|
||||||
|
void flash_write_sr(uint8_t sr);
|
||||||
|
void flash_read(void *dst, uint32_t addr, unsigned len);
|
||||||
|
void flash_page_program(void *src, uint32_t addr, unsigned len);
|
||||||
|
void flash_sector_erase(uint32_t addr);
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* start.S
|
||||||
|
*
|
||||||
|
* Startup code taken from picosoc/picorv32 and adapted for use here
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 Clifford Wolf <clifford@clifford.at>
|
||||||
|
* Copyright (C) 2019 Sylvain Munaut <tnt@246tNt.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.section .text.start
|
||||||
|
.global _start
|
||||||
|
_start:
|
||||||
|
|
||||||
|
// zero-initialize register file
|
||||||
|
addi x1, zero, 0
|
||||||
|
// x2 (sp) is initialized by reset
|
||||||
|
addi x3, zero, 0
|
||||||
|
addi x4, zero, 0
|
||||||
|
addi x5, zero, 0
|
||||||
|
addi x6, zero, 0
|
||||||
|
addi x7, zero, 0
|
||||||
|
addi x8, zero, 0
|
||||||
|
addi x9, zero, 0
|
||||||
|
addi x10, zero, 0
|
||||||
|
addi x11, zero, 0
|
||||||
|
addi x12, zero, 0
|
||||||
|
addi x13, zero, 0
|
||||||
|
addi x14, zero, 0
|
||||||
|
addi x15, zero, 0
|
||||||
|
addi x16, zero, 0
|
||||||
|
addi x17, zero, 0
|
||||||
|
addi x18, zero, 0
|
||||||
|
addi x19, zero, 0
|
||||||
|
addi x20, zero, 0
|
||||||
|
addi x21, zero, 0
|
||||||
|
addi x22, zero, 0
|
||||||
|
addi x23, zero, 0
|
||||||
|
addi x24, zero, 0
|
||||||
|
addi x25, zero, 0
|
||||||
|
addi x26, zero, 0
|
||||||
|
addi x27, zero, 0
|
||||||
|
addi x28, zero, 0
|
||||||
|
addi x29, zero, 0
|
||||||
|
addi x30, zero, 0
|
||||||
|
addi x31, zero, 0
|
||||||
|
|
||||||
|
// copy data section
|
||||||
|
la a0, _sidata
|
||||||
|
la a1, _sdata
|
||||||
|
la a2, _edata
|
||||||
|
bge a1, a2, end_init_data
|
||||||
|
loop_init_data:
|
||||||
|
lw a3, 0(a0)
|
||||||
|
sw a3, 0(a1)
|
||||||
|
addi a0, a0, 4
|
||||||
|
addi a1, a1, 4
|
||||||
|
blt a1, a2, loop_init_data
|
||||||
|
end_init_data:
|
||||||
|
|
||||||
|
// zero-init bss section
|
||||||
|
la a0, _sbss
|
||||||
|
la a1, _ebss
|
||||||
|
bge a0, a1, end_init_bss
|
||||||
|
loop_init_bss:
|
||||||
|
sw zero, 0(a0)
|
||||||
|
addi a0, a0, 4
|
||||||
|
blt a0, a1, loop_init_bss
|
||||||
|
end_init_bss:
|
||||||
|
|
||||||
|
// stack pointer
|
||||||
|
la sp, _estack
|
||||||
|
|
||||||
|
// call main
|
||||||
|
call main
|
||||||
|
loop:
|
||||||
|
j loop
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* usb_desc_app.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2023 Sylvain Munaut <tnt@246tNt.com>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <no2usb/usb_proto.h>
|
||||||
|
#include <no2usb/usb_dfu_proto.h>
|
||||||
|
#include <no2usb/usb.h>
|
||||||
|
|
||||||
|
#include "usb_desc_ids.h"
|
||||||
|
|
||||||
|
#define NULL ((void*)0)
|
||||||
|
#define num_elem(a) (sizeof(a) / sizeof(a[0]))
|
||||||
|
|
||||||
|
|
||||||
|
static const struct {
|
||||||
|
/* Configuration */
|
||||||
|
struct usb_conf_desc conf;
|
||||||
|
|
||||||
|
/* DFU Runtime */
|
||||||
|
struct {
|
||||||
|
struct usb_intf_desc intf;
|
||||||
|
struct usb_dfu_func_desc func;
|
||||||
|
} __attribute__ ((packed)) dfu;
|
||||||
|
} __attribute__ ((packed)) _app_conf_desc = {
|
||||||
|
.conf = {
|
||||||
|
.bLength = sizeof(struct usb_conf_desc),
|
||||||
|
.bDescriptorType = USB_DT_CONF,
|
||||||
|
.wTotalLength = sizeof(_app_conf_desc),
|
||||||
|
.bNumInterfaces = USB_INTF_NUM,
|
||||||
|
.bConfigurationValue = 1,
|
||||||
|
.iConfiguration = 4,
|
||||||
|
.bmAttributes = 0x80,
|
||||||
|
.bMaxPower = 0x4b, /* 150 mA */
|
||||||
|
},
|
||||||
|
.dfu = {
|
||||||
|
.intf = {
|
||||||
|
.bLength = sizeof(struct usb_intf_desc),
|
||||||
|
.bDescriptorType = USB_DT_INTF,
|
||||||
|
.bInterfaceNumber = USB_INTF_DFU,
|
||||||
|
.bAlternateSetting = 0,
|
||||||
|
.bNumEndpoints = 0,
|
||||||
|
.bInterfaceClass = 0xfe,
|
||||||
|
.bInterfaceSubClass = 0x01,
|
||||||
|
.bInterfaceProtocol = 0x01,
|
||||||
|
.iInterface = 5,
|
||||||
|
},
|
||||||
|
.func = {
|
||||||
|
.bLength = sizeof(struct usb_dfu_func_desc),
|
||||||
|
.bDescriptorType = USB_DFU_DT_FUNC,
|
||||||
|
.bmAttributes = 0x0d,
|
||||||
|
.wDetachTimeOut = 0,
|
||||||
|
.wTransferSize = 4096,
|
||||||
|
.bcdDFUVersion = 0x0101,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct usb_conf_desc * const _conf_desc_array[] = {
|
||||||
|
&_app_conf_desc.conf,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct usb_dev_desc _dev_desc = {
|
||||||
|
.bLength = sizeof(struct usb_dev_desc),
|
||||||
|
.bDescriptorType = USB_DT_DEV,
|
||||||
|
.bcdUSB = 0x0200,
|
||||||
|
.bDeviceClass = 0,
|
||||||
|
.bDeviceSubClass = 0,
|
||||||
|
.bDeviceProtocol = 0,
|
||||||
|
.bMaxPacketSize0 = 64,
|
||||||
|
.idVendor = 0x1d50,
|
||||||
|
.idProduct = 0x1986,
|
||||||
|
.bcdDevice = 0x0001, /* v0.1 */
|
||||||
|
.iManufacturer = 2,
|
||||||
|
.iProduct = 3,
|
||||||
|
.iSerialNumber = 1,
|
||||||
|
.bNumConfigurations = num_elem(_conf_desc_array),
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "usb_str_app.gen.h"
|
||||||
|
|
||||||
|
const struct usb_stack_descriptors app_stack_desc = {
|
||||||
|
.dev = &_dev_desc,
|
||||||
|
.conf = _conf_desc_array,
|
||||||
|
.n_conf = num_elem(_conf_desc_array),
|
||||||
|
.str = _str_desc_array,
|
||||||
|
.n_str = num_elem(_str_desc_array),
|
||||||
|
};
|
|
@ -0,0 +1,11 @@
|
||||||
|
/*
|
||||||
|
* usb_desc_ids.h
|
||||||
|
*
|
||||||
|
* Copyright (C) 2023 Sylvain Munaut <tnt@246tNt.com>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define USB_INTF_DFU 0
|
||||||
|
#define USB_INTF_NUM 1
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* usb_dev.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2023 Sylvain Munaut <tnt@246tNt.com>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <no2usb/usb.h>
|
||||||
|
#include <no2usb/usb_proto.h>
|
||||||
|
|
||||||
|
#include "i2c.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define XS_DEV_I2C_REG_ACCESS 0x10
|
||||||
|
#define XS_DEV_I2C_XFER 0x1e
|
||||||
|
#define XS_DEV_I2C_PROBE 0x1f
|
||||||
|
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
int len;
|
||||||
|
uint8_t data[256];
|
||||||
|
} g_i2c_buf;
|
||||||
|
|
||||||
|
|
||||||
|
static bool
|
||||||
|
i2c_xfer_req_cb(struct usb_xfer *xfer)
|
||||||
|
{
|
||||||
|
struct usb_ctrl_req *req = xfer->cb_ctx;
|
||||||
|
g_i2c_buf.len = req->wValue & 0xff;
|
||||||
|
return i2c_xfer(
|
||||||
|
(req->wIndex >> 8),
|
||||||
|
xfer->data,
|
||||||
|
xfer->len,
|
||||||
|
g_i2c_buf.data,
|
||||||
|
g_i2c_buf.len
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static enum usb_fnd_resp
|
||||||
|
_usb_dev_ctrl_req_write(struct usb_ctrl_req *req, struct usb_xfer *xfer)
|
||||||
|
{
|
||||||
|
switch (req->bRequest) {
|
||||||
|
case XS_DEV_I2C_REG_ACCESS:
|
||||||
|
if (!i2c_write_reg((req->wIndex >> 8), req->wIndex & 0xff, req->wValue & 0xff))
|
||||||
|
return USB_FND_ERROR;
|
||||||
|
break;
|
||||||
|
case XS_DEV_I2C_XFER:
|
||||||
|
xfer->cb_done = i2c_xfer_req_cb;
|
||||||
|
xfer->cb_ctx = req;
|
||||||
|
break;
|
||||||
|
case XS_DEV_I2C_PROBE:
|
||||||
|
return i2c_probe(req->wIndex >> 8) ? USB_FND_SUCCESS : USB_FND_ERROR;
|
||||||
|
default:
|
||||||
|
return USB_FND_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return USB_FND_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum usb_fnd_resp
|
||||||
|
_usb_dev_ctrl_req_read(struct usb_ctrl_req *req, struct usb_xfer *xfer)
|
||||||
|
{
|
||||||
|
switch (req->bRequest) {
|
||||||
|
case XS_DEV_I2C_REG_ACCESS:
|
||||||
|
if (!i2c_read_reg((req->wIndex >> 8), req->wIndex & 0xff, &xfer->data[0]))
|
||||||
|
return USB_FND_ERROR;
|
||||||
|
xfer->len = 1;
|
||||||
|
break;
|
||||||
|
case XS_DEV_I2C_XFER:
|
||||||
|
xfer->data = g_i2c_buf.data;
|
||||||
|
xfer->len = g_i2c_buf.len;
|
||||||
|
break;
|
||||||
|
case XS_DEV_I2C_PROBE:
|
||||||
|
xfer->data[0] = i2c_probe(req->wIndex >> 8);
|
||||||
|
xfer->len = 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return USB_FND_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return USB_FND_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum usb_fnd_resp
|
||||||
|
_usb_dev_ctrl_req(struct usb_ctrl_req *req, struct usb_xfer *xfer)
|
||||||
|
{
|
||||||
|
/* Check it's a device-wide vendor request */
|
||||||
|
if (USB_REQ_TYPE_RCPT(req) != (USB_REQ_TYPE_VENDOR | USB_REQ_RCPT_DEV))
|
||||||
|
return USB_FND_CONTINUE;
|
||||||
|
|
||||||
|
/* Read / Write dispatch */
|
||||||
|
if (USB_REQ_IS_READ(req))
|
||||||
|
return _usb_dev_ctrl_req_read(req, xfer);
|
||||||
|
else
|
||||||
|
return _usb_dev_ctrl_req_write(req, xfer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static struct usb_fn_drv _dev_drv = {
|
||||||
|
.ctrl_req = _usb_dev_ctrl_req,
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
usb_dev_init(void)
|
||||||
|
{
|
||||||
|
usb_register_function_driver(&_dev_drv);
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
/*
|
||||||
|
* usb_dev.h
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2022 Sylvain Munaut <tnt@246tNt.com>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
void usb_dev_init(void);
|
|
@ -0,0 +1,5 @@
|
||||||
|
0000000000000000
|
||||||
|
❤️ kolačiću ❤️
|
||||||
|
Xmas Snoopy LED controller
|
||||||
|
Main
|
||||||
|
DFU runtime
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* utils.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2023 Sylvain Munaut <tnt@246tNt.com>
|
||||||
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
|
||||||
|
char *
|
||||||
|
hexstr(void *d, int n, bool space)
|
||||||
|
{
|
||||||
|
static const char * const hex = "0123456789abcdef";
|
||||||
|
static char buf[96];
|
||||||
|
uint8_t *p = d;
|
||||||
|
char *s = buf;
|
||||||
|
char c;
|
||||||
|
|
||||||
|
while (n--) {
|
||||||
|
c = *p++;
|
||||||
|
*s++ = hex[c >> 4];
|
||||||
|
*s++ = hex[c & 0xf];
|
||||||
|
if (space)
|
||||||
|
*s++ = ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
s[space?-1:0] = '\0';
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t
|
||||||
|
hexval(char c)
|
||||||
|
{
|
||||||
|
if (c >= '0' && c <= '9')
|
||||||
|
return c - '0';
|
||||||
|
else if (c >= 'a' && c <= 'f')
|
||||||
|
return 10 + (c - 'a');
|
||||||
|
else if (c >= 'A' && c <= 'F')
|
||||||
|
return 10 + (c - 'A');
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
/*
|
||||||
|
* utils.h
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2023 Sylvain Munaut <tnt@246tNt.com>
|
||||||
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
char *hexstr(void *d, int n, bool space);
|
||||||
|
uint8_t hexval(char c);
|
Loading…
Reference in New Issue