Hi,
I'm trying to use iMX8M Mini SAI1 to output i2s audio to codec. In order to minimize master clock jitter, I want to use an external high quality oscillator to provide master clock to both iMX8 processor and external DAC chip.
In the iMX8M Mini Reference manual, it says that the SAI1_MCLK pin can either be configured as input or output. So, what exactly in the device tree (or what registers) do I have to write in order to configure this pin as input?
So far I have done the following:
1. Set bit 5 of register IOMUXC_GPR_GPR6 (GPR_SAI1_SEL1) to 1 (SAI1_MCLK) in u-boot code
2. Removed the "fsl,sai-mclk-direction-output" property in sai1 device tree
3. Created a fixed-clock node in the device tree, and set the mclk1 clock of sai1 to be that clock.
i2s_mclk: fixed-24576 {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <24576000>;
clock-accuracy = <20000>;
};
&sai1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_sai1>;
status = "okay";
fsl,sai-synchronous-rx;
clocks = <&clk IMX8MM_CLK_SAI1_IPG>,
// MCLK1 is switched to SAI1.MCLK in the IOMUXC_GPR_GPR6 register in uboot code
<&i2s_mclk>,
<&clk IMX8MM_CLK_DUMMY>, <&clk IMX8MM_CLK_DUMMY>;
clock-names = "bus", "mclk1", "mclk2", "mclk3";
fsl,sai-multi-lane;
};
It seems that the sai1_mclk pad is still an output pad, not accepting my external input.
Solved! Go to Solution.
I debugged for a whole day, it turns out that we need to set bit GPR_SAI1_EXT_MCLK_EN of the IOMUXC_GPR_GPR2 register to 1. This register have no description in the reference manual (see screenshot below), but an excel file of iMX8MQ can be found online to describe this register. I hope that you can add a description to the reference manual to avoid confusion.
I debugged for a whole day, it turns out that we need to set bit GPR_SAI1_EXT_MCLK_EN of the IOMUXC_GPR_GPR2 register to 1. This register have no description in the reference manual (see screenshot below), but an excel file of iMX8MQ can be found online to describe this register. I hope that you can add a description to the reference manual to avoid confusion.
Hi @smartdev
Sorry for the confusion, the reference manual does miss some important descriptions. We shall work on the improvement.
Next time when you have similar unclear points, you may check on RM of other platforms, for example 8M nano:
I'm not saying this can always solve any problem, or excusing for the problem in the document. Just want to offer some methods for you to better process your project.
Hi @smartdev
If you did manage to get the MCLK to work as an input could you share how please (i.e a bit more explicitly)?
Thanks.
Hi smartdev,
There are a number of us struggling on this forum to do the same thing, did you manage to run the SAI with and external MCLK? If so, any information would be greatly appreciated!
FYI, our setup is SAI1 in master mode to I2S DAC but with external precision MCLK (that needs to be feed back into MCLK pad same as you)
Thanks.
Hi, I'm working on a similar system, I have an external MCLK and need my SAI peripheral to get this as input and generate BCLK and LRCLK as a master... can you give me any insight if you managed to get your system working?
Hi @Airbus_A320
Not yet. I've been waiting for a SPDIF dev board to arrive to bolt up to our iMX8MM EVK that provides the external MCLK we need, it's here now so it's a matter of finding the time to get back on to it! I've scoured a few forums trying to find out how to do this and I think the trick is as smartdev said the GPR_SAI1_EXT_MCLK_EN of the IOMUXC_GPR_GPR2 needs to be set to 1. Whether this can be done in the SAI setup in the device tree is yet to be determined; I have to crack this problem so I'll let you know. Or if you get there first please pass on the knowhow!
Cheers.
Are you able to find the solution of using the MCLK pad as a master clock input?
Is it required to set both the register OMUXC_GPR_GPR2 (bit:1) and IOMUXC_GPR_GPR6 (bit:5)?
I would appreciate any input you can give me.
Thanks in advance.
Hi @iicsanjeev01,
Yes I did in the end, but it's been a while.
Two IOMUX GRP registers need setting in uboot or early on to use the MCLK pin as an input. Also the SAI MCLK Control Register (MCR) needs to be told to set the MCLK as an input [look in fsl_sai.c].
Try in UBoot:
set GPR_SAI1_EXT_MCLK_EN of the IOMUXC_GPR_GPR2 and
Set bit 5 of register IOMUXC_GPR_GPR6 (GPR_SAI1_SEL1) to 1 (SAI1_MCLK)
mw.l 0x30340008 0x00000001
mw.l 0x30340018 0x00100000
----------------
If your trying to achieve every audio rate (44100, 48000 -> 352K8, 384K) like I was, then you will also need to add some code into the driver to switch between your two MCLK frequencies. You might have to do a few 'work-arounds' if your I2S card is derived off the simple-audio-card like a lot are. Problem is the driver only receives one value for MCLK and everything is divided off that, so when you switch the physical MCLK frequency (to the other) the driver doesn't actually know, so a little 'tweaking' might be necessary. You can worry about all this though once you have got the MCLK working as an input.
The docs are not the easiest to follow when it comes to SAI clocking and tend to change the names in different chapters! One thing though, the SAI still needs a clock or two for internal use, but - what I'll term as the 'final I2S output register' which clocks the I2S pins - is what the MCLK input is connected too (if that makes sense).
Don't forget to modify the SAI your device tree.
Useful -> cat /sys/kernel/debug/clk/clk_summary | grep sai1
Hope this helps!
Thank you so much for your response.
1) dts changes:
sai1_mclk: fixed-24576 {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <24576000>;
clock-accuracy = <20000>;
};
&sai1 {
pinctrl-names = "default", "dsd";
pinctrl-0 = <&pinctrl_sai1>;
pinctrl-1 = <&pinctrl_sai1_dsd>;
clocks = <&clk IMX8MM_CLK_SAI1_IPG>,
// MCLK1 is switched to SAI1.MCLK in the IOMUXC_GPR_GPR6 register in uboot code
<&sai1_mclk>,
<&clk IMX8MM_CLK_DUMMY>, <&clk IMX8MM_CLK_DUMMY>,<&clk IMX8MM_CLK_DUMMY>;
clock-names = "bus", "mclk0", "mclk1", "mclk2", "mclk3";
status = "okay";
};
this is our customized codec and machine driver
audiofpga: audiofpga-titan-cpucodec {
compatible = "fsl,audiofpga-titan-cpucard";
pinctrl-names = "default";
//clocks = <&clk IMX8MM_CLK_SAI1_ROOT>;
clocks = <&sai1_mclk>;
clock-names = "mclk";
status = "okay";
};
sound-audiofpga {
compatible = "fsl,imx-audio-audiofpga-titan-cpucard";
model = "audiofpga-titan-cpucard-audio";
audio-cpu = <&sai1>;
audio-codec = <&audiofpga>;
};
2) I tried adding code changes in the uboot by making changes in ("board/freescale/imx8mm_val/imx8mm_val.c)
static int setup_sai1_mclk(void)
{
struct iomuxc_gpr_base_regs *gpr =
(struct iomuxc_gpr_base_regs *)IOMUXC_GPR_BASE_ADDR;
// GPR2
// GPR_SAI1_EXT_MCLK_EN = 1
setbits_le32(&gpr->gpr[2], 1);
// GPR_SAI1_SEL1 = 1
setbits_le32(&gpr->gpr[6], (1 << 5));
return 0;
}
int board_init(void)
{
setup_sai1_mclk();
}
Now if I read the memory as below
root@device-000000[~]# devmem 0x30340008
0x00000001
root@device-000000[~]# devmem 0x30340018
0x00000020
as per result the GPR_SAI1_EXT_MCLK_EN is set correctly however GPR_SAI1_SEL1 not set with correct values.
here is the clock summery:
root@device-000000[~]# cat /sys/kernel/debug/clk/clk_summary | grep sai1
157: sai1_src 0 0 24000000 0 0
158: sai1_cg 0 0 24000000 0 0
159: sai1_pre_div 0 0 24000000 0 0
160: sai1_div 0 0 24000000 0 0
161: sai1_root_clk 0 0 24000000 0 0
309: sai1_ipg_clk 0 1 400000000
2) if I set these fields from the uboot terminal as you mentioned in your recent post
mw.l 0x30340008 0x00000001
mw.l 0x30340018 0x00100000
then both GPR2 and GPR6 register values are correct, however there is no change is clk_summary of sai1, if you see below:
root@device-000000[~]# devmem 0x30340008
0x00000001
root@device-000000[~]# devmem 0x30340018
0x00100000
root@device-000000[~]# cat /sys/kernel/debug/clk/clk_summary | grep sai1
157: sai1_src 0 0 24000000 0 0
158: sai1_cg 0 0 24000000 0 0
159: sai1_pre_div 0 0 24000000 0 0
160: sai1_div 0 0 24000000 0 0
161: sai1_root_clk 0 0 24000000 0 0
309: sai1_ipg_clk 0 1 400000000
root@device-000000[~]#
So, I wanted to know
a) are my uboot changes are not kept at correct place e.g: board/freescale/imx8mm_val/imx8mm_val.c ?
b) what is the expected result in cat /sys/kernel/debug/clk/clk_summary | grep sai1 ?
c) how to do you check if MCLK pin configured correctly as input in the actual imx8mm device?
Am I missing something here?
Thank you so much again, your input is really helpful.
Regards,
Sanjeev
RE "then both GPR2 and GPR6 register values are correct, however there is no change is clk_summary of sai1, if you see below:"
Key thing is, the values are still correct in user space, it's only pin mux routing so you may not see it represented in the clk_summary.
Don't try to code in too many changes at once as these problems are tricky and in the past I have added code that had just ended up in more wasted time. Until the problem is cracked keep the changes as simple as possible.
The changes should be the SAI in the device tree that sets it use the IPG clock, the sai1_mclk: fixed-24576 in this case is just to supply a 'value' for the MCLK input for your driver (it doesn't know what frequency you are putting into the MCLK pin), the same clock for your audio card and the rest is MCLK pin routing.
#1 The two registers
Comment out the code you added for setting the 2 registers in imx8mm_val.c. If you set them in uboot as I described and they are still set in user space, good stop there - leave that part of it until later.
#2 MCLK defaults to output
In fsl_sai.c comment out the two blocks of code that sets the MCLK pin direction, somewhere around lines 539 and 1628
// if (sai->soc_data->max_register >= FSL_SAI_MCTL) {
// /* SAI is in master mode at this point, so enable MCLK */
// regmap_update_bits(sai->regmap, FSL_SAI_MCTL,
// FSL_SAI_MCTL_MCLK_EN, FSL_SAI_MCTL_MCLK_EN);
// }
and
/* Select MCLK direction */
if (of_find_property(np, "fsl,sai-mclk-direction-output", NULL).....
This ensures MCLK cannot be set as an output. Again, simple step to sort out later.
#3 Device tree
Looks alright, I think it's mine from another post
-------------------------------------------------------------------------------------------------------------------------------
a) are my uboot changes are not kept at correct place e.g: board/freescale/imx8mm_val/imx8mm_val.c ? See above,
b) what is the expected result in cat /sys/kernel/debug/clk/clk_summary | grep sai1 ? This might be helpful to see if the clock settings and choices in the device tree are being recognised. I haven't ran my board for some time so I can't recall what I saw.
Check the imx documents under SAI and general clock routing, there's lots of usable information in them. IMX8MMRM.pdf
c) how to do you check if MCLK pin configured correctly as input in the actual imx8mm device? Difficult until it works! If it's a new board your using can you disconnect the MCLK connection and use a standard PLL1/2 device tree setup and check you are getting MCLK 'out'? If so the physical pin you're working with is the correct one and there's no connection/solder issues. It's not the first time pins have been routed wrong..
Try these things and see how you go. Do you have a NXP iMx8 EVK handy with the edge connector that gives a SAI port that you could experiment with? If this is a new design you might also be fighting with other things. I had to put in a lot of debug in various drivers to figure mine out, it could be you have configured the pin right but it's something totally different.
Cheers.
As you suggested to moved step by step, initially I made changes in the device tree, GPR registers, and fsl_sai.c (commented
//regmap_update_bits(sai->regmap, FSL_SAI_MCTL, FSL_SAI_MCTL_MCLK_EN, FSL_SAI_MCTL_MCLK_EN);
but it didn't work.
then I removed all the changes and kept only GPR register changes in the u-boot (board/freescale/imx8mm_val/imx8mm_val.c), and it did work.
Even though it worked I still have few questions:
1) does following line of code in fsl_sai.c has any impact? Because In my case it did worked only with GPR register changes.
regmap_update_bits(sai->regmap, FSL_SAI_MCTL, FSL_SAI_MCTL_MCLK_EN, FSL_SAI_MCTL_MCLK_EN);
2) Just like your requirement, I too need to support both 48khz and 96khz frequency. So, could you please suggest, how did you achieved it?
Thanks again for you suggestions.
Regards,
Sanjeev
Hi @iicsanjeev01,
Good to hear you had some success! Now you have a working 'base' build you can fine tune the code and add in the tweaks properly according to your specific BSP. Regards question 1, if the line of code worked then keep it, if not, just check that it's not responsible for setting something else! Check what each register and bit settings are actually doing. If it works in uboot then it is as mentioned throughout these forums to set the pad before the OS does - keep it that way.
Point 2, the selection of 48K,96K and so on, is set in whatever driver your hardware is using, if your driver is derived from the simple-audio card it will be in there. There will be some sort of switch statement in your driver making sure your chosen clock is divisible before reporting the rates back to the ALSA framework. In my case I needed all the 44K1 rates as well as the 48K rates so I had to switch the external MCLK frequency via a GPIO line and do some driver changes to suit.
You have listed 48K and 96K both of which are divisible off the same clock frequency, providing your clock is matched your driver should already do both rates. To use the various rates directly you can change your asound.conf or just use the hw 'device' directly i.e. no plughw. If you don't then by default everything will be resampled to the default rate of 44k1 or 48K. You can use Linux's speaker-test application so select the card, format and rates etc to force a card to render a specific rate.
Cheers.