Created
July 12, 2013 11:56
-
-
Save pamaury/5983923 to your computer and use it in GitHub Desktop.
Incomplete work to merge driver into our codebase: use rockbox defines, drop usb_instance, use global config, partially implement target functions. Probably doesn't compile
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/firmware/target/arm/synopsysotg.c b/firmware/target/arm/synopsysotg.c | |
index 6fc620d..8d2d73e 100644 | |
--- a/firmware/target/arm/synopsysotg.c | |
+++ b/firmware/target/arm/synopsysotg.c | |
@@ -1,8 +1,7 @@ | |
-#include "global.h" | |
-#include "core/synopsysotg/synopsysotg.h" | |
-#include "protocol/usb/usb.h" | |
-#include "sys/time.h" | |
-#include "sys/util.h" | |
+#include "synopsysotg_regs.h" | |
+#include "synopsysotg.h" | |
+#include "usb.h" | |
+#include "usb_ch9.h" | |
#ifndef SYNOPSYSOTG_AHB_BURST_LEN | |
#define SYNOPSYSOTG_AHB_BURST_LEN 5 | |
@@ -14,50 +13,86 @@ | |
#define SYNOPSYSOTG_TURNAROUND 3 | |
#endif | |
-static void synopsysotg_flush_in_endpoint(const struct usb_instance* instance, int ep) | |
+struct __attribute__((packed,aligned(4))) synopsysotg_config | |
{ | |
- const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config; | |
- if (data->core->inep_regs[ep].diepctl.b.epena) | |
+ volatile struct synopsysotg_core_regs* core; | |
+ uint32_t phy_16bit : 1; | |
+ uint32_t phy_ulpi : 1; | |
+ uint32_t use_dma : 1; | |
+ uint32_t disable_double_buffering : 1; | |
+ uint32_t reserved0 : 4; | |
+ uint8_t reserved1; | |
+ uint16_t fifosize; | |
+ uint16_t txfifosize[16]; | |
+}; | |
+ | |
+#ifdef IPOD_NANO2G | |
+struct synopsysotg_config g_config = | |
+{ | |
+ .core = (struct synopsysotg_core_regs*)OTGBASE; | |
+ .phy_16bit = true; | |
+ .phy_ulpi = false; | |
+ .use_dma = true; | |
+ .disable_double_buffering = false; | |
+ .fifosize = 1024; | |
+ .txfifosize = { 0x100, 0x100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; | |
+}; | |
+#elif SANSA_CLIPPLUS | |
+struct synopsysotg_config g_config = | |
+{ | |
+ .core = (struct synopsysotg_core_regs*)OTGBASE; | |
+ .phy_16bit = true; | |
+ .phy_ulpi = false; | |
+ .use_dma = true; | |
+ .disable_double_buffering = false; | |
+ .fifosize = 512; | |
+ .txfifosize = { 0x100, 0x100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; | |
+}; | |
+#else | |
+#error define synopsysotg config | |
+#endif | |
+ | |
+static void synopsysotg_flush_in_endpoint(int ep) | |
+{ | |
+ if (g_config.core->inep_regs[ep].diepctl.b.epena) | |
{ | |
// Urgh, someone was still babbling on our IN pipe, and now we have some | |
// old crap in the FIFO. Disable the endpoint, to make sure nobody will | |
// fetch any more old crap while we're trying to get rid of it. | |
- synopsysotg_target_disable_irq(instance); | |
- data->core->inep_regs[ep].diepctl.b.snak = 1; | |
- while (!(data->core->inep_regs[ep].diepint.b.inepnakeff)); | |
- data->core->inep_regs[ep].diepctl.b.epdis = 1; | |
- while (!(data->core->inep_regs[ep].diepint.b.epdisabled)); | |
- synopsysotg_target_enable_irq(instance); | |
+ synopsysotg_target_disable_irq(); | |
+ g_config.core->inep_regs[ep].diepctl.b.snak = 1; | |
+ while (!(g_config.core->inep_regs[ep].diepint.b.inepnakeff)); | |
+ g_config.core->inep_regs[ep].diepctl.b.epdis = 1; | |
+ while (!(g_config.core->inep_regs[ep].diepint.b.epdisabled)); | |
+ synopsysotg_target_enable_irq(); | |
// Wait for any DMA activity to stop, to make sure nobody will touch the FIFO. | |
- while (!data->core->gregs.grstctl.b.ahbidle); | |
+ while (!g_config.core->gregs.grstctl.b.ahbidle); | |
// Flush it all the way down! | |
union synopsysotg_grstctl grstctl = { .b = { .txfnum = ep, .txfflsh = 1 } }; | |
- data->core->gregs.grstctl = grstctl; | |
- while (data->core->gregs.grstctl.b.txfflsh); | |
+ g_config.core->gregs.grstctl = grstctl; | |
+ while (g_config.core->gregs.grstctl.b.txfflsh); | |
} | |
// Reset the transfer size register. Not strictly neccessary, but can't hurt. | |
- data->core->inep_regs[ep].dieptsiz.d32 = 0; | |
+ g_config.core->inep_regs[ep].dieptsiz.d32 = 0; | |
} | |
-static void synopsysotg_flush_ints(const struct usb_instance* instance) | |
+static void synopsysotg_flush_ints() | |
{ | |
- const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config; | |
int i; | |
for (i = 0; i < 16; i++) | |
{ | |
- data->core->outep_regs[i].doepint = data->core->outep_regs[i].doepint; | |
- data->core->inep_regs[i].diepint = data->core->inep_regs[i].diepint; | |
+ g_config.core->outep_regs[i].doepint = g_config.core->outep_regs[i].doepint; | |
+ g_config.core->inep_regs[i].diepint = g_config.core->inep_regs[i].diepint; | |
} | |
- data->core->gregs.gintsts = data->core->gregs.gintsts; | |
+ g_config.core->gregs.gintsts = g_config.core->gregs.gintsts; | |
} | |
-void synopsysotg_start_rx(const struct usb_instance* instance, union usb_endpoint_number ep, void* buf, int size) | |
+void synopsysotg_start_rx(union usb_endpoint_number ep, void* buf, int size) | |
{ | |
- const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config; | |
- struct synopsysotg_state* state = (struct synopsysotg_state*)instance->driver_state; | |
+ struct synopsysotg_state* state = (struct synopsysotg_state*)->driver_state; | |
// Find the appropriate set of endpoint registers | |
- volatile struct synopsysotg_outepregs* regs = &data->core->outep_regs[ep.number]; | |
+ volatile struct synopsysotg_outepregs* regs = &g_config.core->outep_regs[ep.number]; | |
// Calculate number of packets (if size == 0 an empty packet will be sent) | |
int maxpacket = regs->doepctl.b.mps; | |
@@ -65,13 +100,13 @@ void synopsysotg_start_rx(const struct usb_instance* instance, union usb_endpoin | |
if (!packets) packets = 1; | |
// Set up data desination | |
- if (data->use_dma) regs->doepdma = buf; | |
+ if (g_config.use_dma) regs->doepdma = synopsysotg_target_get_phys_addr(buf); | |
else state->endpoints[ep.number].rxaddr = (uint32_t*)buf; | |
union synopsysotg_depxfrsiz deptsiz = { .b = { .pktcnt = packets, .xfersize = size } }; | |
regs->doeptsiz = deptsiz; | |
// Flush CPU cache if necessary | |
- if (data->use_dma) invalidate_dcache(buf, size); | |
+ if (g_config.use_dma) synopsysotg_target_invalidate_dcache(buf, size); | |
// Enable the endpoint | |
union synopsysotg_depctl depctl = regs->doepctl; | |
@@ -80,13 +115,12 @@ void synopsysotg_start_rx(const struct usb_instance* instance, union usb_endpoin | |
regs->doepctl = depctl; | |
} | |
-void synopsysotg_start_tx(const struct usb_instance* instance, union usb_endpoint_number ep, const void* buf, int size) | |
+void synopsysotg_start_tx(union usb_endpoint_number ep, const void* buf, int size) | |
{ | |
- const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config; | |
- struct synopsysotg_state* state = (struct synopsysotg_state*)instance->driver_state; | |
+ struct synopsysotg_state* state = (struct synopsysotg_state*)->driver_state; | |
// Find the appropriate set of endpoint registers | |
- volatile struct synopsysotg_inepregs* regs = &data->core->inep_regs[ep.number]; | |
+ volatile struct synopsysotg_inepregs* regs = &g_config.core->inep_regs[ep.number]; | |
// Calculate number of packets (if size == 0 an empty packet will be sent) | |
int maxpacket = regs->diepctl.b.mps; | |
@@ -94,13 +128,13 @@ void synopsysotg_start_tx(const struct usb_instance* instance, union usb_endpoin | |
if (!packets) packets = 1; | |
// Set up data desination | |
- if (data->use_dma) regs->diepdma = buf; | |
+ if (g_config.use_dma) regs->diepdma = synopsysotg_target_get_phys_addr(buf); | |
else state->endpoints[ep.number].txaddr = (uint32_t*)buf; | |
union synopsysotg_depxfrsiz deptsiz = { .b = { .pktcnt = packets, .xfersize = size } }; | |
regs->dieptsiz = deptsiz; | |
// Flush CPU cache if necessary | |
- if (data->use_dma) clean_dcache(buf, size); | |
+ if (g_config.use_dma) synopsysotg_target_clean_dcache(buf, size); | |
// Enable the endpoint | |
union synopsysotg_depctl depctl = regs->diepctl; | |
@@ -109,329 +143,305 @@ void synopsysotg_start_tx(const struct usb_instance* instance, union usb_endpoin | |
regs->diepctl = depctl; | |
// Start pushing data into the FIFO (must be done after enabling the endpoint) | |
- if (!data->use_dma) data->core->dregs.diepempmsk.ep.in |= (1 << ep.number); | |
+ if (!g_config.use_dma) g_config.core->dregs.diepempmsk.ep.in |= (1 << ep.number); | |
} | |
-void synopsysotg_set_stall(const struct usb_instance* instance, union usb_endpoint_number ep, int stall) | |
+void synopsysotg_set_stall(union usb_endpoint_number ep, int stall) | |
{ | |
- const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config; | |
- if (ep.direction == USB_ENDPOINT_DIRECTION_IN) | |
- data->core->inep_regs[ep.number].diepctl.b.stall = !!stall; | |
- else data->core->outep_regs[ep.number].doepctl.b.stall = !!stall; | |
+ if (ep.direction == DIR_IN) | |
+ g_config.core->inep_regs[ep.number].diepctl.b.stall = !!stall; | |
+ else g_config.core->outep_regs[ep.number].doepctl.b.stall = !!stall; | |
} | |
-void synopsysotg_set_address(const struct usb_instance* instance, uint8_t address) | |
+void synopsysotg_set_address(uint8_t address) | |
{ | |
- const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config; | |
- data->core->dregs.dcfg.b.devaddr = address; | |
+ g_config.core->dregs.dcfg.b.devaddr = address; | |
} | |
-void synopsysotg_unconfigure_ep(const struct usb_instance* instance, union usb_endpoint_number ep) | |
+void synopsysotg_unconfigure_ep(union usb_endpoint_number ep) | |
{ | |
- const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config; | |
union synopsysotg_depctl depctl = { .b = { .epdis = 1 } }; | |
- if (ep.direction == USB_ENDPOINT_DIRECTION_IN) | |
+ if (ep.direction == DIR_IN) | |
{ | |
- synopsysotg_flush_in_endpoint(instance, ep.number); | |
- data->core->inep_regs[ep.number].diepctl = depctl; | |
+ synopsysotg_flush_in_endpoint(ep.number); | |
+ g_config.core->inep_regs[ep.number].diepctl = depctl; | |
// Mask interrupts for this endpoint | |
- data->core->dregs.daintmsk.ep.in &= ~(1 << ep.number); | |
+ g_config.core->dregs.daintmsk.ep.in &= ~(1 << ep.number); | |
} | |
else | |
{ | |
// We can't really do much about in-flight OUT requests except for ignoring them. | |
- data->core->outep_regs[ep.number].doeptsiz.d32 = 0; | |
- data->core->outep_regs[ep.number].doepctl = depctl; | |
+ g_config.core->outep_regs[ep.number].doeptsiz.d32 = 0; | |
+ g_config.core->outep_regs[ep.number].doepctl = depctl; | |
// Mask interrupts for this endpoint | |
- data->core->dregs.daintmsk.ep.out &= ~(1 << ep.number); | |
+ g_config.core->dregs.daintmsk.ep.out &= ~(1 << ep.number); | |
} | |
} | |
-void synopsysotg_configure_ep(const struct usb_instance* instance, union usb_endpoint_number ep, | |
+void synopsysotg_configure_ep(union usb_endpoint_number ep, | |
enum usb_endpoint_type type, int maxpacket) | |
{ | |
- const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config; | |
// Reset the endpoint, just in case someone left it in a dirty state. | |
- synopsysotg_unconfigure_ep(instance, ep); | |
+ synopsysotg_unconfigure_ep(ep); | |
// Write the new configuration and unmask interrupts for the endpoint. | |
// Reset data toggle to DATA0, as required by the USB specification. | |
union synopsysotg_depctl depctl = { .b = { .usbactep = 1, .eptype = type, .mps = maxpacket, .txfnum = ep.number, .setd0pid = 1 } }; | |
- if (ep.direction == USB_ENDPOINT_DIRECTION_IN) | |
+ if (ep.direction == DIR_IN) | |
{ | |
- data->core->inep_regs[ep.number].diepctl = depctl; | |
- data->core->dregs.daintmsk.ep.in |= (1 << ep.number); | |
+ g_config.core->inep_regs[ep.number].diepctl = depctl; | |
+ g_config.core->dregs.daintmsk.ep.in |= (1 << ep.number); | |
} | |
else | |
{ | |
- data->core->outep_regs[ep.number].doepctl = depctl; | |
- data->core->dregs.daintmsk.ep.out |= (1 << ep.number); | |
+ g_config.core->outep_regs[ep.number].doepctl = depctl; | |
+ g_config.core->dregs.daintmsk.ep.out |= (1 << ep.number); | |
} | |
} | |
-void synopsysotg_ep0_start_rx(const struct usb_instance* instance, int non_setup) | |
+void synopsysotg_ep0_start_rx(int non_setup) | |
{ | |
- const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config; | |
- struct synopsysotg_state* state = (struct synopsysotg_state*)instance->driver_state; | |
+ struct synopsysotg_state* state = (struct synopsysotg_state*)->driver_state; | |
// If we don't expect a non-SETUP packet, we can stall the OUT pipe, | |
// SETUP packets will ignore that. | |
- if (!non_setup) data->core->outep_regs[0].doepctl.b.stall = 1; | |
+ if (!non_setup) g_config.core->outep_regs[0].doepctl.b.stall = 1; | |
// Set up data desination | |
- if (data->use_dma) data->core->outep_regs[0].doepdma = instance->buffer; | |
- else state->endpoints[0].rxaddr = (uint32_t*)instance->buffer; | |
+ if (g_config.use_dma) g_config.core->outep_regs[0].doepdma = ->buffer; | |
+ else state->endpoints[0].rxaddr = (uint32_t*)->buffer; | |
union synopsysotg_dep0xfrsiz deptsiz = { .b = { .supcnt = 3, .pktcnt = !!non_setup, .xfersize = 64 } }; | |
- data->core->outep_regs[0].doeptsiz.d32 = deptsiz.d32; | |
+ g_config.core->outep_regs[0].doeptsiz.d32 = deptsiz.d32; | |
// Flush CPU cache if necessary | |
- if (data->use_dma) invalidate_dcache(instance->buffer, sizeof(instance->buffer)); | |
+ if (g_config.use_dma) invalidate_dcache(->buffer, sizeof(->buffer)); | |
// Enable the endpoint | |
- union synopsysotg_depctl depctl = data->core->outep_regs[0].doepctl; | |
+ union synopsysotg_depctl depctl = g_config.core->outep_regs[0].doepctl; | |
depctl.b.epena = 1; | |
depctl.b.cnak = 1; | |
- data->core->outep_regs[0].doepctl = depctl; | |
+ g_config.core->outep_regs[0].doepctl = depctl; | |
} | |
-void synopsysotg_ep0_start_tx(const struct usb_instance* instance, const void* buf, int len) | |
+void synopsysotg_ep0_start_tx(const void* buf, int len) | |
{ | |
- const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config; | |
- struct synopsysotg_state* state = (struct synopsysotg_state*)instance->driver_state; | |
+ struct synopsysotg_state* state = (struct synopsysotg_state*)->driver_state; | |
if (len) | |
{ | |
// Set up data source | |
- if (data->use_dma) data->core->inep_regs[0].diepdma = buf; | |
+ if (g_config.use_dma) g_config.core->inep_regs[0].diepdma = buf; | |
else state->endpoints[0].txaddr = buf; | |
union synopsysotg_dep0xfrsiz deptsiz = { .b = { .pktcnt = (len + 63) >> 6, .xfersize = len } }; | |
- data->core->inep_regs[0].dieptsiz.d32 = deptsiz.d32; | |
+ g_config.core->inep_regs[0].dieptsiz.d32 = deptsiz.d32; | |
} | |
else | |
{ | |
// Set up the IN pipe for a zero-length packet | |
union synopsysotg_dep0xfrsiz deptsiz = { .b = { .pktcnt = 1 } }; | |
- data->core->inep_regs[0].dieptsiz.d32 = deptsiz.d32; | |
+ g_config.core->inep_regs[0].dieptsiz.d32 = deptsiz.d32; | |
} | |
// Flush CPU cache if necessary | |
- if (data->use_dma) clean_dcache(buf, len); | |
+ if (g_config.use_dma) clean_dcache(buf, len); | |
// Enable the endpoint | |
- union synopsysotg_depctl depctl = data->core->inep_regs[0].diepctl; | |
+ union synopsysotg_depctl depctl = g_config.core->inep_regs[0].diepctl; | |
depctl.b.epena = 1; | |
depctl.b.cnak = 1; | |
- data->core->inep_regs[0].diepctl = depctl; | |
+ g_config.core->inep_regs[0].diepctl = depctl; | |
// Start pushing data into the FIFO (must be done after enabling the endpoint) | |
- if (len && !data->use_dma) data->core->dregs.diepempmsk.ep.in |= 1; | |
+ if (len && !g_config.use_dma) g_config.core->dregs.diepempmsk.ep.in |= 1; | |
} | |
-static void synopsysotg_ep0_init(const struct usb_instance* instance) | |
+static void synopsysotg_ep0_init() | |
{ | |
- const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config; | |
- | |
// Make sure both EP0 pipes are active. | |
// (The hardware should take care of that, but who knows...) | |
union synopsysotg_depctl depctl = { .b = { .usbactep = 1 } }; | |
- data->core->outep_regs[0].doepctl = depctl; | |
- data->core->inep_regs[0].diepctl = depctl; | |
+ g_config.core->outep_regs[0].doepctl = depctl; | |
+ g_config.core->inep_regs[0].diepctl = depctl; | |
// Prime EP0 for the first setup packet. | |
- usb_ep0_expect_setup(instance); | |
+ usb_ep0_expect_setup(); | |
} | |
-void synopsysotg_irq(const struct usb_instance* instance) | |
+void synopsysotg_irq() | |
{ | |
- const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config; | |
- struct synopsysotg_state* state = (struct synopsysotg_state*)instance->driver_state; | |
+ struct synopsysotg_state* state = (struct synopsysotg_state*)->driver_state; | |
- union synopsysotg_gintsts gintsts = data->core->gregs.gintsts; | |
+ union synopsysotg_gintsts gintsts = g_config.core->gregs.gintsts; | |
if (gintsts.b.usbreset) | |
{ | |
- data->core->dregs.dcfg.b.devaddr = 0; | |
- synopsysotg_ep0_init(instance); | |
- usb_handle_bus_reset(instance, data->core->dregs.dsts.b.enumspd == 0); | |
+ g_config.core->dregs.dcfg.b.devaddr = 0; | |
+ synopsysotg_ep0_init(); | |
+ usb_handle_bus_reset(g_config.core->dregs.dsts.b.enumspd == 0); | |
} | |
if (gintsts.b.rxstsqlvl) | |
{ | |
// Device to memory part of the "software DMA" implementation, used to receive data if use_dma == 0. | |
// Handle one packet at a time, the IRQ will re-trigger if there's something left. | |
- union synopsysotg_grxfsts rxsts = data->core->gregs.grxstsp; | |
+ union synopsysotg_grxfsts rxsts = g_config.core->gregs.grxstsp; | |
int ep = rxsts.b.chnum; | |
int words = (rxsts.b.bcnt + 3) >> 2; | |
- while (words--) *state->endpoints[ep].rxaddr++ = data->core->dfifo[0][0]; | |
+ while (words--) *state->endpoints[ep].rxaddr++ = g_config.core->dfifo[0][0]; | |
} | |
if (gintsts.b.inepintr) | |
{ | |
- union synopsysotg_daint daint = data->core->dregs.daint; | |
+ union synopsysotg_daint daint = g_config.core->dregs.daint; | |
int ep; | |
for (ep = 0; ep < 16; ep++) | |
if (daint.ep.in & (1 << ep)) | |
{ | |
- union synopsysotg_diepintn epints = data->core->inep_regs[ep].diepint; | |
+ union synopsysotg_diepintn epints = g_config.core->inep_regs[ep].diepint; | |
if (epints.b.emptyintr) | |
{ | |
// Memory to device part of the "software DMA" implementation, used to transmit data if use_dma == 0. | |
- union synopsysotg_depxfrsiz deptsiz = data->core->inep_regs[ep].dieptsiz; | |
- if (!deptsiz.b.pktcnt) data->core->dregs.diepempmsk.ep.in &= ~(1 << ep); | |
+ union synopsysotg_depxfrsiz deptsiz = g_config.core->inep_regs[ep].dieptsiz; | |
+ if (!deptsiz.b.pktcnt) g_config.core->dregs.diepempmsk.ep.in &= ~(1 << ep); | |
else | |
{ | |
// Push data into the TX FIFO until we don't have anything left or the FIFO would overflow. | |
int left = (deptsiz.b.xfersize + 3) >> 2; | |
while (left) | |
{ | |
- int words = data->core->inep_regs[ep].dtxfsts.b.txfspcavail; | |
+ int words = g_config.core->inep_regs[ep].dtxfsts.b.txfspcavail; | |
if (words > left) words = left; | |
if (!words) break; | |
left -= words; | |
- while (words--) data->core->dfifo[ep][0] = *state->endpoints[ep].txaddr++; | |
+ while (words--) g_config.core->dfifo[ep][0] = *state->endpoints[ep].txaddr++; | |
} | |
} | |
} | |
- union usb_endpoint_number epnum = { .direction = USB_ENDPOINT_DIRECTION_IN, .number = ep }; | |
- int bytesleft = data->core->inep_regs[ep].dieptsiz.b.xfersize; | |
- if (epints.b.timeout) usb_handle_timeout(instance, epnum, bytesleft); | |
- if (epints.b.xfercompl) usb_handle_xfer_complete(instance, epnum, bytesleft); | |
- data->core->inep_regs[ep].diepint = epints; | |
+ union usb_endpoint_number epnum = { .direction = DIR_IN, .number = ep }; | |
+ int bytesleft = g_config.core->inep_regs[ep].dieptsiz.b.xfersize; | |
+ if (epints.b.timeout) usb_handle_timeout(epnum, bytesleft); | |
+ if (epints.b.xfercompl) usb_handle_xfer_complete(epnum, bytesleft); | |
+ g_config.core->inep_regs[ep].diepint = epints; | |
} | |
} | |
if (gintsts.b.outepintr) | |
{ | |
- union synopsysotg_daint daint = data->core->dregs.daint; | |
+ union synopsysotg_daint daint = g_config.core->dregs.daint; | |
int ep; | |
for (ep = 0; ep < 16; ep++) | |
if (daint.ep.out & (1 << ep)) | |
{ | |
- union synopsysotg_doepintn epints = data->core->outep_regs[ep].doepint; | |
- union usb_endpoint_number epnum = { .direction = USB_ENDPOINT_DIRECTION_OUT, .number = ep }; | |
+ union synopsysotg_doepintn epints = g_config.core->outep_regs[ep].doepint; | |
+ union usb_endpoint_number epnum = { .direction = DIR_OUT, .number = ep }; | |
if (epints.b.setup) | |
{ | |
- if (data->use_dma) invalidate_dcache(instance->buffer, sizeof(instance->buffer)); | |
- union synopsysotg_dep0xfrsiz deptsiz = { .d32 = data->core->outep_regs[0].doeptsiz.d32 }; | |
+ if (g_config.use_dma) invalidate_dcache(->buffer, sizeof(->buffer)); | |
+ union synopsysotg_dep0xfrsiz deptsiz = { .d32 = g_config.core->outep_regs[0].doeptsiz.d32 }; | |
int back2back = 3 - deptsiz.b.supcnt; | |
- synopsysotg_flush_in_endpoint(instance, ep); | |
- usb_handle_setup_received(instance, epnum, back2back); | |
+ synopsysotg_flush_in_endpoint(ep); | |
+ usb_handle_setup_received(epnum, back2back); | |
} | |
else if (epints.b.xfercompl) | |
{ | |
- int bytesleft = data->core->inep_regs[ep].dieptsiz.b.xfersize; | |
- usb_handle_xfer_complete(instance, epnum, bytesleft); | |
+ int bytesleft = g_config.core->inep_regs[ep].dieptsiz.b.xfersize; | |
+ usb_handle_xfer_complete(epnum, bytesleft); | |
} | |
- data->core->outep_regs[ep].doepint = epints; | |
+ g_config.core->outep_regs[ep].doepint = epints; | |
} | |
} | |
- data->core->gregs.gintsts = gintsts; | |
+ g_config.core->gregs.gintsts = gintsts; | |
} | |
-void synopsysotg_init(const struct usb_instance* instance) | |
+void synopsysotg_init() | |
{ | |
int i; | |
- const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config; | |
- | |
// Disable IRQ during setup | |
- synopsysotg_target_disable_irq(instance); | |
+ synopsysotg_target_disable_irq(); | |
// Enable OTG clocks | |
- synopsysotg_target_enable_clocks(instance); | |
+ synopsysotg_target_enable_clocks(); | |
// Enable PHY clocks | |
union synopsysotg_pcgcctl pcgcctl = { .b = {} }; | |
- data->core->pcgcctl = pcgcctl; | |
+ g_config.core->pcgcctl = pcgcctl; | |
// Configure PHY type (must be done before reset) | |
union synopsysotg_gccfg gccfg = { .b = { .disablevbussensing = 1, .pwdn = 0 } }; | |
- data->core->gregs.gccfg = gccfg; | |
+ g_config.core->gregs.gccfg = gccfg; | |
union synopsysotg_gusbcfg gusbcfg = { .b = { .force_dev = 1, .usbtrdtim = SYNOPSYSOTG_TURNAROUND } }; | |
- if (data->phy_16bit) gusbcfg.b.phyif = 1; | |
- else if (data->phy_ulpi) gusbcfg.b.ulpi_utmi_sel = 1; | |
+ if (g_config.phy_16bit) gusbcfg.b.phyif = 1; | |
+ else if (g_config.phy_ulpi) gusbcfg.b.ulpi_utmi_sel = 1; | |
else gusbcfg.b.physel = 1; | |
- data->core->gregs.gusbcfg = gusbcfg; | |
+ g_config.core->gregs.gusbcfg = gusbcfg; | |
// Reset the whole USB core | |
union synopsysotg_grstctl grstctl = { .b = { .csftrst = 1 } }; | |
udelay(100); | |
- while (!data->core->gregs.grstctl.b.ahbidle); | |
- data->core->gregs.grstctl = grstctl; | |
- while (data->core->gregs.grstctl.b.csftrst); | |
- while (!data->core->gregs.grstctl.b.ahbidle); | |
+ while (!g_config.core->gregs.grstctl.b.ahbidle); | |
+ g_config.core->gregs.grstctl = grstctl; | |
+ while (g_config.core->gregs.grstctl.b.csftrst); | |
+ while (!g_config.core->gregs.grstctl.b.ahbidle); | |
// Soft disconnect | |
union synopsysotg_dctl dctl = { .b = { .sftdiscon = 1 } }; | |
- data->core->dregs.dctl = dctl; | |
+ g_config.core->dregs.dctl = dctl; | |
// Configure the core | |
- union synopsysotg_gahbcfg gahbcfg = { .b = { .dmaenable = data->use_dma, .hburstlen = SYNOPSYSOTG_AHB_BURST_LEN, .glblintrmsk = 1 } }; | |
- if (data->disable_double_buffering) | |
+ union synopsysotg_gahbcfg gahbcfg = { .b = { .dmaenable = g_config.use_dma, .hburstlen = SYNOPSYSOTG_AHB_BURST_LEN, .glblintrmsk = 1 } }; | |
+ if (g_config.disable_double_buffering) | |
{ | |
gahbcfg.b.nptxfemplvl_txfemplvl = 1; | |
gahbcfg.b.ptxfemplvl = 1; | |
} | |
- data->core->gregs.gahbcfg = gahbcfg; | |
- data->core->gregs.gusbcfg = gusbcfg; | |
+ g_config.core->gregs.gahbcfg = gahbcfg; | |
+ g_config.core->gregs.gusbcfg = gusbcfg; | |
gccfg.b.pwdn = 1; | |
- data->core->gregs.gccfg = gccfg; | |
+ g_config.core->gregs.gccfg = gccfg; | |
union synopsysotg_dcfg dcfg = { .b = { .nzstsouthshk = 1 } }; | |
- data->core->dregs.dcfg = dcfg; | |
+ g_config.core->dregs.dcfg = dcfg; | |
// Configure the FIFOs | |
- if (data->use_dma) | |
+ if (g_config.use_dma) | |
{ | |
union synopsysotg_dthrctl dthrctl = { .b = { .arb_park_en = 1, .rx_thr_en = 1, .iso_thr_en = 0, .non_iso_thr_en = 0, | |
.rx_thr_len = SYNOPSYSOTG_AHB_THRESHOLD } }; | |
- data->core->dregs.dthrctl = dthrctl; | |
+ g_config.core->dregs.dthrctl = dthrctl; | |
} | |
- int addr = data->fifosize; | |
+ int addr = g_config.fifosize; | |
for (i = 0; i < 16; i++) | |
{ | |
- int size = data->txfifosize[i]; | |
+ int size = g_config.txfifosize[i]; | |
addr -= size; | |
if (size) | |
{ | |
union synopsysotg_txfsiz fsiz = { .b = { .startaddr = addr, .depth = size } }; | |
- if (!i) data->core->gregs.dieptxf0_hnptxfsiz = fsiz; | |
- else data->core->gregs.dieptxf[i - 1] = fsiz; | |
+ if (!i) g_config.core->gregs.dieptxf0_hnptxfsiz = fsiz; | |
+ else g_config.core->gregs.dieptxf[i - 1] = fsiz; | |
} | |
} | |
union synopsysotg_rxfsiz fsiz = { .b = { .depth = addr } }; | |
- data->core->gregs.grxfsiz = fsiz; | |
+ g_config.core->gregs.grxfsiz = fsiz; | |
// Set up interrupts | |
union synopsysotg_doepintn doepmsk = { .b = { .xfercompl = 1, .setup = 1 } }; | |
- data->core->dregs.doepmsk = doepmsk; | |
+ g_config.core->dregs.doepmsk = doepmsk; | |
union synopsysotg_diepintn diepmsk = { .b = { .xfercompl = 1, .timeout = 1 } }; | |
- data->core->dregs.diepmsk = diepmsk; | |
- data->core->dregs.diepempmsk.d32 = 0; | |
+ g_config.core->dregs.diepmsk = diepmsk; | |
+ g_config.core->dregs.diepempmsk.d32 = 0; | |
union synopsysotg_daint daintmsk = { .ep = { .in = 0b0000000000000001, .out = 0b0000000000000001 } }; | |
- data->core->dregs.daintmsk = daintmsk; | |
+ g_config.core->dregs.daintmsk = daintmsk; | |
union synopsysotg_gintmsk gintmsk = { .b = { .usbreset = 1, .outepintr = 1, .inepintr = 1 } }; | |
- if (!data->use_dma) gintmsk.b.rxstsqlvl = 1; | |
- data->core->gregs.gintmsk = gintmsk; | |
- synopsysotg_flush_ints(instance); | |
- synopsysotg_target_clear_irq(instance); | |
- synopsysotg_target_enable_irq(instance); | |
+ if (!g_config.use_dma) gintmsk.b.rxstsqlvl = 1; | |
+ g_config.core->gregs.gintmsk = gintmsk; | |
+ synopsysotg_flush_ints(); | |
+ synopsysotg_target_clear_irq(); | |
+ synopsysotg_target_enable_irq(); | |
// Soft reconnect | |
dctl.b.sftdiscon = 0; | |
- data->core->dregs.dctl = dctl; | |
+ g_config.core->dregs.dctl = dctl; | |
} | |
- | |
-const struct usb_driver synopsysotg_driver = | |
-{ | |
- .init = synopsysotg_init, | |
- .ep0_start_rx = synopsysotg_ep0_start_rx, | |
- .ep0_start_tx = synopsysotg_ep0_start_tx, | |
- .start_rx = synopsysotg_start_rx, | |
- .start_tx = synopsysotg_start_tx, | |
- .set_stall = synopsysotg_set_stall, | |
- .set_address = synopsysotg_set_address, | |
- .configure_ep = synopsysotg_configure_ep, | |
- .unconfigure_ep = synopsysotg_unconfigure_ep, | |
-}; | |
diff --git a/firmware/target/arm/synopsysotg.h b/firmware/target/arm/synopsysotg.h | |
index a335115..bce99e5 100644 | |
--- a/firmware/target/arm/synopsysotg.h | |
+++ b/firmware/target/arm/synopsysotg.h | |
@@ -1,22 +1,8 @@ | |
#ifndef __CORE_SYNOPSYSOTG_SYNOPSYSOTG_H__ | |
#define __CORE_SYNOPSYSOTG_SYNOPSYSOTG_H__ | |
-#include "global.h" | |
-#include "protocol/usb/usb.h" | |
-#include "core/synopsysotg/regs.h" | |
- | |
-struct __attribute__((packed,aligned(4))) synopsysotg_config | |
-{ | |
- volatile struct synopsysotg_core_regs* core; | |
- uint32_t phy_16bit : 1; | |
- uint32_t phy_ulpi : 1; | |
- uint32_t use_dma : 1; | |
- uint32_t disable_double_buffering : 1; | |
- uint32_t reserved0 : 4; | |
- uint8_t reserved1; | |
- uint16_t fifosize; | |
- uint16_t txfifosize[16]; | |
-}; | |
+#include "usb.h" | |
+#include "usb_ch9.h" | |
struct __attribute__((packed,aligned(4))) synopsysotg_state | |
{ | |
@@ -28,12 +14,34 @@ struct __attribute__((packed,aligned(4))) synopsysotg_state | |
} endpoints[]; | |
}; | |
-extern const struct usb_driver synopsysotg_driver; | |
+union __attribute__((packed)) usb_endpoint_number | |
+{ | |
+ struct __attribute__((packed)) | |
+ { | |
+ int number : 4; | |
+ int reserved: 3; | |
+ int direction : 1; | |
+ }; | |
+ uint8_t byte; | |
+}; | |
+ | |
+void synopsysotg_init(); | |
+void synopsysotg_ep0_start_rx(int non_setup); | |
+void synopsysotg_ep0_start_tx(const void* buf, int len); | |
+void synopsysotg_start_rx(union usb_endpoint_number ep, void* buf, int size); | |
+void synopsysotg_start_tx(union usb_endpoint_number ep, const void* buf, int size); | |
+void synopsysotg_set_stall(union usb_endpoint_number ep, int stall); | |
+void synopsysotg_set_address(uint8_t address); | |
+void synopsysotg_configure_ep(union usb_endpoint_number ep, enum usb_endpoint_type type, int maxpacket); | |
+void synopsysotg_unconfigure_ep(union usb_endpoint_number ep); | |
+void synopsysotg_irq(); | |
-extern void synopsysotg_irq(const struct usb_instance* instance); | |
-extern void synopsysotg_target_enable_clocks(const struct usb_instance* instance); | |
-extern void synopsysotg_target_enable_irq(const struct usb_instance* instance); | |
-extern void synopsysotg_target_disable_irq(const struct usb_instance* instance); | |
-extern void synopsysotg_target_clear_irq(const struct usb_instance* instance); | |
+void synopsysotg_target_enable_clocks(); | |
+void synopsysotg_target_enable_irq(); | |
+void synopsysotg_target_disable_irq(); | |
+void synopsysotg_target_clear_irq(); | |
+void synopsysotg_target_clean_dcache(const void *buf, int len); | |
+void synopsysotg_target_invalidate_dcache(const void *buf, int len); | |
+uint32_t synopsysotg_target_get_phys_addr(const void *buf); | |
#endif | |
diff --git a/firmware/target/arm/usb-s3c6400x.c b/firmware/target/arm/usb-s3c6400x.c | |
index 48521aa..d39e7be 100644 | |
--- a/firmware/target/arm/usb-s3c6400x.c | |
+++ b/firmware/target/arm/usb-s3c6400x.c | |
@@ -40,229 +40,84 @@ | |
#include "logf.h" | |
#if CONFIG_CPU == AS3525v2 | |
-#define UNCACHED_ADDR AS3525_UNCACHED_ADDR | |
-#define PHYSICAL_ADDR AS3525_PHYSICAL_ADDR | |
-static inline void discard_dma_buffer_cache(void) {} | |
-#else | |
-#define UNCACHED_ADDR | |
-#define PHYSICAL_ADDR | |
-static inline void discard_dma_buffer_cache(void) { commit_discard_dcache(); } | |
-#endif | |
- | |
-/* store per endpoint, per direction, information */ | |
-struct ep_type | |
-{ | |
- unsigned int size; /* length of the data buffer */ | |
- struct semaphore complete; /* wait object */ | |
- int8_t status; /* completion status (0 for success) */ | |
- bool active; /* true is endpoint has been requested (true for EP0) */ | |
- bool done; /* transfer completed */ | |
- bool busy; /* true is a transfer is pending */ | |
-}; | |
- | |
-static const uint8_t in_ep_list[] = {0, 1, 3, 5}; | |
-static const uint8_t out_ep_list[] = {0, 2, 4}; | |
- | |
-/* state of EP0 (to correctly schedule setup packet enqueing) */ | |
-enum ep0state | |
+void synopsysotg_target_clean_dcache(const void *buf, int len) | |
{ | |
- /* Setup packet is enqueud, waiting for actual data */ | |
- EP0_WAIT_SETUP = 0, | |
- /* Waiting for ack (either IN or OUT) */ | |
- EP0_WAIT_ACK = 1, | |
- /* Ack complete, waiting for data (either IN or OUT) | |
- * This state is necessary because if both ack and data complete in the | |
- * same interrupt, we might process data completion before ack completion | |
- * so we need this bizarre state */ | |
- EP0_WAIT_DATA = 2, | |
- /* Setup packet complete, waiting for ack and data */ | |
- EP0_WAIT_DATA_ACK = 3, | |
-}; | |
- | |
-/* endpoints[ep_num][DIR_IN/DIR_OUT] */ | |
-static struct ep_type endpoints[USB_NUM_ENDPOINTS][2]; | |
-/* setup packet for EP0 */ | |
- | |
-/* USB control requests may be up to 64 bytes in size. | |
- Even though we never use anything more than the 8 header bytes, | |
- we are required to accept request packets of up to 64 bytes size. | |
- Provide buffer space for these additional payload bytes so that | |
- e.g. write descriptor requests (which are rejected by us, but the | |
- payload is transferred anyway) do not cause memory corruption. | |
- Fixes FS#12310. -- Michael Sparmann (theseven) */ | |
-static union { | |
- struct usb_ctrlrequest header; /* 8 bytes */ | |
- unsigned char payload[64]; | |
-} _ep0_setup_pkt USB_DEVBSS_ATTR; | |
- | |
-static struct usb_ctrlrequest *ep0_setup_pkt = UNCACHED_ADDR(&_ep0_setup_pkt.header); | |
- | |
-/* state of EP0 */ | |
-static enum ep0state ep0_state; | |
+ commit_dcache_range(buf, len); | |
+} | |
-bool usb_drv_stalled(int endpoint, bool in) | |
+void synopsysotg_target_invalidate_dcache(const void *buf, int len) | |
{ | |
- return DEPCTL(endpoint, !in) & DEPCTL_stall; | |
+ discard_dcache_range(buf, len); | |
} | |
-void usb_drv_stall(int endpoint, bool stall, bool in) | |
+uint32_t synopsysotg_target_get_phys_addr(const void *buf) | |
{ | |
- if (stall) | |
- DEPCTL(endpoint, !in) |= DEPCTL_stall; | |
- else | |
- DEPCTL(endpoint, !in) &= ~DEPCTL_stall; | |
+ return AS3525_PHYSICAL_ADDR(buf); | |
} | |
- | |
-void usb_drv_set_address(int address) | |
+#else | |
+void synopsysotg_target_clean_dcache(const void *buf, int len) | |
{ | |
- (void)address; | |
- /* Ignored intentionally, because the controller requires us to set the | |
- new address before sending the response for some reason. So we'll | |
- already set it when the control request arrives, before passing that | |
- into the USB core, which will then call this dummy function. */ | |
} | |
-static void ep_transfer(int ep, void *ptr, int len, bool out) | |
+void synopsysotg_target_invalidate_dcache(const void *buf, int len) | |
{ | |
- /* disable interrupts to avoid any race */ | |
- int oldlevel = disable_irq_save(); | |
- | |
- struct ep_type *endpoint = &endpoints[ep][out ? DIR_OUT : DIR_IN]; | |
- endpoint->busy = true; | |
- endpoint->size = len; | |
- endpoint->status = -1; | |
- | |
- if (out) | |
- DEPCTL(ep, out) &= ~DEPCTL_stall; | |
- | |
- int mps = usb_drv_port_speed() ? 512 : 64; | |
- int nb_packets = (len + mps - 1) / mps; | |
- if (nb_packets == 0) | |
- nb_packets = 1; | |
- | |
- DEPDMA(ep, out) = len ? (void*)PHYSICAL_ADDR(ptr) : NULL; | |
- DEPTSIZ(ep, out) = (nb_packets << DEPTSIZ_pkcnt_bitp) | len; | |
- if(out) | |
- discard_dcache_range(ptr, len); | |
- else | |
- commit_dcache_range(ptr, len); | |
- | |
- logf("pkt=%d dma=%lx", nb_packets, DEPDMA(ep, out)); | |
- | |
-// if (!out) while (((GNPTXSTS & 0xffff) << 2) < MIN(mps, length)); | |
- | |
- DEPCTL(ep, out) |= DEPCTL_epena | DEPCTL_cnak; | |
- | |
- restore_irq(oldlevel); | |
} | |
-int usb_drv_send_nonblocking(int endpoint, void *ptr, int length) | |
+uint32_t synopsysotg_target_get_phys_addr(const void *buf) | |
{ | |
- ep_transfer(EP_NUM(endpoint), ptr, length, false); | |
- return 0; | |
+ return (uint32_t)buf; | |
} | |
+#endif | |
-int usb_drv_recv(int endpoint, void* ptr, int length) | |
+void synopsysotg_target_enable_clocks() | |
{ | |
- ep_transfer(EP_NUM(endpoint), ptr, length, true); | |
- return 0; | |
} | |
-int usb_drv_port_speed(void) | |
+void synopsysotg_target_enable_irq() | |
{ | |
- static const uint8_t speed[4] = { | |
- [DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ] = 1, | |
- [DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ] = 0, | |
- [DSTS_ENUMSPD_FS_PHY_48MHZ] = 0, | |
- [DSTS_ENUMSPD_LS_PHY_6MHZ] = 0, | |
- }; | |
- | |
- unsigned enumspd = extract(DSTS, enumspd); | |
- | |
- if(enumspd == DSTS_ENUMSPD_LS_PHY_6MHZ) | |
- panicf("usb-drv: LS is not supported"); | |
- | |
- return speed[enumspd & 3]; | |
} | |
-void usb_drv_set_test_mode(int mode) | |
+void synopsysotg_target_disable_irq() | |
{ | |
- /* there is a perfect matching between usb test mode code | |
- * and the register field value */ | |
- DCTL = (DCTL & ~bitm(DCTL, tstctl)) | (mode << DCTL_tstctl_bitp); | |
} | |
-void usb_attach(void) | |
+void synopsysotg_target_clear_irq() | |
{ | |
- usb_enable(true); // s5l only ? | |
- /* Nothing to do */ | |
} | |
-static void prepare_setup_ep0(void) | |
+bool usb_drv_stalled(int endpoint, bool in) | |
{ | |
- DEPDMA(0, true) = (void*)PHYSICAL_ADDR(&_ep0_setup_pkt); | |
- DEPTSIZ(0, true) = (1 << DEPTSIZ0_supcnt_bitp) | |
- | (1 << DEPTSIZ0_pkcnt_bitp) | |
- | 8; | |
- DEPCTL(0, true) |= DEPCTL_epena | DEPCTL_cnak; | |
+} | |
- ep0_state = EP0_WAIT_SETUP; | |
+void usb_drv_stall(int endpoint, bool stall, bool in) | |
+{ | |
} | |
-static size_t num_eps(bool out) | |
+void usb_drv_set_address(int address) | |
{ | |
- return out ? sizeof(out_ep_list) : sizeof(in_ep_list); | |
} | |
-static void reset_endpoints(void) | |
+int usb_drv_send_nonblocking(int endpoint, void *ptr, int length) | |
{ | |
- for (int dir = 0; dir < 2; dir++) | |
- { | |
- bool out = dir == DIR_OUT; | |
- for (unsigned i = 0; i < num_eps(dir == DIR_OUT); i++) | |
- { | |
- int ep = ((dir == DIR_IN) ? in_ep_list : out_ep_list)[i]; | |
- struct ep_type *endpoint = &endpoints[ep][out]; | |
- endpoint->active = false; | |
- endpoint->busy = false; | |
- endpoint->status = -1; | |
- endpoint->done = true; | |
- semaphore_release(&endpoint->complete); | |
+} | |
- if (i != 0) | |
- DEPCTL(ep, out) = DEPCTL_setd0pid; | |
- } | |
- DEPCTL(0, out) = /*(DEPCTL_MPS_64 << DEPCTL_mps_bitp) | */ DEPCTL_usbactep; | |
- } | |
+int usb_drv_recv(int endpoint, void* ptr, int length) | |
+{ | |
+} | |
- /* Setup next chain for IN eps */ | |
- for (unsigned i = 0; i < num_eps(false); i++) | |
- { | |
- int ep = in_ep_list[i]; | |
- int next_ep = in_ep_list[(i + 1) % num_eps(false)]; | |
- DEPCTL(ep, false) |= next_ep << DEPCTL_nextep_bitp; | |
- } | |
+int usb_drv_port_speed(void) | |
+{ | |
- prepare_setup_ep0(); | |
} | |
-static void cancel_all_transfers(bool cancel_ep0) | |
+void usb_drv_set_test_mode(int mode) | |
{ | |
- int flags = disable_irq_save(); | |
- | |
- for (int dir = 0; dir < 2; dir++) | |
- for (unsigned i = !!cancel_ep0; i < num_eps(dir == DIR_OUT); i++) | |
- { | |
- int ep = ((dir == DIR_IN) ? in_ep_list : out_ep_list)[i]; | |
- struct ep_type *endpoint = &endpoints[ep][dir == DIR_OUT]; | |
- endpoint->status = -1; | |
- endpoint->busy = false; | |
- endpoint->done = false; | |
- semaphore_release(&endpoint->complete); | |
- DEPCTL(ep, dir) = (DEPCTL(ep, dir) & ~DEPCTL_usbactep); | |
- } | |
+} | |
- restore_irq(flags); | |
+void usb_attach(void) | |
+{ | |
+ usb_enable(true); // s5l only ? | |
+ /* Nothing to do */ | |
} | |
#if CONFIG_CPU == AS3525v2 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment