Skip to content

Commit

Permalink
usbhs: implement USBHS USB device driver
Browse files Browse the repository at this point in the history
Implement the Embassy USB driver for the USBHS peripheral. The
implementation largely mirrors the OTG_FS driver and has similar
limitation such as non-configurable endpoint buffer sizes, in/out on the
same endpoint index, lack of iso transfer, and untested bulk transfer.

This change requires the latest fixes in ch32-metapac due to incorrect
striding of the endpoint control registers.

Tested on CH32V305 with the following applications:
- USB HID device, for testing non-control endpoints interrupt transfer.
- USB DFU, for testing the control endpoint.

Co-authored-by: Harry Brooke <harry.brooke1@hotmail.co.uk>
Co-authored-by: Codetector <codetector@codetector.org>
Co-authored-by: Dummyc0m <xieyuanchu@gmail.com>
  • Loading branch information
3 people committed Nov 3, 2024
1 parent 29e93f6 commit 47cbc57
Show file tree
Hide file tree
Showing 5 changed files with 833 additions and 0 deletions.
2 changes: 2 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,8 @@ fn main() {
// USB is splitted into multiple impls
(("usbd", "DP"), quote!(crate::usbd::DpPin)),
(("usbd", "DM"), quote!(crate::usbd::DmPin)),
(("usbhs", "DP"), quote!(crate::usbhs::DpPin)),
(("usbhs", "DM"), quote!(crate::usbhs::DmPin)),
// USBPD, handled by usbpd/mod.rs
//(("usbpd", "CC1"), quote!(crate::usbpd::Cc1Pin)),
//(("usbpd", "CC2"), quote!(crate::usbpd::Cc2Pin)),
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ pub mod otg_fs;
#[cfg(usbd)]
pub mod usbd;

#[cfg(usbhs_v3)]
pub mod usbhs;

#[cfg(usbpd)]
pub mod usbpd;

Expand Down
189 changes: 189 additions & 0 deletions src/usbhs/control.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
use core::future::poll_fn;
use core::marker::PhantomData;
use core::task::Poll;

use ch32_metapac::usbhs::vals::{EpRxResponse, EpTog, EpTxResponse, UsbToken};
use embassy_usb_driver::EndpointError;

use super::endpoint::Endpoint;
use super::{Instance, EP_WAKERS};
use crate::usb::InOut;

pub struct ControlPipe<'d, T: Instance> {
_phantom: PhantomData<&'d mut T>,
ep0: Endpoint<'d, T, InOut>,
}

impl<'d, T: Instance> ControlPipe<'d, T> {
pub(crate) fn new(ep0: Endpoint<'d, T, InOut>) -> Self {
Self {
_phantom: PhantomData,
ep0,
}
}
}

impl<'d, T: Instance> embassy_usb_driver::ControlPipe for ControlPipe<'d, T> {
fn max_packet_size(&self) -> usize {
use embassy_usb_driver::Endpoint;

self.ep0.info().max_packet_size as usize
}

async fn setup(&mut self) -> [u8; 8] {
poll_fn(|ctx| {
EP_WAKERS[0].register(ctx.waker());
let r = T::regs();
let d = T::dregs();

let flag = r.int_fg().read();

if flag.setup_act() {
d.ep_rx_ctrl(0).write(|w| w.set_mask_uep_r_res(EpRxResponse::NAK));
d.ep_tx_ctrl(0).write(|w| w.set_mask_uep_t_res(EpTxResponse::NAK));

let mut data: [u8; 8] = [0; 8];
self.ep0.data.buffer.read_volatile(&mut data[..]);
r.int_fg().write(|w| {
w.set_setup_act(true);
w.set_transfer(true);
});
critical_section::with(|_| {
r.int_en().modify(|w| {
w.set_setup_act(true);
w.set_transfer(true);
});
});
Poll::Ready(data)
} else {
Poll::Pending
}
})
.await
}

async fn data_out(&mut self, buf: &mut [u8], first: bool, _last: bool) -> Result<usize, EndpointError> {
let d = T::dregs();

if buf.len() > self.ep0.data.max_packet_size as usize {
return Err(EndpointError::BufferOverflow);
}

if first {
d.ep_rx_ctrl(0).write(|v| {
v.set_mask_uep_r_tog(EpTog::DATA1);
v.set_mask_uep_r_res(EpRxResponse::ACK);
})
} else {
d.ep_rx_ctrl(0).modify(|v| {
v.set_mask_uep_r_res(EpRxResponse::ACK);
v.set_mask_uep_r_tog(if let EpTog::DATA0 = v.mask_uep_r_tog() {
EpTog::DATA1
} else {
EpTog::DATA0
});
})
}

self.ep0.data_out(buf).await
}

async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError> {
let d = T::dregs();
let r = T::regs();

self.ep0.data_in_write_buffer(data)?;

if first {
d.ep_tx_ctrl(0).write(|v| {
v.set_mask_uep_t_res(EpTxResponse::ACK);
v.set_mask_uep_t_tog(EpTog::DATA1);
})
} else {
d.ep_tx_ctrl(0).modify(|v| {
v.set_mask_uep_t_res(EpTxResponse::ACK);
v.set_mask_uep_t_tog(if let EpTog::DATA0 = v.mask_uep_t_tog() {
EpTog::DATA1
} else {
EpTog::DATA0
});
});
}

self.ep0.data_in_transfer().await?;

if last {
// status stage = recieve, DATA1, ack
d.ep_rx_ctrl(0).write(|w| {
w.set_mask_uep_r_tog(EpTog::DATA1);
w.set_mask_uep_r_res(EpRxResponse::ACK);
});
poll_fn(|ctx| {
EP_WAKERS[0].register(ctx.waker());

let transfer = d.int_fg().read().transfer();
let status = d.int_st().read();

if transfer && status.endp() == 0 {
let res = match status.token() {
UsbToken::OUT => {
if r.rx_len().read() != 0 {
error!("Expected 0 len OUT stage, found non-zero len, aborting");
Poll::Ready(Err(EndpointError::BufferOverflow))
} else {
// Set the EP back to NAK so that we are "not ready to recieve"
Poll::Ready(Ok(()))
}
}
token => {
error!("Got {} instead of OUT token", token.to_bits());
Poll::Ready(Err(EndpointError::Disabled))
}
};

d.ep_rx_ctrl(0).write(|v| {
v.set_mask_uep_r_res(EpRxResponse::NAK);
});
d.int_fg().write(|w| w.set_transfer(true));
critical_section::with(|_| r.int_en().modify(|v| v.set_transfer(true)));
res
} else {
Poll::Pending
}
})
.await?;
}
Ok(())
}

async fn accept(&mut self) {
let d = T::dregs();

d.ep_t_len(0).write(|w| w.set_len(0));
d.ep_tx_ctrl(0).modify(|w| {
// status stage starts with DATA1
w.set_mask_uep_t_tog(EpTog::DATA1);
w.set_mask_uep_t_res(EpTxResponse::ACK);
});

unwrap!(self.ep0.data_in_transfer().await);
}

async fn reject(&mut self) {
let d = T::dregs();

d.ep_tx_ctrl(0).modify(|v| {
v.set_mask_uep_t_res(EpTxResponse::STALL);
});
d.ep_rx_ctrl(0).modify(|v| {
v.set_mask_uep_r_res(EpRxResponse::STALL);
});
}

async fn accept_set_address(&mut self, addr: u8) {
let r = T::regs();
trace!("accepting address: {}", addr);
self.accept().await;
r.dev_ad().write(|w| w.set_addr(addr));
}
}
Loading

0 comments on commit 47cbc57

Please sign in to comment.