6, 1, 9)),
SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 10),
SUNXI_FUNCTION(0x0, "gpio_in"),
SUNXI_FUNCTION(0x1, "gpio_out"),
SUNXI_FUNCTION(0x2, "csi"), /* D7 */
SUNXI_FUNCTION(0x3, "uart2"), /* CTS */
SUNXI_FUNCTION(0x4, "spi1"), /* MISO */
SUNXI_FUNCTION_IRQ_BANK(0x6, 1, 10)),
SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 11),
SUNXI_FUNCTION(0x0, "gpio_in"),
SUNXI_FUNCTION(0x1, "gpio_out"),
SUNXI_FUNCTION(0x2, "clk0"), /* OUT */
SUNXI_FUNCTION(0x3, "i2c0"), /* SCK */
SUNXI_FUNCTION(0x4, "ir"), /* RX */
SUNXI_FUNCTION_IRQ_BANK(0x6, 1, 11)),
SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 12),
SUNXI_FUNCTION(0x0, "gpio_in"),
SUNXI_FUNCTION(0x1, "gpio_out"),
SUNXI_FUNCTION(0x2, "i2s"), /* MCLK */
SUNXI_FUNCTION(0x3, "i2c0"), /* SDA */
SUNXI_FUNCTION(0x4, "pwm0"), /* PWM0 */
SUNXI_FUNCTION_IRQ_BANK(0x6, 1, 12)),
所以,我们现在知道了function
这个字符串的值域。那么对于外部中断引脚,应该怎么填function呢?像spi的功能,上面的代码告诉我们,直接填spi1,那么SUNXI_FUNCTION_IRQ_BANK
又是个啥?继续看它的头文件pinctrl-sunxi.h
:
#define SUNXI_FUNCTION_IRQ(_val, _irq) \
{ \
.name = "irq", \
.muxval = _val, \
.irqnum = _irq, \
}
#define SUNXI_FUNCTION_IRQ_BANK(_val, _bank, _irq) \
{ \
.name = "irq", \
.muxval = _val, \
.irqbank = _bank, \
.irqnum = _irq, \
}
所以我猜对于中断引脚,我们应该把function设置为irq,这样他就会自动帮我们初始化这个引脚的复用功能(全志可能没这个说法)。
接下来让我们修改suniv-f1c100s-licheepi-nano.dts
:
&spi1{
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&spi1_pins>;
enc28j60: ethernet@0 {
compatible = "microchip,enc28j60";
pinctrl-names = "default";
pinctrl-0 = <&enc28j60_pins>;
reg = <0>;
interrupt-parent = <&pio>;
interrupts = <4 11 IRQ_TYPE_EDGE_FALLING>;
spi-max-frequency = <12000000>;
};
};
这里基本就是对着全志设备树,照着它文档抄了。现在我们也可以解答一下我高中时代的疑问。这个interrupt-parent
是啥呢?interrupt-parent
属性用于指定中断路由到的控制器,并包含引用中断控制器节点的单个 phandle。 该属性是继承的,因此可以在中断客户端节点或其任何父节点中指定。 “中断”属性中列出的中断始终引用节点的中断父级。翻译成人话就是它用的中断要对应在哪个中断控制器,至少在本芯片内是一种些许无用的抽象,因为f1c100s就一个简单的中断控制器INTC,但是对于更复杂的片上中断控制器则是特别好的设计。并且这样做也方便了我们配置外部引脚的中断。
dtsi
中的#interrupt-cells = <3>
告诉我们,interrupts
这个属性应该有3个元素。通过翻其它全志的设备树,我们也知道了应该要如何配置interrupts
这个属性。那就是先放引脚Port的编号,那就是A放0,B放1,C放2,D放3,E放4。。。第二个参数是引脚编号,我们用的PE11,直接填。第三个参数,直接抄模块的文档。
现在已经大功告成,让我们拷贝好设备树和内核镜像,准备启动!
第三步:启动内核
启动内核后,我们dmesg,发现并没有任何关于enc28j60的影子,甚至连关于spi的影子也没有,这是怎么回事呢?再折腾了好几天后,我发现这是因为spi根本就没有被加载。我们芯片设备树对spi的配置中是这样配置compatible属性的:"allwinner,suniv-spi", "allwinner,sun8i-h3-spi"
,事实上,linux内核中根本没有代码去适配allwinner,suniv-spi
,所以内核会去找allwinner,sun8i-h3-spi
。而在drivers/spi/spi-sun6i.c
中:
static const struct of_device_id sun6i_spi_match[] = {
{ .compatible = "allwinner,sun6i-a31-spi", .data = &sun6i_a31_spi_cfg },
{ .compatible = "allwinner,sun8i-h3-spi", .data = &sun8i_h3_spi_cfg },
{
.compatible = "allwinner,sun50i-r329-spi",
.data = &sun50i_r329_spi_cfg
所以我们来到内核配置Device Drivers ---> SPI support
,发现allwinner的spi就俩,一个Allwinner A31 SPI controller
,一个Allwinner A31 SPI controller
。查看Kconfig后发现,后者对应的symbol是SPI_SUN6I
。故我们应该将后者设置成Y。而我之前是将前者设置成Y,后者设置成了N。
再次启动内核,一切都顺利