程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

linux设备树-linux内核设备树移植(二)

----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------

一、修改设备树s3c2440.dtsi

s3c2440.dtsi设备树存放的是s3c2440这个SoC跟其他s3c24xx系列不同的一些硬件信息,如clock控制器、串口等等;

修改arch/arm/boot/dts/s3c2440.dtsi文件,将该文件中的2416全部替换成2440,同时移除s3c6410相关的节点,文件内容如下:

复制代码
#include <dt-bindings/clock/s3c2443.h>  /* 注意 */
#include "s3c24xx.dtsi"
#include "s3c2440-pinctrl.dtsi"

/ {
        model = "Samsung S3C2440 SoC";
        compatible = "samsung,s3c2440","samsung,mini2440";

        aliases {
                serial3 = &uart_3;
        };

        cpus {
                cpu {
                        compatible = "arm,arm926ej-s";
                };
        };

        interrupt-controller@4a000000 {
                compatible = "samsung,s3c2440-irq";
        };

        clocks: clock-controller@4c000000 {
                compatible = "samsung,s3c2440-clock";
                reg = <0x4c000000 0x40>;
                #clock-cells = <1>;
        };

        pinctrl@56000000 {
                compatible = "samsung,s3c2440-pinctrl";
        };

        timer@51000000 {
                clocks = <&clocks PCLK_PWM>;
                clock-names = "timers";
        };

        uart_0: serial@50000000 {
                compatible = "samsung,s3c2440-uart";
                clock-names = "uart", "clk_uart_baud2",
                                "clk_uart_baud3";
                clocks = <&clocks PCLK_UART0>, <&clocks PCLK_UART0>,
                                <&clocks SCLK_UART>;
        };

        uart_1: serial@50004000 {
                compatible = "samsung,s3c2440-uart";
                clock-names = "uart", "clk_uart_baud2",
                                "clk_uart_baud3";
                clocks = <&clocks PCLK_UART1>, <&clocks PCLK_UART1>,
                                <&clocks SCLK_UART>;
        };

        uart_2: serial@50008000 {
                compatible = "samsung,s3c2440-uart";
                clock-names = "uart", "clk_uart_baud2",
                                "clk_uart_baud3";
                clocks = <&clocks PCLK_UART2>, <&clocks PCLK_UART2>,
                                <&clocks SCLK_UART>;
        };
        uart_3: serial@5000c000 {
                compatible = "samsung,s3c2440-uart";
                reg = <0x5000C000 0x4000>;
                interrupts = <1 18 24 4>, <1 18 25 4>;
                clock-names = "uart", "clk_uart_baud2",
                                "clk_uart_baud3";
                clocks = <&clocks PCLK_UART3>, <&clocks PCLK_UART3>,
                                <&clocks SCLK_UART>;
                status = "disabled";
        };

        watchdog: watchdog@53000000 {
                interrupts = <1 9 27 3>;
                clocks = <&clocks PCLK_WDT>;
                clock-names = "watchdog";
        };

        rtc: rtc@57000000 {
                compatible = "samsung,s3c2440-rtc";
                clocks = <&clocks PCLK_RTC>;
                clock-names = "rtc";
        };

        i2c@54000000 {
                compatible = "samsung,s3c2440-i2c";
                clocks = <&clocks PCLK_I2C0>;
                clock-names = "i2c";
        };
};
View Code
复制代码

接着我们需要对该文件进行修改来适配s3c2440。

1.1 修改头文件

修改时钟编号相关宏头文件:

#include <dt-bindings/clock/s3c2443.h>

修改为:

#include <dt-bindings/clock/s3c2410.h>

1.2 修改根节点compatible 

compatible需要与mach-smdk2440-dt.c文件dt_compat数组数组的compatible匹配,修改为:

model = "Samsung S3C2440 SoC";
compatible = "samsung,s3c2440","samsung,mini2440";

1.3 cpus节点

由于s3c2440内核为arm920t,因此修改cpus为:

cpus {
        cpu {
                compatible = "arm,arm920t";
        };
};

1.4 中断控制器节点

移除s3c2440.dtsi中中断控制器节点,在父级设备树s3c24xx.dtsi文件中定义有:

intc:interrupt-controller@4a000000 {
     compatible = "samsung,s3c2410-irq";
     reg = <0x4a000000 0x100>;
     interrupt-controller;
     #interrupt-cells = <4>;
};

中断控制器节点在上一片博客已经介绍过了,这里不重复介绍了。

关于中断控制器这部分可以参考内核文档:

  • Documentation/devicetree/bindings/interrupt-controller/samsung,s3c24xx-irq.txt;
  • Documentation/devicetree/bindings/interrupt-controller/arm,gic.yaml;
  • Documentation/devicetree/bindings/interrupt-controller/interrupts.txt;
1.4.1 未使用设备树

在内核移植不使用设备树的时候,在arch/arm/mach-s3c24xx/mach-smdk2440.c文件:

复制代码
MACHINE_START(S3C2440, "SMDK2440")
        /* Maintainer: Ben Dooks <ben-linux@fluff.org> */
        .atag_offset    = 0x100,

        .init_irq       = s3c2440_init_irq,
        .map_io         = smdk2440_map_io,
        .init_machine   = smdk2440_machine_init,
        .init_time      = smdk2440_init_time,
MACHINE_END

void __init s3c2440_init_irq(void)
{
        pr_info("S3C2440: IRQ Support\n");

#ifdef CONFIG_FIQ
        init_FIQ(FIQ_START);
#endif

        s3c_intc[0] = s3c24xx_init_intc(NULL, &init_s3c2440base[0], NULL,    // 初始化32个主中断源相关的中断
                                        0x4a000000);
        if (IS_ERR(s3c_intc[0])) {
                pr_err("irq: could not create main interrupt controller\n");
                return;
        }

        s3c24xx_init_intc(NULL, &init_eint[0], s3c_intc[0], 0x560000a4);      // 初始化外部中断相关的中断、外部中断4~7、8~23分别对应主中断源中的的EINT4~7、EINT8~23
        s3c_intc[1] = s3c24xx_init_intc(NULL, &init_s3c2440subint[0],         // 初始化带有子中断的内部中断相关的中断
                                        s3c_intc[0], 0x4a000018);
}
复制代码

这里直接调用s3c24xx_init_intc进行中断控制器初始化,函数位于drivers/irqchip/irq-s3c24xx.c,具体可以参考linux驱动移植-中断子系统执行流程

1.4.2 使用设备树

在linux内核根路径下搜索samsung,s3c2410-irq:

root@zhengyang:/work/sambashare/linux-5.2.8-dt# grep "samsung,s3c2410-irq" * -nR
drivers/irqchip/irq-s3c24xx.c:1307:IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c2410_init_intc_of);

定位到drivers/irqchip/irq-s3c24xx.c文件:

复制代码
static struct s3c24xx_irq_of_ctrl s3c2410_ctrl[] = {
        {
                .name = "intc",
                .offset = 0,
        }, {
                .name = "subintc",
                .offset = 0x18,
                .parent = &s3c_intc[0],
        }
};

int __init s3c2410_init_intc_of(struct device_node *np,
                        struct device_node *interrupt_parent)
{
        return s3c_init_intc_of(np, interrupt_parent,
                                s3c2410_ctrl, ARRAY_SIZE(s3c2410_ctrl));
}
IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c2410_init_intc_of);
复制代码

IRQCHIP_DECLARE的作用如下所述:用IRQCHIP_DECLARE声明中断控制器驱动,并将其与初始化函数关联。

其中IRQCHIP_DECLARE宏定义在include/linux/irqchip.h中:

复制代码
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
 
#define OF_DECLARE_2(table, name, compat, fn) \ 
        _OF_DECLARE(table, name, compat, fn, of_init_fn_2)
 
#define _OF_DECLARE(table, name, compat, fn, fn_type)            \ 
    static const struct of_device_id __of_table_##name        \ 
        __used __section(__##table##_of_table)            \ 
         = { .compatible = compat,                \ 
             .data = (fn == (fn_type)NULL) ? fn : fn  }
复制代码

展开后,其实就是定义了:

static const struct of_device_id __clk_of_table_s3c2410_irq \
__used__section(__irqchip_of_table) \
= {
  .compatible = "samsung,s3c2410-irq",
  .data = s3c2410_init_intc_of,
};

定义一个名为__clk_of_table_s3c2410_irq的of_device_id结构体,并将其放置在内核的.__irqchip_of_table节(section)内。 

这样当设备树定义有compatible = "samsung,s3c2410-irq"时,内核匹配到相应的设备时就会直接调用驱动初始化函数s3c2410_init_intc_of了。

复制代码
static int __init s3c_init_intc_of(struct device_node *np,
                        struct device_node *interrupt_parent,
                        struct s3c24xx_irq_of_ctrl *s3c_ctrl, int num_ctrl)
{
        struct s3c_irq_intc *intc;
        struct s3c24xx_irq_of_ctrl *ctrl;
        struct irq_domain *domain;
        void __iomem *reg_base;
        int i;

        reg_base = of_iomap(np, 0);
        if (!reg_base) {
                pr_err("irq-s3c24xx: could not map irq registers\n");
                return -EINVAL;
        }

        domain = irq_domain_add_linear(np, num_ctrl * 32,
                                                     &s3c24xx_irq_ops_of, NULL);
        if (!domain) {
                pr_err("irq: could not create irq-domain\n");
                return -EINVAL;
        }

        for (i = 0; i < num_ctrl; i++) {
                ctrl = &s3c_ctrl[i];

                pr_debug("irq: found controller %s\n", ctrl->name);

                intc = kzalloc(sizeof(struct s3c_irq_intc), GFP_KERNEL);
                if (!intc)
                        return -ENOMEM;

                intc->domain = domain;
                intc->irqs = kcalloc(32, sizeof(struct s3c_irq_data),
                                     GFP_KERNEL);
                if (!intc->irqs) {
                        kfree(intc);
                        return -ENOMEM;
                }

                if (ctrl->parent) {
                        intc->reg_pending = reg_base + ctrl->offset;
                        intc->reg_mask = reg_base + ctrl->offset + 0x4;

                        if (*(ctrl->parent)) {
                                intc->parent = *(ctrl->parent);
                        } else {
                                pr_warn("irq: parent of %s missing\n",
                                        ctrl->name);
                                kfree(intc->irqs);
                                kfree(intc);
                                continue;
                        }
                } else {
                        intc->reg_pending = reg_base + ctrl->offset;
                        intc->reg_mask = reg_base + ctrl->offset + 0x08;
                        intc->reg_intpnd = reg_base + ctrl->offset + 0x10;
                }

                s3c24xx_clear_intc(intc);
                s3c_intc[i] = intc;
        }

        set_handle_irq(s3c24xx_handle_irq);

        return 0;
}
View Code
复制代码

1.5 时钟控制器节点

s3c2440.dtsi中定义了时钟控制器节点,在内核文档中称之为"Clock providers":

clocks: clock-controller@4c000000 {
        compatible = "samsung,s3c2440-clock";
        reg = <0x4c000000 0x40>;
        #clock-cells = <1>;
};

时钟提供者节点必须有#clock-cells属性,说明该节点是clock provider。它有2种取值:

  • #clock-cells = <0>,只有一个时钟输出;
  • #clock-cells = <1>,有多个时钟输出;

设备需要时钟时,它是clock consumer。它描述了使用哪一个clock provider中的哪一个时钟;

  • 每个时钟都分配了一个标识符,设备节点可以使用此标识符来指定它们使用的时钟。其中一些时钟仅在特定的SoC上可用;
  • 所有可用的时钟都定义为预处理器宏并位于dt-bindings/clock/s3c2410.h头文件中,可以在设备树源代码中使用;

比如使用时钟控制器生成的时钟的UART控制器节点:

serial@50004000 {
        compatible = "samsung,s3c2440-uart";
        reg = <0x50004000 0x4000>;
        interrupts = <1 23 3 4>, <1 23 4 4>;
        clock-names = "uart", "clk_uart_baud2";  /* 时钟名,uart、clk_uart_baud2 */
        clocks = <&clocks PCLK_UART0>, <&clocks PCLK_UART0>;  /*  指定uart时钟来自PCLK_UART0; 指定clk_uart_baud2时钟来自PCLK_UART0
                                                       其中PCLK_UART0参考include/dt-bindings/clock/s3c2410.h或者这个clock控制器驱动的实现。 */
};

时钟使用者(clock consumer)的node节点必须有clocks属性,说明该节点是clock consumer。clocks属性由2部分组成:phandle、clock-specifier。

  •  phandle是@节点名称(例如上个示例中的@clocks);
  •  clock-specifier怎么理解呢?一个时钟控制器可以控制很多时钟硬件(例如5种基本时钟:fixed-rate、fixed-factor、gate、mux、divider),每种时钟硬件都有对应的ID号。clock-specifier就是这个ID号。例如,s3c2440的时钟硬件编号已经在include/dt-bindings/clock/s3c2410.h中声明了;

clocks-names:用于指定时钟名,调用devm_clk_get获取时钟时,可以传入该名字;比如在s3c24xx_serial_probe中,调用clk_get(&platdev->dev, "uart") 获取时钟;

另外,注意:如果时钟提供者将#clock-cells指定为“0”,则仅会出现该对中的phandle部分。

关于clock这部分可以参考内核文档:

  • Documentation/devicetree/bindings/clock/samsung,s3c2410-clock.txt;
  • Documentation/devicetree/bindings/clock/clock-bindings.txt;
1.5.1 未使用设备树

在内核移植不使用设备树的时候,在arch/arm/mach-s3c24xx/mach-smdk2440.c文件:

复制代码
MACHINE_START(S3C2440, "SMDK2440")
        /* Maintainer: Ben Dooks <ben-linux@fluff.org> */
        .atag_offset    = 0x100,

        .init_irq       = s3c2440_init_irq,
        .map_io         = smdk2440_map_io,
        .init_machine   = smdk2440_machine_init,
        .init_time      = smdk2440_init_time,
MACHINE_END

static void __init smdk2440_init_time(void)
{
        s3c2440_init_clocks(12000000);
        samsung_timer_init();
}
复制代码

直接调用s3c2440_init_clocks初始化linux内核的时钟,晶振频率为12MHz;

void __init s3c2440_init_clocks(int xtal)
{
        s3c2410_common_clk_init(NULL, xtal, 1, S3C24XX_VA_CLKPWR);    // 1对应的枚举变量S3C2440
}

这里直接调用s3c2410_common_clk_init进行s3c2440时钟的初始化,具体可以参考linux驱动移植-通用时钟框架子系统

复制代码
void __init s3c2410_common_clk_init(struct device_node *np, unsigned long xti_f,   // np为NULL、xti_f=12000000、current_soc=1
                                    int current_soc,
                                    void __iomem *base)
{
        ....

        /* Register external clocks only in non-dt cases */
        if (!np)
                s3c2410_common_clk_register_fixed_ext(ctx, xti_f);   // 注册通用的外部固定时钟,即注册晶振

        ...
}
复制代码
1.5.2 使用设备树

在linux内核根路径下搜索samsung,s3c2440-clock:

root@zhengyang:/work/sambashare/linux-5.2.8-dt# grep "s3c2440-clock" * -nR
Documentation/devicetree/bindings/clock/samsung,s3c2410-clock.txt:11:  - "samsung,s3c2440-clock" - controller compatible with S3C2440 SoC.
drivers/clk/samsung/clk-s3c2410.c:437:CLK_OF_DECLARE(s3c2440_clk, "samsung,s3c2440-clock", s3c2440_clk_init);

定位到drivers/clk/samsung/clk-s3c2410.c文件:

static void __init s3c2440_clk_init(struct device_node *np)
{
        s3c2410_common_clk_init(np, 0, S3C2440, NULL);
}
CLK_OF_DECLARE(s3c2440_clk, "samsung,s3c2440-clock", s3c2440_clk_init);

CLK_OF_DECLARE的作用如下所述:用CLK_OF_DECLARE声明兼容的时钟驱动,并将其与初始化函数关联。

其中CLK_OF_DECLARE宏定义在include/linux/clk-provider.h中:

#define CLK_OF_DECLARE(name, compat, fn) \
static const struct of_device_id __clk_of_table_##name \
__used __section(__clk_of_table) \
= { .compatible = compat, .data = fn };

这一段需要借助内核编译的lds文件来解读,其中传入了参数给编译器来确定变量的存放位置,其实就是定义了:

static const struct of_device_id __clk_of_table_s3c2440_clk \
__used__section(__clk_of_table) \
= {
  .compatible = "samsung,s3c2440-clock",
  .data = s3c2440_clk_init,
};

通过CLK_OF_DECLARE来定义相应的of_device_id,并且要把相应的驱动初始化函数fn的地址(即s3c2440_clk_init)传给data。

这样当设备树定义有compatible = "samsung,s3c2440-clock"时,匹配到相应的设备时就会直接调用驱动初始化函数s3c2440_clk_init了。

s3c2440_clk_init函数其内部调用s3c2410_common_clk_init进行s3c2440时钟的初始化。

复制代码
void __init s3c2410_common_clk_init(struct device_node *np, unsigned long xti_f,   // np为NULL、xti_f=12000000、current_soc=1
                                    int current_soc,
                                    void __iomem *base)
{
        ....

        /* Register external clocks only in non-dt cases */
        if (!np)  // 不为空,所以不会走这里   外部固定时钟的注册也是通过设备树实现的,后面介绍
                s3c2410_common_clk_register_fixed_ext(ctx, xti_f);   

        ...
}
复制代码

1.6 pinctrl节点

s3c2440.dtsi文件pinctrl节点定义如下:

 pinctrl@56000000 {
          compatible = "samsung,s3c2440-pinctrl";
 };

在父级设备树s3c24xx.dtsi文件中定义有:

复制代码
pinctrl_0: pinctrl@56000000 {
        reg = <0x56000000 0x1000>;

        wakeup-interrupt-controller {
                compatible = "samsung,s3c2410-wakeup-eint";
                interrupts = <0 0 0 3>,
                             <0 0 1 3>,
                             <0 0 2 3>,
                             <0 0 3 3>,
                             <0 0 4 4>,
                             <0 0 5 4>;
        };
};
复制代码

在s3c2440-pinctrl.dtsi文件中定义了pin bank、以及pin group子节点:

复制代码
&pinctrl_0 {
        /*
         * Pin banks
         */

        gpa: gpa {
                gpio-controller;
                #gpio-cells = <2>;
        };
        ....
}
复制代码

1.7 串口节点

s3c2440只有三个串口,因此需要修改s3c2440.dtsi文件中定义的串口节点uart_0、uart_1、uart_2,同时删除uart_3节点、以及aliases 节点:

复制代码
uart_0: serial@50000000 {
        compatible = "samsung,s3c2440-uart";   // 对应驱动定义在drivers/tty/serial/samsung.c
        clock-names = "uart","clk_uart_baud2";
        clocks = <&clocks PCLK_UART0>,<&clocks PCLK_UART0>;
};

uart_1: serial@50004000 {
        compatible = "samsung,s3c2440-uart";
        clock-names = "uart","clk_uart_baud2";
        clocks = <&clocks PCLK_UART1>,<&clocks PCLK_UART1>;
};

uart_2: serial@50008000 {
        compatible = "samsung,s3c2440-uart";
        clock-names = "uart","clk_uart_baud2";
        clocks = <&clocks PCLK_UART2>,<&clocks PCLK_UART2>;
};
复制代码

在父级设备树s3c24xx.dtsi文件中定义有:

复制代码
uart0: serial@50000000 {
        compatible = "samsung,s3c2410-uart";
        reg = <0x50000000 0x4000>;
        interrupts = <1 28 0 4>, <1 28 1 4>;
        status = "disabled";
};

uart1: serial@50004000 {
        compatible = "samsung,s3c2410-uart";
        reg = <0x50004000 0x4000>;
        interrupts = <1 23 3 4>, <1 23 4 4>;
        status = "disabled";
};

uart2: serial@50008000 {
        compatible = "samsung,s3c2410-uart";
        reg = <0x50008000 0x4000>;
        interrupts = <1 15 6 4>, <1 15 7 4>;
        status = "disabled";
};
复制代码

串口节点属性在上一片博客已经介绍过了,这里不重复介绍了。

在drivers/tty/serial/samsung.c中可以搜索关键字 “samsung,s3c2440-uart”;当platform设备和驱动匹配后,s3c24xx_serial_probe函数被调用。

在s3c24xx_serial_probe函数中,调用clk_get(&platdev->dev, "uart"),clk_get(&platdev->dev, "clk_uart_baud2")根据设备名称/时钟别名来查找并获取对时钟生产者的引用。其中:

  • dev是时钟使用者设备(device for clock consumer);
  • "uart"是串口时钟别名;

因此uart_0 clk_get函数获取到的时钟为<&clocks PCLK_UART0>;uart_1 clk_get函数获取到的时钟为<&clocks PCLK_UART1>,uart_2 clk_get函数获取到的时钟为<&clocks PCLK_UART2>;

关于串口这部分可以参考内核文档:

  • Documentation/devicetree/bindings/serial/samsung_uart.txt;

1.8 看门狗节点

修改s3c2440.dtsi中看门狗节点信息为:

watchdog: watchdog@53000000 {
        interrupts = <1 9 27 3>;
        clocks = <&clocks PCLK>;     /* PCLK_WDT修改为PCLK */
        clock-names = "watchdog";
};

在父级设备树s3c24xx.dtsi文件中定义有:

watchdog@53000000 {
        compatible = "samsung,s3c2410-wdt";
        reg = <0x53000000 0x100>;
        interrupts = <0 0 9 3>;
        status = "disabled";
};

从s3c2440芯片手册上看,看门狗的时钟直接接到PCLK上,没有加任何开关,所以这里clocks修改为PCLK

关于watchdog这部分可以参考内核文档:

  • Documentation/devicetree/bindings/watchdog/samsung-wdt.txt;

1.9 rtc节点

修改s3c2440.dtsi中rtc节点信息为:

rtc: rtc@57000000 {
        compatible = "samsung,s3c2140-rtc";   // 对应驱动对应在 drivers/rtc/rtc-s3c.c
        clocks = <&clocks PCLK_RTC>;
        clock-names = "rtc";
};

在父级设备树s3c24xx.dtsi文件中定义有:

rtc@57000000 {
        compatible = "samsung,s3c2410-rtc";
        reg = <0x57000000 0x100>;
        interrupts = <0 0 30 3>, <0 0 8 3>;
        status = "disabled";
};

在drivers/rtc/rtc-s3c.c中可以搜索关键字 “samsung,s3c2410-rtc”;当platform设备和驱动匹配后,s3c_rtc_probe函数被调用。

在s3c_rtc_probe函数中,调用devm_clk_get(&pdev->dev, "rtc")根据设备名称/时钟别名来查找并获取对时钟生产者的引用。其中:

  • dev是时钟使用者设备(device for clock consumer);
  • "rtc"是rtc时钟别名;

关于rtc这部分可以参考内核文档:

  • Documentation/devicetree/bindings/rtc/rtc.txt;
  • Documentation/devicetree/bindings/rtc/s3c-rtc.txt;

1.10 i2c节点

修改s3c2440.dtsi中i2c节点信息为:

i2c: i2c@54000000 {
        compatible = "samsung,s3c2440-i2c";
        clocks = <&clocks PCLK_I2C>;  /* PCLK_I2C0修改为PCLK_I2C */
        clock-names = "i2c";
};

在父级设备树s3c24xx.dtsi文件中定义有:

i2c@54000000 {
        compatible = "samsung,s3c2410-i2c";
        reg = <0x54000000 0x100>;
        interrupts = <0 0 27 3>;
        #address-cells = <1>;
        #size-cells = <0>;
        status = "disabled";
};

关于i2c这部分可以参考内核文档:

  • Documentation/devicetree/bindings/i2c/i2c-s3c2410.txt;

1.11 新增nand节点

复制代码
nand0: nand@4e000000 {
    compatible = "samsung,s3c2440-nand";
    reg = <0x4e000000 0x40>;
    interrupts = <0 0 24 3>;
    clocks = <&clocks HCLK_NAND>;
    clock-names = "nand";
    pinctrl-names = "default";
    pinctrl-0 = <&nand_pinctrl>;
    status = "disabled";
};
复制代码

1.12 文件内容

修改之后,arch/arm/boot/dts/s3c2440.dtsi文件内容如下:

复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * Samsung's S3C2440 SoC device tree source
 *
 * Copyright (c) 2013 Heiko Stuebner <heiko@sntech.de>
 */

#include <dt-bindings/clock/s3c2410.h>
#include "s3c24xx.dtsi"
#include "s3c2440-pinctrl.dtsi"

/ {
        model = "Samsung S3C2440 SoC";
        compatible = "samsung,s3c2440","samsung,mini2440";

        cpus {
                cpu {
                        compatible = "arm,arm920t";
                };
        };


        clocks: clock-controller@4c000000 {
                compatible = "samsung,s3c2440-clock";
                reg = <0x4c000000 0x40>;
                #clock-cells = <1>;
        };

        pinctrl@56000000 {
                compatible = "samsung,s3c2440-pinctrl";
        };

        timer@51000000 {
                clocks = <&clocks PCLK_PWM>;
                clock-names = "timers";
        };

        uart_0: serial@50000000 {
                compatible = "samsung,s3c2440-uart";
                clock-names = "uart","clk_uart_baud2";
                clocks = <&clocks PCLK_UART0>,<&clocks PCLK_UART0>;
        };

        uart_1: serial@50004000 {
                compatible = "samsung,s3c2440-uart";
                clock-names = "uart","clk_uart_baud2";
                clocks = <&clocks PCLK_UART1>,<&clocks PCLK_UART1>;
        };

        uart_2: serial@50008000 {
                compatible = "samsung,s3c2440-uart";
                clock-names = "uart","clk_uart_baud2";
                clocks = <&clocks PCLK_UART2>,<&clocks PCLK_UART2>;
        };

        watchdog: watchdog@53000000 {
                interrupts = <1 9 27 3>;
                clocks = <&clocks PCLK>;
                clock-names = "watchdog";
        };

        rtc: rtc@57000000 {
                compatible = "samsung,s3c2410-rtc";
                clocks = <&clocks PCLK_RTC>;
                clock-names = "rtc";
        };

        i2c: i2c@54000000 {
                compatible = "samsung,s3c2440-i2c";
                clocks = <&clocks PCLK_I2C>;
                clock-names = "i2c";
        };

        nand0: nand@4e000000 {
                compatible = "samsung,s3c2440-nand";
                reg = <0x4e000000 0x40>;
                interrupts = <0 0 24 3>;
                clocks = <&clocks HCLK_NAND>;
                clock-names = "nand";
                pinctrl-names = "default";
                pinctrl-0 = <&nand_pinctrl>;
                status = "disabled";
   };
};
View Code
复制代码

二、修改设备树s3c2440-pinctrl.dtsi

由于s3c2440只有GPA~GPJ,因此将GPJ之后的引脚配置移除,比如gpl,得到文件内容如下:

复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * Samsung S3C2416 pinctrl settings
 *
 * Copyright (c) 2013 Heiko Stuebner <heiko@sntech.de>
 */

#include <dt-bindings/pinctrl/samsung.h>

&pinctrl_0 {
    /*
     * Pin banks
     */

    gpa: gpa {
        gpio-controller;
        #gpio-cells = <2>;
    };

    gpb: gpb {
        gpio-controller;
        #gpio-cells = <2>;
    };

    gpc: gpc {
        gpio-controller;
        #gpio-cells = <2>;
    };

    gpd: gpd {
        gpio-controller;
        #gpio-cells = <2>;
    };

    gpe: gpe {
        gpio-controller;
        #gpio-cells = <2>;
    };

    gpf: gpf {
        gpio-controller;
        #gpio-cells = <2>;
        interrupt-controller;
        #interrupt-cells = <2>;
    };

    gpg: gpg {
        gpio-controller;
        #gpio-cells = <2>;
        interrupt-controller;
        #interrupt-cells = <2>;
    };

    gph: gph {
        gpio-controller;
        #gpio-cells = <2>;
    };

    gpj: gpj {
        gpio-controller;
        #gpio-cells = <2>;
    };/*
     * Pin groups
     */

    uart0_data: uart0-data {
        samsung,pins = "gph-0", "gph-1";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
    };

    uart0_fctl: uart0-fctl {
        samsung,pins = "gph-8", "gph-9";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
    };

    uart1_data: uart1-data {
        samsung,pins = "gph-2", "gph-3";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
    };

    uart1_fctl: uart1-fctl {
        samsung,pins = "gph-10", "gph-11";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
    };

    uart2_data: uart2-data {
        samsung,pins = "gph-4", "gph-5";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
    };

    uart2_fctl: uart2-fctl {
        samsung,pins = "gph-6", "gph-7";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
    };

    uart3_data: uart3-data {
        samsung,pins = "gph-6", "gph-7";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
    };

    extuart_clk: extuart-clk {
        samsung,pins = "gph-12";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
    };

    i2c0_bus: i2c0-bus {
        samsung,pins = "gpe-14", "gpe-15";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
    };

    spi0_bus: spi0-bus {
        samsung,pins = "gpe-11", "gpe-12", "gpe-13";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
    };

    sd0_clk: sd0-clk {
        samsung,pins = "gpe-5";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
    };

    sd0_cmd: sd0-cmd {
        samsung,pins = "gpe-6";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
    };

    sd0_bus1: sd0-bus1 {
        samsung,pins = "gpe-7";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
    };

    sd0_bus4: sd0-bus4 {
        samsung,pins = "gpe-8", "gpe-9", "gpe-10";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
    };
};
View Code
复制代码

接着我们需要对该文件进行修改来适配s3c2440。

2.1 gpa节点

gpa: gpa {
    gpio-controller;
    #gpio-cells = <2>;
};

2.2 gpf节点

gpf: gpf {
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
};

以上设备树描述的是一个中断控制器节点gpf,它同时也是一个GPIO控制器。下面是各个属性的含义及作用:

  • gpio-controller:表示该节点是一个GPIO控制器;
  • #gpio-cells:指定使用几个cell来描述一个GPIO;这里设为2,即包含两个参数,第一个为 GPIO 编号,第二个是GPIO 的功能;
  • interrupt-controller:表示该节点是一个中断控制器节点;
  • #interrupt-cells:用来描述子节点中"interrupts"属性使用了几个cell才能确定所使用的中断。这里设为2,第一个表示硬件中断编号,第二个表示中断触发类型(比如上升沿触发、下降沿触发等)。

该中断控制器用于连接和处理系统内部或外部的中断信号。当有一个设备产生中断时,会将中断信号通过该中断控制器进行转发和处理。

同时,该节点也是一个GPIO控制器,可以控制一些GPIO引脚的输出或输入状态。这样可以实现对一些外设的控制或监测功能

例如,在某个GPIO引脚产生了按键按下或释放事件时,可以通过GPIO接口通知到CPU,CPU再进行相应的处理。

2.3 串口

2.3.1 uart0_data

串口0对应的引脚GPH2:TXD0,GPH3:RXD0,因此修改uart0_data节点为:

uart0_data: uart0-data {
    samsung,pins = "gph-2", "gph-3";
    samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
};
2.3.2 uart0_fctl

移除uart0_fctl节点。

2.3.3 uart1_data

串口1对应的引脚GPH4:TXD1,GPH5:RXD1,因此修改uart1_data节点为:

uart1_data: uart1-data {
    samsung,pins = "gph-4", "gph-5";
    samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
};
2.3.4 uart1_fctl

移除uart1_fctl节点。

2.3.5 uart2_data

串口2对应的引脚GPH6:TXD2,GPH7:RXD2,因此修改uart2_data节点为:

uart2_data: uart2-data {
    samsung,pins = "gph-6", "gph-7";
    samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
};
2.3.6 uart2_fctl

移除uart2_fctl节点。

2.3.7 uart3_data

移除uart3_data节点。

2.4 extuart_clk

串口外部时钟输入引脚为gph8,因此修改extuart_clk节点为:

extuart_clk: extuart-clk {
    samsung,pins = "gph-8";
    samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
};

2.5 i2c0_bus

s3c2440这款SoC其内部只有一个I2C控制器,其中SCL连接GPE14引脚,SDA连接GPE15引脚。因此i2c0_bus节点如下:

i2c0_bus: i2c0-bus {
    samsung,pins = "gpe-14", "gpe-15";
    samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
};

2.6 spi0_bus

s3c2440这款SoC的SPI结构,其内部有两个SPI控制器:s3c2440 SPI相关引脚定义:

SPI SCLK MOSI MISO SS
SPI0 GPE13 GPE12 GPE11 GPG2
SPI1 GPG7 GPG6 GPG5 GPG3

因此spi0_bus节点如下:

spi0_bus: spi0-bus {
    samsung,pins = "gpe-11", "gpe-12", "gpe-13";
    samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
};

2.7 SD节点

s3c2440包含一个SD控制器,SD相关引脚定义:

SDDAT[3:0]

SD接收/发送数据

SDCMD

SDK接收应答/发送指令

SDCLK

SD时钟

GPE10~GPE7 GPE6 GPE5

因此SD相关节点如下:

复制代码
    sd0_clk: sd0-clk {
        samsung,pins = "gpe-5";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
    };

    sd0_cmd: sd0-cmd {
        samsung,pins = "gpe-6";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
    };

    sd0_bus1: sd0-bus1 {
        samsung,pins = "gpe-7";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
    };

    sd0_bus4: sd0-bus4 {
        samsung,pins = "gpe-8", "gpe-9", "gpe-10";
        samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
    };
复制代码

2.8 新增nand节点

/*添加Nand Flash所用的管脚*/
nand_pinctrl: nand_pinctrl {
    samsung,pins = "gpa-17", "gpa-18", "gpa-19",
                 "gpa-20", "gpa-22";
    samsung,pin-function = <1>;
};

2.9 文件内容

修改之后,arch/arm/boot/dts/s3c2440-pinctrl.dtsi文件内容如下:

复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * Samsung S3C2416 pinctrl settings
 *
 * Copyright (c) 2013 Heiko Stuebner <heiko@sntech.de>
 */

#include <dt-bindings/pinctrl/samsung.h>

&pinctrl_0 {
        /*
         * Pin banks
         */

        gpa: gpa {
                gpio-controller;
                #gpio-cells = <2>;
        };

        gpb: gpb {
                gpio-controller;
                #gpio-cells = <2>;
        };

        gpc: gpc {
                gpio-controller;
                #gpio-cells = <2>;
        };

        gpd: gpd {
                gpio-controller;
                #gpio-cells = <2>;
        };

        gpe: gpe {
                gpio-controller;
                #gpio-cells = <2>;
        };

        gpf: gpf {
                gpio-controller;
                #gpio-cells = <2>;
                interrupt-controller;
                #interrupt-cells = <2>;
        };

        gpg: gpg {
                gpio-controller;
                #gpio-cells = <2>;
                interrupt-controller;
                #interrupt-cells = <2>;
        };

        gph: gph {
                gpio-controller;
                #gpio-cells = <2>;
        };

        gpj: gpj {
                gpio-controller;
                #gpio-cells = <2>;
        };


        /*
         * Pin groups
         */

        uart0_data: uart0-data {
                samsung,pins = "gph-2", "gph-3";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        uart1_data: uart1-data {
                samsung,pins = "gph-4", "gph-5";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };


        uart2_data: uart2-data {
                samsung,pins = "gph-6", "gph-7";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        uart2_fctl: uart2-fctl {
                samsung,pins = "gph-6", "gph-7";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        extuart_clk: extuart-clk {
                samsung,pins = "gph-8";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        i2c0_bus: i2c0-bus {
                samsung,pins = "gpe-14", "gpe-15";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        spi0_bus: spi0-bus {
                samsung,pins = "gpe-11", "gpe-12", "gpe-13";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        sd0_clk: sd0-clk {
                samsung,pins = "gpe-5";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        sd0_cmd: sd0-cmd {
                samsung,pins = "gpe-6";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        sd0_bus1: sd0-bus1 {
                samsung,pins = "gpe-7";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        sd0_bus4: sd0-bus4 {
                samsung,pins = "gpe-8", "gpe-9", "gpe-10";
                samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
        };

        /*添加Nand Flash所用的管脚*/
        nand_pinctrl: nand_pinctrl {
                samsung,pins = "gpa-17", "gpa-18", "gpa-19",
                     "gpa-20", "gpa-22";
               samsung,pin-function = <1>;
       };
};
View Code
复制代码

三、修改设备树s3c2440-smdk2440.dts

修改s3c2440-smdk2440.dts设备树,将该文件中的2416全部替换成2440,同时移除&sdhci_0、&sdhci_1、&uart_3节点,文件内容如下:

复制代码
/dts-v1/;
#include "s3c2440.dtsi"

/ {
        model = "SMDK2440";
        compatible = "samsung,s3c2440","samsung,mini2440";

        memory@30000000 {
                device_type = "memory";
                reg =  <0x30000000 0x4000000>;
        };

        clocks {
                compatible = "simple-bus";
                #address-cells = <1>;
                #size-cells = <0>;

                xti: xti@0 {
                        compatible = "fixed-clock";
                        reg = <0>;
                        clock-frequency = <12000000>;
                        clock-output-names = "xti";
                        #clock-cells = <0>;
                };
        };
};

&rtc {
        status = "okay";
};

&uart_0 {
        status = "okay";
        pinctrl-names = "default";
        pinctrl-0 = <&uart0_data>, <&uart0_fctl>;
};

&uart_1 {
        status = "okay";
        pinctrl-names = "default";
        pinctrl-0 = <&uart1_data>, <&uart1_fctl>;
};

&uart_2 {
        status = "okay";
        pinctrl-names = "default";
        pinctrl-0 = <&uart2_data>;
};

&watchdog {
        status = "okay";
};
View Code
复制代码

接着我们需要对该文件进行修改来适配Mini2440开发板。

3.1 fixed-clock时钟配置

晶振时钟设备节点xti定义如下:

复制代码
 clocks {
                compatible = "simple-bus";
                #address-cells = <1>;
                #size-cells = <0>;

                xti: xti@0 {
                        compatible = "fixed-clock";
                        reg = <0>;
                        clock-frequency = <12000000>;
                        clock-output-names = "xti";
                        #clock-cells = <0>;
                };
        };
复制代码

其中与clk注册相关的属性:

  • clock-cells:前面已经介绍过了,不重复介绍了;设置为0,表示对外输出一路时钟;
  • clocks-output-names:用于说明一个clk provider输出多路clock的名称,clock consumer的设备树节点中提供的clock provider的标识是一个index,通过这个index可以在clock-output-names属性值中找到对应的输出clock provider的名字;
  • clock-frequency:输出时钟的频率;

在linux内核drivers路径下搜索fixed-clock:

root@zhengyang:/work/sambashare/linux-5.2.8-dt/drivers# grep "fixed-clock" * -nR
clk/clk-fixed-rate.c:194:CLK_OF_DECLARE(fixed_clk, "fixed-clock", of_fixed_clk_setup);
clk/clk-fixed-rate.c:224:       { .compatible = "fixed-clock" },
clk/ti/clk.c:395:       { .compatible = "fixed-clock" },

定位到clk/clk-fixed-rate.c文件:

复制代码
static struct clk *_of_fixed_clk_setup(struct device_node *node)
{
        struct clk *clk;
        const char *clk_name = node->name;
        u32 rate;
        u32 accuracy = 0;
        int ret;

        if (of_property_read_u32(node, "clock-frequency", &rate))  // rate = 12000000
                return ERR_PTR(-EIO);

        of_property_read_u32(node, "clock-accuracy", &accuracy);   // accuracy = 0

        of_property_read_string(node, "clock-output-names", &clk_name);  // clk_name = "xti"

        clk = clk_register_fixed_rate_with_accuracy(NULL, clk_name, NULL,  // 注册fixed rate clock
                                                    0, rate, accuracy);
        if (IS_ERR(clk))
                return clk;

        ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);  // 针对于设备树,注册of_clk_provider
        if (ret) {
                clk_unregister(clk);
                return ERR_PTR(ret);
        }

        return clk;
}

/**
 * of_fixed_clk_setup() - Setup function for simple fixed rate clock
 */
void __init of_fixed_clk_setup(struct device_node *node)
{
        _of_fixed_clk_setup(node);
}
CLK_OF_DECLARE(fixed_clk, "fixed-clock", of_fixed_clk_setup);
复制代码

通过CLK_OF_DECLARE来定义相应的of_device_id,并且要把相应的驱动初始化函数fn的地址(即of_fixed_clk_setup)传给data。

这样当设备树定义有compatible = "fixed-clock"时,匹配到相应的设备时就会直接调用驱动初始化函数of_fixed_clk_setup了。

of_fixed_clk_setup函数其内部调用_of_fixed_clk_setup注册fixed rate clock。

3.2 新增网卡节点

3.2.1 DM9000网卡硬件接线

linux驱动移植-DM9000网卡驱动介绍过Mini2440开发板的DM9000的接线原理图;

  • DM9000片选线是nLAN_CS(AEN)连接在s3c2440的nGCS4上;
  • DM9000 IRQ_LAN(INT)接的是s3c2440的ENT7(GPF7),用的外部中断7,这个中断用于接收数据时触发的,高电平有效;
  • DM9000只有一地址线CMD,地址线连接在s3c2440的ADDR2口上;
  • DM9000数据线SD0~SD15连接在s3c2440的LDATA0~LDATA15;

nGCS4对应的片选信号是0x20000000开头的,在0x20000000-0x28000000之间。所以当CPU发出0x20000000-0x28000000的物理地址数据时,DM9000就会被选中。

对于DM9000芯片来说,读写地址与数据使用的是同一组16个I/O引脚,DM9000芯片通过CMD引脚区分数据线传输的的是DM9000的寄存器地址,还是寄存器数据。

也就是说当CPU发出物理地址(0x20000000-0x28000000)| 0x4时,此时CPU再发出的数据线信号对于DM9000来说实际是地址信号。例如:

  • 当在地址0x20000000上读写数据时,表示读写的数据是DM9000的地址;
  • 当访问的地址0x20000004上读写数据时,表示读写的数据是DM9000的数据;
3.2.2 设备树节点

添加DM9000 网卡,以便于后面的驱动开发调试,需要引入头文件:

#include <dt-bindings/interrupt-controller/irq.h>

同时在根节点下新增子节点:

复制代码
    /*添加DM9000 网卡,以便于后面的驱动开发调试*/
    srom-cs4@20000000 {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        reg = <0x20000000 0x8000000>;
        ranges;
        ethernet@20000000 {
            compatible = "davicom,dm9000";
            reg = <0x20000000 0x2 0x20000004 0x2>;
            interrupt-parent = <&gpf>;
            interrupts = <7 IRQ_TYPE_EDGE_RISING>;
            local-mac-address = [00 00 de ad be ef];
            davicom,no-eeprom;
        };
    };
复制代码

以上设备树描述的是一个srom-cs4设备节点,它包含了一个以太网控制器子节点ethernet。下面是各个属性的含义:

srom-cs4:

  • compatible:表示设备类型名称,这里为 "simple-bus";
  • #address-cells:地址单元格数量,这里为 1,即使用1个u32数来表示地址;
  • #size-cells:大小单元格数量,这里为 1,即使用1个u32数来表示地址跨度;
  • reg:物理地址和大小,这里表示该设备在物理地址 0x20000000 处,共占用 0x8000000 个字节的空间;
  • ranges:空属性;

ethernet:

  • compatible:表示设备类型名称,这里为 "davicom,dm9000",说明该以太网控制器使用的是 DM9000 型号控制器
  • reg:该以太网控制器在地址 0x20000000 处,共占用 2 个字节的空间;地址 0x20000004 处,共占用 2 个字节的空间;
  • interrupt-parent:指定该设备产生中断时,需要向哪个中断控制器发送中断信号。这里指向了全局中断控制器gpf;
  • interrupts:定义该设备所连接的中断号和触发方式。这里它会使用第7号中断,中断触发方式为边沿触发;
  • local-mac-address:该以太网控制器的 MAC 地址,这里设置为 [00 00 de ad be ef]
  • davicom,no-eeprom:表示该设备没有 EEPROM 存储器;

在drivers/net/ethernet/davicom/dm9000.c中可以搜索关键字 “davicom,dm9000”;当platform设备和驱动匹配后,dm9000_probe函数被调用。具体参考博linux驱动移植-DM9000网卡驱动

3.3 串口

3.3.1 uart_0

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。

日期姓名金额
2023-09-06*源19
2023-09-11*朝科88
2023-09-21*号5
2023-09-16*真60
2023-10-26*通9.9
2023-11-04*慎0.66
2023-11-24*恩0.01
2023-12-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
2024-09-06*强8.8
2024-09-09*波1
2024-09-10*口1
2024-09-10*波1
2024-09-12*波10
2024-09-18*明1.68
2024-09-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(830)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2019-04-13 Java基础 -- 字符串(格式化输出、正则表达式)
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示