-
Notifications
You must be signed in to change notification settings - Fork 7.3k
rpi_pico: Added Generic PIO support and a PIO based UART driver #56678
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
rpi_pico: Added Generic PIO support and a PIO based UART driver #56678
Conversation
5ffbe73
to
8c5f9df
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instruction / State machine allocation is performed at driver init. A static allocation scheme was > experimented with in the beginning, but it falls short for devices sharing program space.
I think static allocation seems possible in theory. It would be helpful if you could give me more details about the problem.
* @param dev Pointer to the parent device | ||
* @param cfg Pointer to the IRQ configuration | ||
*/ | ||
void rpi_pico_pio_irq_register(const struct device *dev, struct rpi_pico_pio_irq_cfg *cfg); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there is a need to distribute IRQs.
It's good to treat it as a bus on the devicetree syntax.
But I think PIO is not 'Actual bus', because it is not bus controller.
The protocol driver can use the interrupt defined in the PIO node directly.
Two devices cannot use one PIO simultaneously, and dts statically configure devices, so a particular device occupies PIO. It not cause problem.
IRQ_CONNECT( \
DT_IRQ_BY_IDX(DT_INST_PARENT(idx), 0, irq), \
DT_IRQ_BY_IDX(DT_INST_PARENT(idx), 0, priority), \
irq_handler, NULL, (0)); \
It is easy to handle as it follows standard IRQ practices.
And from other viewpoint, ISR should be better not have intermediate processing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually you can have as many devices as you want (can) on a PIO instance. See below for how I'm adding 4 extra uarts (2 per each PIO instance). In practice, you are only limited by the number of SMs / Instruction memory availability as there's no real inter dependency between SMs.
The 'bus' / 'on-bus' thing is mostly for static checking as you are required to be on a PIO parent device. This could be however implemented with a phandle property for each device, but it makes the DTS and checks more confusing.
The intermediate IRQ routing is done for potential load sharing purposes. You could for example overlay PIO IRQ0 to a higher prio and assign it to a child device that requires that higher prio, or you could have one or more child devices that need separate IRQ lines.
&pio0 {
status = "okay";
pio0_uart0: pio0_uart0 {
compatible = "raspberrypi,pico-pio-uart";
pio,interrupts = <0>;
pio,interrupt-names = "shared";
pinctrl-0 = <&pio0_uart0_default>;
pinctrl-names = "default";
current-speed = <115200>;
};
pio0_uart1: pio0_uart1 {
compatible = "raspberrypi,pico-pio-uart";
pio,interrupts = <1>;
pio,interrupt-names = "shared";
pinctrl-0 = <&pio0_uart1_default>;
pinctrl-names = "default";
current-speed = <115200>;
};
};
&pio1 {
status = "okay";
pio1_uart0: pio1_uart0 {
compatible = "raspberrypi,pico-pio-uart";
pio,interrupts = <0>;
pio,interrupt-names = "shared";
pinctrl-0 = <&pio1_uart0_default>;
pinctrl-names = "default";
current-speed = <115200>;
};
pio1_uart1: pio1_uart1 {
compatible = "raspberrypi,pico-pio-uart";
pio,interrupts = <1>;
pio,interrupt-names = "shared";
pinctrl-0 = <&pio1_uart1_default>;
pinctrl-names = "default";
current-speed = <115200>;
};
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the explanation. I understood it.
In this case, since there is only one PIO program, I would expect multiple instances of the exact driver.
If so, I think it would be good to distribute interrupts for each driver. However, in this case, duplicate implementations may increase.
Providing this as a simple framework for client drivers would be nice.
(Simply, I think it is better that driver developers need not care about any special rules.)
#define PIO_ASM_JMP_COND_PIN 6u | ||
#define PIO_ASM_JMP_COND_NOTOSRE 7u | ||
|
||
#define PIO_ASM_JMP(cond, addr, dss) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a much interesting implementation.
It should be useful for manage pio code even if it is back-translated from pioasm output.
Could you please consider the following two points?
- Separating PIO_ASM_... macros to standalone file.
- Adding NOP instructions (I understand it can implements by MOV, but frequently appeared.)
8c5f9df
to
37457df
Compare
It's a bit of a hack, but the idea is as follows: Here is a snippet:
|
832a6fd
to
4fbf811
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The interrupts for PIO should be done as a nested interrupt controller and thus their representation in devicetree would utilize normal interrupt properties.
pinctrl-device.yaml | ||
] | ||
|
||
properties: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uart-controller.yaml now has stop-bits
and data-bits
, so you can remove them here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Open to suggestions, maybe I'm missing some things.
|
bc149e7
to
17f86da
Compare
17f86da
to
8be30c3
Compare
Hey @yonsch @soburi @galak @dcpleung. Can you have another look at this one? I think the major point of debate around this was about interrupt handling. Another topic was the use of the raspberry pi API. The PIO has 6 registers for each SM, and 3 of them (CLKDIV, ADDR, INSTR) are pretty self explanatory, the others (EXECCTRL, SHIFTCTRL, PINCTRL) are pretty well documented in the DS. So it's pretty easy to understand and configure. There are also other issues described in the conversations above. Now, architecturally defining a PIO peripheral might not be that easy, but this is true also by using the SDK. Programs and flows still need to be thought of, and there has to be a pretty good understanding on how the PIO works (the DS is pretty good on this side). Rpi provided examples are a good starting point, but they are pretty limited in scope and most of the times expanding them to a full featured peripheral requires a major rewrite. If anyone has any questions/suggestions, please let me know. P.S. The proposed API (including the UART drivers) was pretty well tested for some time in a limited production environment. No obvious issues till now. Also, I've used this api for another PIO based driver (JTAG/SWD) with quite interesting results. Ionut |
IMO, It is good to be prudent about adding peripheral-specific API. |
@yonsch Could you tell us your opinion, please? |
@soburi |
Follows a 'bus' / 'on-bus' approach. The PIO device grants access to program instructions, state machines and shared interrupt lines. Each child device will use these to implement a specific device function. Pin numbers are derrived from the pinctrl properties, but the specifics are left to the device implementing the function. Implementation does not involve any external tool (such as pioasm). The HW usage is up to the child device, (some helper functions are provided). This makes more sense than using the sdk provided by raspberrypi. In reality, the PIO (aside from program design), is not that complicated at the register level. This flexibility allows for different design approaches (such as runtime program morphing and patching). Signed-off-by: TOKITA Hiroshi <[email protected]> Signed-off-by: Yonatan Schachter <[email protected]> Signed-off-by: Ionut Catalin Pavel <[email protected]>
Added a full(ish) PIO based uart driver. Supports [5, 6, 7, 8]N[0.5, 1, 1.5, 2] modes. Experiments were made with adding parity support on the TX side, and wide (9 bit) data support. However I don't think these are really usefull ATM. Supports interrupt driven mode and polled mode. Can be used as a shell uart. Uses 2 state machines and < 16 instruction, therefore, 4 extra uarts could be added to the pico. Signed-off-by: TOKITA Hiroshi <[email protected]> Signed-off-by: Yonatan Schachter <[email protected]> Signed-off-by: Ionut Catalin Pavel <[email protected]>
Simple example showing how to use the PIO based uart as a console / log backend. Signed-off-by: TOKITA Hiroshi <[email protected]> Signed-off-by: Yonatan Schachter <[email protected]> Signed-off-by: Ionut Catalin Pavel <[email protected]>
293f573
to
1c399a5
Compare
@yonsch Could you tell us your opinion, please? |
Another idea is overwriting the "compatible" line by overlay file to replace the PIO driver completely. |
@soburi @ALL Most likely, to make this PR pass all checks again and maintain the SPI driver, me (or someone else) needs to recreate the driver using the framework in this PR (either by a new commit on this PR or chaining another PR starting from this one as base and temporary disabling the existing SPI driver). It's not a massive amount of work, but it's work nonetheless. So, I would appreciate some feedback on this PR to see where it's going. As more PIO based drivers will be created this will become harder to keep up to date. |
@iocapa @soburi
Many of which are a topic for a discussion. Reimplementing the PIO device without pico-sdkThis is a dilemma we're running into with most of the pico drivers. pico-sdk sometimes does pretty annoying things internally, which forces us to reimplement low level code. I generally think we should start migrating away from the pico-sdk code, but this is a big decision and requires a more thorough discussion. Adding an "assembler" implemented with macrosThis is a cool idea, but I wonder why we'd prefer it over using pioasm. I know that currently it's not available, but supposedly we'll one day have it as an installable package (raspberrypi/pico-sdk#1345), which we could then use to assemble the programs on every build. It might require some more work around the build system, but that's less work than maintaining an assembler. Just think of debugging a PIO program and discovering that it's a "compiler" bug... Adding support for interruptsThis should be very carefully planned, both on the PIO side and core side. I really should dive deeper into this before commenting further. 4, 5 and 6Sounds good overall Renaming stuff (e.g. rpi pico pio to pio rpi pico)Not sure if this was intentional or just an artifact. If it's intentional, and you have good reasoning to change a name, we can probably still change it. If it's just a matter of taste, I prefer to leave it as is. |
@yonsch
At least on the PIO side, the sdk is pretty messy. Sure it's nice to read a few examples and make a really limited implementation for a peripheral. This falls apart pretty fast when you get into the more advanced PIO usage scenarios (interrupts, DMA, dynamic patching, execution from FIFO streams, dynamic reconfiguration, etc...).
The macros are not meant to replace pioasm, they're just to avoid random hardcoded hex numbers in source files. I have no strong feeling on any approach. The idea was to make it clear (and easily changeable) without the need to propagate *.pio files or the assembler. For example, there are some cases when you need to inject PIO instruction through the FIFOs, and this:
There are quite a lot of comments in this PR that explain why this cannot be avoided if maximum PIO flexibility is desired.
Files were not renamed from the names you had in your original PR (only initially, renaming was reverted quite some time ago). |
This pull request has been marked as stale because it has been open (more than) 60 days with no activity. Remove the stale label or add a comment saying that you would like to have the label removed otherwise this pull request will automatically be closed in 14 days. Note, that you can always re-open a closed pull request at any time. |
This pull request has been marked as stale because it has been open (more than) 60 days with no activity. Remove the stale label or add a comment saying that you would like to have the label removed otherwise this pull request will automatically be closed in 14 days. Note, that you can always re-open a closed pull request at any time. |
This pull request has been marked as stale because it has been open (more than) 60 days with no activity. Remove the stale label or add a comment saying that you would like to have the label removed otherwise this pull request will automatically be closed in 14 days. Note, that you can always re-open a closed pull request at any time. |
This pull request has been marked as stale because it has been open (more than) 60 days with no activity. Remove the stale label or add a comment saying that you would like to have the label removed otherwise this pull request will automatically be closed in 14 days. Note, that you can always re-open a closed pull request at any time. |
Needed some extra uarts some time ago (interrupt capable).
Knew that #44316 existed, but I needed something fast and decided to roll my own.
@yonsch and everyone else.
Feedback is appreciated.
Cleaned the code up a bit and opened this PR, as it was sitting on my drive for some time.
Leaving this here, maybe someone could use it in their own project or maybe it would be helpful in current/future PIO implementations.
Some technical details:
Uses the existing
bus
/on-bus
pattern. The PIO is the bus and manages resources / interrupts. The child devices implement the functionality.Does not use the
pico sdk
orpioasm
. This was intended, as the PIO is not really a complicated piece of hardware at the register level (program design is so-so). The programs are crafted by the peripheral implementing the function using whatever is fit for that implementation (there are some helper macros provided). This allows for more flexibility, for example, in the case of UART, a patch operation (dynamically changing instructions during runtime) is desired to change the number of stop bits.Instruction / State machine allocation is performed at driver
init
. A static allocation scheme was experimented with in the beginning, but it falls short for devices sharing program space.The rule on usage depends on the driver. The idea is that the child device should take care not to access anything (SMs / instructions / registers) not specifically allocated to it, including IRQ flags. For example, a driver using only SM0 should only use IRQ flags 0 and 4, a driver using all 4 SMs could use all flags.
Uart:
Not fully tested in all modes (data/stop lengths) but looks like it works as it should for shell / logging operations.
Experiments were done with adding TX parity suport / wide (9 bit) support, but I don't think it's useful ATM.
Pretty sure I omitted some technical details, but if anyone has any question, feel free to ask.