summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-06-04 15:31:45 +0700
committerhathach <[email protected]>2026-06-04 15:31:45 +0700
commit0393a5eaa1fc9ef33cbfd847c924cb83d82696f3 (patch)
treeb96374c05142757820304e2d89d6f260b24f1354
parent232315d6909d9ca7948c830a5e43ad35189da57f (diff)
parentac32feafeb2fb0f6cc5c3010d32db319ff15ca64 (diff)
Merge branch 'master' into dwc2_postfix
-rw-r--r--.github/labeler.yml77
-rw-r--r--.github/workflows/labeler.yml159
-rw-r--r--.github/workflows/sponsor-triage.yml165
-rw-r--r--hw/bsp/family_support.cmake36
-rw-r--r--hw/bsp/stm32f7/boards/stm32f769disco/board.cmake1
-rw-r--r--src/class/mtp/mtp_device.c11
-rw-r--r--src/portable/synopsys/dwc2/dcd_dwc2.c60
-rw-r--r--test/hil/tinyusb.json42
8 files changed, 465 insertions, 86 deletions
diff --git a/.github/labeler.yml b/.github/labeler.yml
new file mode 100644
index 000000000..6c7aa7e7d
--- /dev/null
+++ b/.github/labeler.yml
@@ -0,0 +1,77 @@
+# Path-based auto-labeling for USB IP / port drivers.
+# Maps changed dcd/hcd files under src/portable/ to their "Port <ip>" label.
+# Consumed by actions/labeler (see .github/workflows/labeler.yml -> label-port job).
+
+"Port DWC2":
+ - changed-files:
+ - any-glob-to-any-file: 'src/portable/synopsys/dwc2/**'
+
+"Port EHCI":
+ - changed-files:
+ - any-glob-to-any-file: 'src/portable/ehci/**'
+
+"Port OHCI":
+ - changed-files:
+ - any-glob-to-any-file: 'src/portable/ohci/**'
+
+"Port FSDev":
+ - changed-files:
+ - any-glob-to-any-file: 'src/portable/st/stm32_fsdev/**'
+
+"Port ChipIdea":
+ - changed-files:
+ - any-glob-to-any-file: 'src/portable/chipidea/**'
+
+"Port NXP IP3511":
+ - changed-files:
+ - any-glob-to-any-file: 'src/portable/nxp/lpc_ip3511/**'
+
+"Port NXP IP3516":
+ - changed-files:
+ - any-glob-to-any-file: 'src/portable/nxp/lpc_ip3516/**'
+
+"Port MUSB":
+ - changed-files:
+ - any-glob-to-any-file:
+ - 'src/portable/mentor/musb/**'
+ - 'src/portable/sunxi/**'
+
+"Port RUSB2":
+ - changed-files:
+ - any-glob-to-any-file: 'src/portable/renesas/rusb2/**'
+
+"Port WCH USBFS":
+ - changed-files:
+ - any-glob-to-any-file: 'src/portable/wch/*usbfs*'
+
+"Port WCH USBHS":
+ - changed-files:
+ - any-glob-to-any-file: 'src/portable/wch/*usbhs*'
+
+"Port MAX3421":
+ - changed-files:
+ - any-glob-to-any-file: 'src/portable/analog/max3421/**'
+
+"Port SAMD":
+ - changed-files:
+ - any-glob-to-any-file: 'src/portable/microchip/samd/**'
+
+"Port SAMG":
+ - changed-files:
+ - any-glob-to-any-file: 'src/portable/microchip/samg/**'
+
+"Port nRF":
+ - changed-files:
+ - any-glob-to-any-file: 'src/portable/nordic/nrf5x/**'
+
+"Port Nuvoton":
+ - changed-files:
+ - any-glob-to-any-file: 'src/portable/nuvoton/**'
+
+"Port RP2":
+ - changed-files:
+ - any-glob-to-any-file: 'src/portable/raspberrypi/**'
+
+"Port MSP430":
+ - changed-files:
+ - any-glob-to-any-file: 'src/portable/ti/msp430x5xx/**'
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
index c3cc59d0d..fe09413f1 100644
--- a/.github/workflows/labeler.yml
+++ b/.github/workflows/labeler.yml
@@ -4,23 +4,29 @@ on:
issues:
types: [opened]
pull_request_target:
- types: [opened]
+ types: [opened, synchronize, reopened]
+ discussion:
+ types: [created]
jobs:
label-priority:
+ # Author-based priority labels: only on issue/PR/discussion creation, not on PR updates.
+ if: github.event_name != 'pull_request_target' || github.event.action == 'opened'
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
+ discussions: write
steps:
- - name: Label New Issue or PR
- uses: actions/github-script@v7
+ - name: Label New Issue, PR or Discussion
+ uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- let label = '';
+ let labels = [];
let username = '';
let issueOrPrNumber = 0;
+ let discussionNodeId = '';
if (context.eventName === 'issues') {
username = context.payload.issue.user.login;
@@ -28,25 +34,85 @@ jobs:
} else if (context.eventName === 'pull_request_target') {
username = context.payload.pull_request.user.login;
issueOrPrNumber = context.payload.pull_request.number;
+ } else if (context.eventName === 'discussion') {
+ username = context.payload.discussion.user.login;
+ discussionNodeId = context.payload.discussion.node_id;
}
- // Check if an Adafruit member
- try {
- const adafruitResponse = await github.rest.orgs.checkMembershipForUser({
- org: 'adafruit',
- username: username
- });
+ // Maintainer is an Adafruit member; skip the Adafruit perks for their own
+ // issues/PRs and treat them as a plain contributor (Prio only).
+ const isOwner = username.toLowerCase() === 'hathach';
- if (adafruitResponse.status === 204) {
- console.log('Adafruit Member');
- label = 'Prio Urgent';
+ // Check if an Adafruit member: Adafruit + Sponsor + top priority
+ if (!isOwner) {
+ try {
+ const adafruitResponse = await github.rest.orgs.checkMembershipForUser({
+ org: 'adafruit',
+ username: username
+ });
+
+ if (adafruitResponse.status === 204) {
+ console.log('Adafruit Member');
+ labels = ['Adafruit 🌸', 'Sponsor 💖', 'Prio Top 🚨'];
+ }
+ } catch (error) {
+ console.log('Not an Adafruit member');
}
- } catch (error) {
- console.log('Not an Adafruit member');
}
- // Check if a contributor
- if (label == '') {
+ // Check if a public GitHub Sponsor of the repo owner.
+ // Word ($32) tier and up get triage priority; DWORD/QWORD ($128+) go to the top.
+ // Private sponsorships are not visible to GITHUB_TOKEN, so only public sponsors are detected.
+ if (labels.length === 0) {
+ try {
+ const result = await github.graphql(`
+ query($sponsorable: String!, $sponsor: String!) {
+ user(login: $sponsorable) {
+ isSponsoredBy(accountLogin: $sponsor)
+ sponsorshipsAsMaintainer(includePrivate: false, first: 100) {
+ nodes {
+ sponsorEntity {
+ ... on User { login }
+ ... on Organization { login }
+ }
+ tier { monthlyPriceInDollars }
+ }
+ }
+ }
+ }`, { sponsorable: context.repo.owner, sponsor: username });
+
+ const owner = result.user;
+ if (owner && owner.isSponsoredBy) {
+ let monthly = 0;
+ const nodes = (owner.sponsorshipsAsMaintainer && owner.sponsorshipsAsMaintainer.nodes) || [];
+ for (const node of nodes) {
+ const login = node.sponsorEntity && node.sponsorEntity.login;
+ if (login && login.toLowerCase() === username.toLowerCase()) {
+ monthly = (node.tier && node.tier.monthlyPriceInDollars) || 0;
+ break;
+ }
+ }
+
+ if (monthly >= 128) {
+ console.log('Sponsor (DWORD/QWORD tier)');
+ labels = ['Sponsor 💖', 'Prio Top 🚨'];
+ } else if (monthly >= 32) {
+ console.log('Sponsor (Word tier)');
+ labels = ['Sponsor 💖', 'Prio 📌'];
+ } else {
+ console.log('Sponsor (below Word tier or tier not visible)');
+ labels = ['Sponsor 💖'];
+ }
+ } else {
+ console.log('Not a public sponsor');
+ }
+ } catch (error) {
+ console.log('Sponsor lookup failed: ' + error.message);
+ }
+ }
+
+ // Check if a contributor: prioritized in triage queue
+ if (labels.length === 0) {
try {
const collaboratorResponse = await github.rest.repos.checkCollaborator({
owner: context.repo.owner,
@@ -56,18 +122,61 @@ jobs:
if (collaboratorResponse.status === 204) {
console.log('Contributor');
- label = 'Prio Higher';
+ labels = ['Prio 📌'];
}
} catch (error) {
console.log('Not a contributor');
}
}
- if (label !== '') {
- await github.rest.issues.addLabels({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: issueOrPrNumber,
- labels: [label]
- });
+ if (labels.length !== 0) {
+ if (context.eventName === 'discussion') {
+ // Discussions are not covered by the REST issues API; resolve the label
+ // names to node IDs and attach them with the GraphQL labelable mutation.
+ const labelIds = [];
+ for (const name of labels) {
+ const res = await github.graphql(`
+ query($owner: String!, $repo: String!, $name: String!) {
+ repository(owner: $owner, name: $repo) {
+ label(name: $name) { id }
+ }
+ }`, { owner: context.repo.owner, repo: context.repo.repo, name: name });
+ if (res.repository.label) {
+ labelIds.push(res.repository.label.id);
+ }
+ }
+ if (labelIds.length !== 0) {
+ await github.graphql(`
+ mutation($labelableId: ID!, $labelIds: [ID!]!) {
+ addLabelsToLabelable(input: { labelableId: $labelableId, labelIds: $labelIds }) {
+ clientMutationId
+ }
+ }`, { labelableId: discussionNodeId, labelIds: labelIds });
+ }
+ } else {
+ await github.rest.issues.addLabels({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issueOrPrNumber,
+ labels: labels
+ });
+ }
}
+
+ # Path-based Port labels: attach "Port <ip>" when a PR touches the matching
+ # dcd/hcd driver under src/portable/. Mapping lives in .github/labeler.yml.
+ label-port:
+ if: github.event_name == 'pull_request_target'
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: write
+ issues: write # allow auto-creating a Port label that doesn't exist yet
+ steps:
+ - uses: actions/labeler@v5
+ with:
+ configuration-path: .github/labeler.yml
+ # sync-labels so a Port label is removed once a PR no longer touches
+ # that driver (job reruns on synchronize). Only labels listed in
+ # labeler.yml are managed, so author-based Prio/Sponsor labels are untouched.
+ sync-labels: true
diff --git a/.github/workflows/sponsor-triage.yml b/.github/workflows/sponsor-triage.yml
new file mode 100644
index 000000000..48b2ac7cc
--- /dev/null
+++ b/.github/workflows/sponsor-triage.yml
@@ -0,0 +1,165 @@
+name: Sponsor Triage
+
+# Periodically add open issues and PRs opened by sponsors (public and private) to the private "Sponsor Triage" project board
+# Requires a PAT in secret SPONSOR_TOKEN with scopes:
+# - project (write project items / fields)
+# - read:org (search org issues, check Adafruit membership)
+# - read:user / sponsors (read own sponsorships incl. private)
+
+on:
+ schedule:
+ - cron: '0 */6 * * *' # every 6 hours
+ workflow_dispatch:
+
+concurrency:
+ group: sponsor-triage
+ cancel-in-progress: false
+
+jobs:
+ sync:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Sync sponsor issues to project board
+ uses: actions/github-script@v8
+ with:
+ github-token: ${{ secrets.SPONSOR_TOKEN }}
+ script: |
+ const PROJECT_OWNER = 'hathach';
+ const PROJECT_NUMBER = 3;
+ // Where to look for sponsor-authored open issues.
+ const SEARCH_SCOPES = ['org:adafruit', 'user:hathach'];
+
+ const tierFromMonthly = (m) => {
+ if (m >= 512) return 'QWORD';
+ if (m >= 128) return 'DWORD';
+ if (m >= 32) return 'Word';
+ if (m >= 8) return 'Byte';
+ if (m >= 2) return 'Bit';
+ return null;
+ };
+
+ // --- 1. Resolve project id + field/option ids (by name, never hardcoded) ---
+ const proj = await github.graphql(`
+ query($owner: String!, $number: Int!) {
+ user(login: $owner) {
+ projectV2(number: $number) {
+ id
+ fields(first: 50) {
+ nodes {
+ ... on ProjectV2SingleSelectField {
+ id name options { id name }
+ }
+ }
+ }
+ }
+ }
+ }`, { owner: PROJECT_OWNER, number: PROJECT_NUMBER });
+
+ const project = proj.user.projectV2;
+ const fieldByName = {};
+ for (const f of project.fields.nodes) {
+ if (f && f.name) {
+ fieldByName[f.name] = { id: f.id, options: {} };
+ for (const o of (f.options || [])) fieldByName[f.name].options[o.name] = o.id;
+ }
+ }
+ const tierField = fieldByName['Tier'];
+ const visField = fieldByName['Visibility'];
+
+ // --- 2. Build sponsor map: loginLower -> { tier, visibility } ---
+ const sponsors = new Map();
+
+ // 2a. GitHub Sponsors of the owner, including private ones.
+ let after = null;
+ for (let page = 0; page < 20; page++) {
+ const res = await github.graphql(`
+ query($owner: String!, $after: String) {
+ user(login: $owner) {
+ sponsorshipsAsMaintainer(includePrivate: true, first: 100, after: $after) {
+ pageInfo { hasNextPage endCursor }
+ nodes {
+ privacyLevel
+ tier { monthlyPriceInDollars }
+ sponsorEntity {
+ ... on User { login }
+ ... on Organization { login }
+ }
+ }
+ }
+ }
+ }`, { owner: PROJECT_OWNER, after });
+ const conn = res.user.sponsorshipsAsMaintainer;
+ for (const n of conn.nodes) {
+ const login = n.sponsorEntity && n.sponsorEntity.login;
+ if (!login) continue;
+ const tier = tierFromMonthly((n.tier && n.tier.monthlyPriceInDollars) || 0);
+ const visibility = n.privacyLevel === 'PRIVATE' ? 'Private' : 'Public';
+ sponsors.set(login.toLowerCase(), { login, tier, visibility });
+ }
+ if (!conn.pageInfo.hasNextPage) break;
+ after = conn.pageInfo.endCursor;
+ }
+
+ core.info(`Resolved ${sponsors.size} sponsor account(s).`);
+ if (sponsors.size === 0) return;
+
+ // 2b. Adafruit org members get the Adafruit tier regardless of $ amount.
+ for (const s of sponsors.values()) {
+ try {
+ const m = await github.rest.orgs.checkMembershipForUser({ org: 'adafruit', username: s.login });
+ if (m.status === 204) s.tier = 'Adafruit';
+ } catch (e) { /* not an Adafruit member */ }
+ }
+
+ // --- 3. Find each sponsor's open issues and PRs in the search scopes ---
+ // (search type ISSUE returns both issues and pull requests)
+ const found = new Map(); // contentId -> { tier, visibility }
+ for (const s of sponsors.values()) {
+ for (const scope of SEARCH_SCOPES) {
+ const q = `${scope} is:open author:${s.login}`;
+ try {
+ const res = await github.graphql(`
+ query($q: String!) {
+ search(query: $q, type: ISSUE, first: 100) {
+ nodes { ... on Issue { id } ... on PullRequest { id } }
+ }
+ }`, { q });
+ for (const node of res.search.nodes) {
+ if (node && node.id && !found.has(node.id)) {
+ found.set(node.id, { tier: s.tier, visibility: s.visibility });
+ }
+ }
+ } catch (e) {
+ core.warning(`Search failed for one scope: ${e.message}`);
+ }
+ }
+ }
+ core.info(`Found ${found.size} open sponsor issue(s) across scopes.`);
+
+ // --- 4. Add to board + set Tier / Visibility (idempotent) ---
+ const setField = async (itemId, field, optionName) => {
+ if (!field || !optionName) return;
+ const optId = field.options[optionName];
+ if (!optId) return;
+ await github.graphql(`
+ mutation($p: ID!, $i: ID!, $f: ID!, $o: String!) {
+ updateProjectV2ItemFieldValue(input: {
+ projectId: $p, itemId: $i, fieldId: $f, value: { singleSelectOptionId: $o }
+ }) { projectV2Item { id } }
+ }`, { p: project.id, i: itemId, f: field.id, o: optId });
+ };
+
+ let added = 0;
+ for (const [contentId, meta] of found) {
+ const res = await github.graphql(`
+ mutation($p: ID!, $c: ID!) {
+ addProjectV2ItemById(input: { projectId: $p, contentId: $c }) {
+ item { id }
+ }
+ }`, { p: project.id, c: contentId });
+ const itemId = res.addProjectV2ItemById.item.id;
+ await setField(itemId, tierField, meta.tier);
+ await setField(itemId, visField, meta.visibility);
+ added++;
+ }
+ core.info(`Synced ${added} item(s) to the board.`);
diff --git a/hw/bsp/family_support.cmake b/hw/bsp/family_support.cmake
index 2468ac43c..07d693d77 100644
--- a/hw/bsp/family_support.cmake
+++ b/hw/bsp/family_support.cmake
@@ -244,7 +244,7 @@ function(family_add_bloaty TARGET)
COMMAND ${BLOATY_EXE} ${OPTION_LIST} $<TARGET_FILE:${TARGET}>
VERBATIM)
- set_property(TARGET ${TARGET}-bloaty PROPERTY FOLDER ${TARGET}-group)
+ #set_property(TARGET ${TARGET}-bloaty PROPERTY FOLDER ${TARGET}-group)
# post build
# add_custom_command(TARGET ${TARGET} POST_BUILD
# COMMAND ${BLOATY_EXE} --csv ${OPTION_LIST} $<TARGET_FILE:${TARGET}> > ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_bloaty.csv
@@ -265,7 +265,7 @@ function(family_add_linkermap TARGET)
VERBATIM
)
- set_property(TARGET ${TARGET}-linkermap PROPERTY FOLDER ${TARGET}-group)
+ #set_property(TARGET ${TARGET}-linkermap PROPERTY FOLDER ${TARGET}-group)
# post build
add_custom_command(TARGET ${TARGET} POST_BUILD
@@ -347,7 +347,7 @@ echo \"$MEMBROWSE_CMD\"")
COMMAND ${CMAKE_COMMAND} -E env MEMBROWSE_UPLOAD=0 bash -lc "${MEMBROWSE_PREPARE_CMD}; eval \"$MEMBROWSE_CMD\""
VERBATIM
)
- set_property(TARGET ${TARGET}-membrowse PROPERTY FOLDER ${TARGET}-group)
+ #set_property(TARGET ${TARGET}-membrowse PROPERTY FOLDER ${TARGET}-group)
add_custom_target(${TARGET}-membrowse-upload
COMMAND ${CMAKE_COMMAND} -E env MEMBROWSE_UPLOAD=1 bash -lc "${MEMBROWSE_PREPARE_CMD}; eval \"$MEMBROWSE_CMD\""
@@ -359,7 +359,7 @@ echo \"$MEMBROWSE_CMD\"")
endif ()
add_dependencies(examples-membrowse-upload ${TARGET}-membrowse-upload)
- set_property(TARGET ${TARGET}-membrowse-upload PROPERTY FOLDER ${TARGET}-group)
+ #set_property(TARGET ${TARGET}-membrowse-upload PROPERTY FOLDER ${TARGET}-group)
endif ()
endfunction()
@@ -648,7 +648,7 @@ exit"
VERBATIM
)
- set_property(TARGET ${NAME_TARGET}-jlink PROPERTY FOLDER ${TARGET}-group)
+# set_property(TARGET ${NAME_TARGET}-jlink PROPERTY FOLDER ${NAME_TARGET}-group)
endfunction()
@@ -663,7 +663,7 @@ function(family_flash_stlink TARGET)
COMMAND ${STM32_PROGRAMMER_CLI} --connect port=swd --write $<TARGET_FILE:${TARGET}> --go
)
- set_property(TARGET ${TARGET}-stlink PROPERTY FOLDER ${TARGET}-group)
+ #set_property(TARGET ${TARGET}-stlink PROPERTY FOLDER ${TARGET}-group)
endfunction()
@@ -678,7 +678,7 @@ function(family_flash_stflash TARGET)
COMMAND ${ST_FLASH} write $<TARGET_FILE_DIR:${TARGET}>/${TARGET}.bin 0x8000000
)
- set_property(TARGET ${TARGET}-stflash PROPERTY FOLDER ${TARGET}-group)
+ #set_property(TARGET ${TARGET}-stflash PROPERTY FOLDER ${TARGET}-group)
endfunction()
@@ -706,7 +706,7 @@ function(family_flash_openocd TARGET)
VERBATIM
)
- set_property(TARGET ${TARGET}-openocd PROPERTY FOLDER ${TARGET}-group)
+ #set_property(TARGET ${TARGET}-openocd PROPERTY FOLDER ${TARGET}-group)
endfunction()
@@ -769,7 +769,7 @@ function(family_flash_wlink_rs TARGET)
COMMAND ${WLINK_RS} flash $<TARGET_FILE:${TARGET}>
)
- set_property(TARGET ${TARGET}-wlink-rs PROPERTY FOLDER ${TARGET}-group)
+ #set_property(TARGET ${TARGET}-wlink-rs PROPERTY FOLDER ${TARGET}-group)
endfunction()
@@ -784,7 +784,7 @@ function(family_flash_pyocd TARGET)
COMMAND ${PYOCD} flash -t ${PYOCD_TARGET} $<TARGET_FILE:${TARGET}>
)
- set_property(TARGET ${TARGET}-pyocd PROPERTY FOLDER ${TARGET}-group)
+ #set_property(TARGET ${TARGET}-pyocd PROPERTY FOLDER ${TARGET}-group)
endfunction()
@@ -794,7 +794,7 @@ function(family_flash_uf2 TARGET FAMILY_ID)
DEPENDS ${TARGET}
COMMAND python ${UF2CONV_PY} -f ${FAMILY_ID} --deploy $<TARGET_FILE_DIR:${TARGET}>/${TARGET}.uf2
)
- set_property(TARGET ${TARGET}-uf2 PROPERTY FOLDER ${TARGET}-group)
+ #set_property(TARGET ${TARGET}-uf2 PROPERTY FOLDER ${TARGET}-group)
endfunction()
@@ -810,7 +810,7 @@ function(family_flash_teensy TARGET)
COMMAND ${TEENSY_CLI} --mcu=${TEENSY_MCU} -w -s $<TARGET_FILE_DIR:${TARGET}>/${TARGET}.hex
)
- set_property(TARGET ${TARGET}-teensy PROPERTY FOLDER ${TARGET}-group)
+ #set_property(TARGET ${TARGET}-teensy PROPERTY FOLDER ${TARGET}-group)
endfunction()
@@ -830,7 +830,7 @@ function(family_flash_nxplink TARGET)
COMMAND ${LINKSERVER_PATH} flash ${NXPLINK_DEVICE} load $<TARGET_FILE:${TARGET}>
)
- set_property(TARGET ${TARGET}-nxplink PROPERTY FOLDER ${TARGET}-group)
+ #set_property(TARGET ${TARGET}-nxplink PROPERTY FOLDER ${TARGET}-group)
endfunction()
@@ -845,7 +845,7 @@ function(family_flash_dfu_util TARGET OPTION)
VERBATIM
)
- set_property(TARGET ${TARGET}-dfu-util PROPERTY FOLDER ${TARGET}-group)
+ #set_property(TARGET ${TARGET}-dfu-util PROPERTY FOLDER ${TARGET}-group)
endfunction()
function(family_flash_msp430flasher TARGET)
@@ -862,7 +862,7 @@ function(family_flash_msp430flasher TARGET)
${MSP430FLASHER} -w $<TARGET_FILE_DIR:${TARGET}>/${TARGET}.hex -z [VCC]
)
- set_property(TARGET ${TARGET}-msp430flasher PROPERTY FOLDER ${TARGET}-group)
+ #set_property(TARGET ${TARGET}-msp430flasher PROPERTY FOLDER ${TARGET}-group)
endfunction()
function(family_flash_rfp TARGET)
@@ -880,7 +880,7 @@ function(family_flash_rfp TARGET)
VERBATIM
)
- set_property(TARGET ${TARGET}-rfp PROPERTY FOLDER ${TARGET}-group)
+ #set_property(TARGET ${TARGET}-rfp PROPERTY FOLDER ${TARGET}-group)
endfunction()
@@ -897,7 +897,7 @@ function(family_flash_uniflash TARGET)
VERBATIM
)
- set_property(TARGET ${TARGET}-uniflash PROPERTY FOLDER ${TARGET}-group)
+ #set_property(TARGET ${TARGET}-uniflash PROPERTY FOLDER ${TARGET}-group)
endfunction()
# Add flash ft9xx target need to remove kernal's ftdi_sio and bind D2XX drivers
@@ -912,7 +912,7 @@ function(family_flash_ft9xx TARGET)
COMMAND ${FT9XXPROG} -f $<TARGET_FILE_DIR:${TARGET}>/${TARGET}.bin
)
- set_property(TARGET ${TARGET}-ft9xx PROPERTY FOLDER ${TARGET}-group)
+ #set_property(TARGET ${TARGET}-ft9xx PROPERTY FOLDER ${TARGET}-group)
endfunction()
#----------------------------------
diff --git a/hw/bsp/stm32f7/boards/stm32f769disco/board.cmake b/hw/bsp/stm32f7/boards/stm32f769disco/board.cmake
index 2335b869e..dbdd07e4d 100644
--- a/hw/bsp/stm32f7/boards/stm32f769disco/board.cmake
+++ b/hw/bsp/stm32f7/boards/stm32f769disco/board.cmake
@@ -1,5 +1,6 @@
set(MCU_VARIANT stm32f769xx)
set(JLINK_DEVICE stm32f769ni)
+#set(JLINK_OPTION "-USB 000778170924")
set(LD_FILE_GNU ${CMAKE_CURRENT_LIST_DIR}/STM32F769ZITx_FLASH.ld)
diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c
index fd06b4601..1f76dfcc7 100644
--- a/src/class/mtp/mtp_device.c
+++ b/src/class/mtp/mtp_device.c
@@ -443,15 +443,12 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
// Check completion for IN and OUT separately
bool is_complete;
-
- if (is_data_in)
- {
+ if (is_data_in) {
// IN completion: short packet, ZLP, or reaching total_len
is_complete = (xferred_bytes == 0 || xferred_bytes < threshold || p_mtp->xferred_len >= p_mtp->total_len);
- }
- else
- {
- // OUT completion: reaching total_len or ZLP
+ } else {
+ // OUT completion: reaching total_len or ZLP only. A short packet does NOT end the phase
+ // (an early short packet before total_len is the cancel case, not normal completion).
is_complete = (p_mtp->xferred_len >= p_mtp->total_len) || ((xferred_bytes == 0 && p_mtp->xferred_len > 0));
}
diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c
index e6d7dc08e..c7899a354 100644
--- a/src/portable/synopsys/dwc2/dcd_dwc2.c
+++ b/src/portable/synopsys/dwc2/dcd_dwc2.c
@@ -793,15 +793,15 @@ static void handle_bus_reset(uint8_t rhport) {
xfer_status[0][TUSB_DIR_OUT].max_size = CFG_TUD_ENDPOINT0_SIZE;
xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE;
- uint32_t oepmsk = 0;
+ uint32_t gintmsk = GINTMSK_OTGINT | GINTMSK_IEPINT | GINTMSK_IISOIXFRM;
if(dma_device_enabled(dwc2)) {
- oepmsk = GINTMSK_OEPINT;
+ gintmsk |= GINTMSK_OEPINT;
dma_setup_prepare(rhport);
} else {
dwc2->epout[0].doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos);
}
- dwc2->gintmsk |= GINTMSK_OTGINT | oepmsk | GINTMSK_IEPINT | GINTMSK_IISOIXFRM;
+ dwc2->gintmsk |= gintmsk;
}
static void handle_enum_done(uint8_t rhport) {
@@ -885,45 +885,47 @@ static void handle_rxflvl_irq(uint8_t rhport) {
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
const volatile uint32_t* rx_fifo = dwc2->fifo[0];
+ // DWC2 v3.10a (e.g. STM32L476) emits an extra EP0 RX_COMPLETE that is NOT a real OUT data transfer completion, in two
+ // situations - each flagged by a DOEPINT bit set on that word:
+ // - DOEPINT.STPKTRX (Setup Packet Received): pushed between SETUP_RX and SETUP_DONE of every control transfer.
+ // - DOEPINT.STSPHSRX (Status Phase Received for control write): pushed after the OUT data stage when the host
+ // starts the IN status phase.
+ // Both are dropped in the RX_COMPLETE case below, clearing the flag (W1C) so a latched STSPHSRX
+ // does not block the core from emitting the next SETUP_DONE. usbd still processes the real OUT data
+ // and queues the IN status ZLP itself - the core does not auto-complete the control-write status.
+ const bool quirk_v310a = (dwc2->gsnpsid == DWC2_CORE_REV_3_10a);
+
// Pop control word off FIFO
const dwc2_grxstsp_t grxstsp = {.value = dwc2->grxstsp};
+ const uint8_t packet_status = grxstsp.packet_status;
const uint8_t epnum = grxstsp.ep_ch_num;
dwc2_dep_t* epout = &dwc2->epout[epnum];
- switch (grxstsp.packet_status) {
+ switch (packet_status) {
case GRXSTS_PKTSTS_GLOBAL_OUT_NAK:
// Global OUT NAK: do nothing
break;
case GRXSTS_PKTSTS_SETUP_RX: {
// Setup packet received
- uint32_t* setup = (uint32_t*)(uintptr_t) _dcd_usbbuf.setup_buffer;
+ uint32_t * setup = (uint32_t*)(uintptr_t) _dcd_usbbuf.setup_buffer;
// We can receive up to three setup packets in succession, but only the last one is valid.
setup[0] = (*rx_fifo);
setup[1] = (*rx_fifo);
-
- dwc2_dep_t* epin0 = &dwc2->epin[0];
- if (edpt_is_enabled(epin0)) {
- edpt_disable(rhport, 0x80, false);
- }
-
- // (GenID < 3.00a) Must wait SETUP_DONE before next OUT transfer, otherwise OUT data may be corrupted.
- // (GenID >= 3.00a) On the other hand STUPCNT is auto reloaded and SETUP_DONE is only triggered once after bus reset.
- if (dwc2->gsnpsid >= DWC2_CORE_REV_3_00a) {
- dcd_event_setup_received(rhport, _dcd_usbbuf.setup_buffer, true);
- }
break;
}
- case GRXSTS_PKTSTS_SETUP_DONE:
- // Setup packet done:
+ case GRXSTS_PKTSTS_SETUP_DONE: {
+ // Pop this word causes the Setup interrupt
epout->doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos);
-
- if (dwc2->gsnpsid < DWC2_CORE_REV_3_00a) {
- dcd_event_setup_received(rhport, _dcd_usbbuf.setup_buffer, true);
+ epout->doepint = DOEPINT_SETUP | DOEPINT_STPKTRX; // Clear SETUP interrupt, required for core to re-write this control word
+ if (edpt_is_enabled(&dwc2->epin[0])) {
+ edpt_disable(rhport, 0x80, false);
}
+ dcd_event_setup_received(rhport, _dcd_usbbuf.setup_buffer, true);
break;
+ }
case GRXSTS_PKTSTS_RX_DATA: {
// Out packet received
@@ -952,7 +954,19 @@ static void handle_rxflvl_irq(uint8_t rhport) {
}
case GRXSTS_PKTSTS_RX_COMPLETE: {
- // Out packet done
+ // Pop this word causes the xfer complete interrupt
+ const uint32_t doepint = epout->doepint;
+ epout->doepint = DOEPINT_XFRC;
+
+ // v3.10a quirk (see top of function): the extra RX_COMPLETE flagged with Setup Packet Received (STPKTRX) or
+ // Status Phase Received for control write (STSPHSRX) is not a real OUT completion. Drop it
+ if (quirk_v310a) {
+ if (doepint & (DOEPINT_STPKTRX | DOEPINT_STSPHSRX)) {
+ epout->doepint = DOEPINT_STPKTRX | DOEPINT_STSPHSRX;
+ break;
+ }
+ }
+
xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT);
if (epnum == 0 && _dcd_data.ep0_pending[TUSB_DIR_OUT] > 0) {
// EP0 can only handle one packet, schedule another packet to be received.
@@ -1096,6 +1110,8 @@ static void handle_ep_irq(uint8_t rhport, uint8_t dir) {
#if CFG_TUD_DWC2_SLAVE_ENABLE
if (dir == TUSB_DIR_IN) {
handle_epin_slave(rhport, epnum, intr.diepint_bm);
+ } else {
+ // epout is handled in handle_rxflvl_irq
}
#endif
}
diff --git a/test/hil/tinyusb.json b/test/hil/tinyusb.json
index dc28df7b9..467b7378a 100644
--- a/test/hil/tinyusb.json
+++ b/test/hil/tinyusb.json
@@ -344,6 +344,20 @@
"comment": "2x16 access scheme with 1KB USB SRAM"
},
{
+ "name": "stm32f407disco",
+ "uid": "30001A000647313332353735",
+ "tests": {
+ "device": true,
+ "host": false,
+ "dual": false
+ },
+ "flasher": {
+ "name": "jlink",
+ "uid": "000773661813",
+ "args": "-device stm32f407vg"
+ }
+ },
+ {
"name": "stm32f723disco",
"uid": "460029001951373031313335",
"build": {
@@ -413,6 +427,20 @@
"args": "-f interface/stlink.cfg -f target/stm32g0x.cfg"
},
"comment": "32-bit scheme, 2KB USB SRAM"
+ },
+ {
+ "name": "stm32l476disco",
+ "uid": "3C0050001150334258343920",
+ "tests": {
+ "device": true,
+ "host": false,
+ "dual": false
+ },
+ "flasher": {
+ "name": "jlink",
+ "uid": "777632258",
+ "args": "-device STM32L476VG"
+ }
}
],
"boards-skip": [
@@ -449,20 +477,6 @@
"uid": "EBCA8F0670AF",
"args": ""
}
- },
- {
- "name": "stm32f407disco",
- "uid": "30001A000647313332353735",
- "tests": {
- "device": true,
- "host": false,
- "dual": false
- },
- "flasher": {
- "name": "jlink",
- "uid": "000773661813",
- "args": "-device stm32f407vg"
- }
}
]
}