image

在這一篇文章開始,開始有一點點"門檻"了,不過不用擔心,撇開一些專業知識,基本上照著做就可以加入一個新的 spi driver了.

首先我們必須先知道幾點事情,分別為
evk到底有沒有支援 spi介面?
有幾個spi?
evk上有沒有已經有用到spi的driver可以參考?

 

在 evk的文件-IMX8MMCEC.PDF中,可以看到EVK總共有3個ecspi可以用,evk的ecspi為 Enhanced Configurable Serial Peripheral Interface.

imx 8m ecspi最快速度可以到 52 Mbit/s,每組ecspi有 4 個 chip selects可以用

也就是每個 ecspi bus上可以支援到4個裝置(靠 chip selects去切換). 所以總共可以接 3x4=12個spi 裝置.

image

 

確定有支援spi後,我們可以從 IMX8MMEVKHUG.pdf 文件中 ,查到evk板上expansion connector 的腳位定義

image

 

可以看到"擴充接頭",有預留 ecspi2給使用者接 spi裝置上去開發,因為每個人開發的spi 硬體都不一樣,所以目前我們先不實際接HW上去,先單純嘗試把一個"空殼的spi driver"掛上去

目前我們的目標是在 ecspi2 上,掛上一個 spi driver 上去 

image

 

這邊我想要掛上去的 spi HW是一個 epson d1s1300 ic,它是靠spi與cpu溝通,所以這篇文章中的 code修改就會以這樣的前提來開發.

 

接著我們先看看,目前android P code base有沒有人有用過 ecspi,這樣比較有個參考
我們在 kernel 的 source code下找找看(android_build\vendor\nxp-opensource\kernel_imx\)

search 之後,發現 \kernel_imx\arch\arm64\boot\dts\freescale\fsl-imx8mm-ddr3l-val.dts 檔案中有用到 ecspi1,我們就參考這個檔案,來完成 dtsi與dts的部分.

 

而在fsl-imx8mm.dtsi 文件中,我們可以看到以下內容(ecspi1-ecxpi3-預設status = "disabled";,之後要開啟設定成status = "okay"; )

===

ecspi1: ecspi@30820000 {

#address-cells = <1>;

#size-cells = <0>;

compatible = "fsl,imx8mm-ecspi", "fsl,imx51-ecspi";

reg = <0x0 0x30820000 0x0 0x10000>;

interrupts = <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>;

clocks = <&clk IMX8MM_CLK_ECSPI1_ROOT>,

<&clk IMX8MM_CLK_ECSPI1_ROOT>;

clock-names = "ipg", "per";

dmas = <&sdma1 0 7 1>, <&sdma1 1 7 2>;

dma-names = "rx", "tx";

status = "disabled";

};

 

ecspi2: ecspi@30830000 {

#address-cells = <1>;

#size-cells = <0>;

compatible = "fsl,imx8mm-ecspi", "fsl,imx51-ecspi";

reg = <0x0 0x30830000 0x0 0x10000>;

interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;

clocks = <&clk IMX8MM_CLK_ECSPI2_ROOT>,

<&clk IMX8MM_CLK_ECSPI2_ROOT>;

clock-names = "ipg", "per";

dmas = <&sdma1 2 7 1>, <&sdma1 3 7 2>;

dma-names = "rx", "tx";

status = "disabled";

};

 

ecspi3: ecspi@30840000 {

#address-cells = <1>;

#size-cells = <0>;

compatible = "fsl,imx8mm-ecspi", "fsl,imx51-ecspi";

reg = <0x0 0x30840000 0x0 0x10000>;

interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;

clocks = <&clk IMX8MM_CLK_ECSPI3_ROOT>,

<&clk IMX8MM_CLK_ECSPI3_ROOT>;

clock-names = "ipg", "per";

dmas = <&sdma1 4 7 1>, <&sdma1 5 7 2>;

dma-names = "rx", "tx";

status = "disabled";

};

===

 

這些30820000 memory map的位址,要從文件 IMX8MMRM.pdf 中獲得 (page 29)

image

 

收集完相關資訊後,我們可以開始來改code了,我將修改的檔案整理到下列的表格中

新增spi修改檔案
檔名 修改內容 說明
device/imx8m/evk_8mm/BoardConfig.mk

新增

TARGET_BOARD_DTS_CONFIG += imx8mm-mipi-epson:fsl-imx8mm-evk-s1d13c00.dtb

指定產出 imx8mm-mipi-epson.img,之後用fastboot flash dtbo_a imx8mm-mipi-epson.img 更新到 evk上
/kernel_imx/arch/arm64/boot/dts/freescale/Makefile

新增

fsl-imx8mm-evk-s1d13c00.dtb \   fo imx8mm

 
/kernel_imx/arch/arm64/boot/dts/freescale/fsl-imx8mm-evk.dts

新增 pinctrl 到pinctrl_fec1: fec1grp { 裡面,因為 SPI driver dtsi會用到
        pinctrl_ecspi2: ecspi2grp {
            fsl,pins = <
                MX8MN_IOMUXC_ECSPI2_SCLK_ECSPI2_SCLK        0x82
                MX8MN_IOMUXC_ECSPI2_MOSI_ECSPI2_MOSI        0x82
                MX8MN_IOMUXC_ECSPI2_MISO_ECSPI2_MISO        0x82
            >;
        };

        pinctrl_ecspi2_cs: ecspi2cs {
            fsl,pins = <
                MX8MN_IOMUXC_ECSPI2_SS0_GPIO5_IO13        0x40000
            >;
        };

 
/kernel_imx/arch/arm64/configs/android_defconfig

新增

add CONFIG_DRM_PANEL_EPSON_S1D13C00=y

 
/kernel_imx/arch/arm64/configs/defconfig

新增

add CONFIG_DRM_PANEL_EPSON_S1D13C00=y

 
/kernel_imx/drivers/gpu/drm/panel/Kconfig 加上以下內容
 config DRM_PANEL_EPSON_S1D13C00
    tristate "Epson S1D12C00 MDC with MIP panel"
    depends on OF
    depends on DRM_MIPI_DSI
    depends on BACKLIGHT_CLASS_DEVICE
    help
      Say Y here if you want to enable support for Epson S1D12C00 MDC with MIP panel
      (240x320) panel.
 
/kernel_imx/drivers/gpu/drm/panel/Makefile 加上
obj-$(CONFIG_DRM_PANEL_EPSON_S1D13C00) += panel-epson-s1d13c00.o
 
/kernel_imx/arch/arm64/boot/dts/freescale/fsl-imx8mm-evk-s1d13c00.dts

#include "fsl-imx8mm-evk.dts"

&ecspi2 {
    #address-cells = <1>;
    #size-cells = <0>;
    fsl,spi-num-chipselects = <1>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_ecspi2 &pinctrl_ecspi2_cs>;
    cs-gpios = <&gpio5 13 GPIO_ACTIVE_LOW>;
    status = "okay";

    spidev0: spi@0 {
        reg = <0>;
        compatible = "epson,s1d13c00";
        spi-max-frequency = <500000>;
    };
};

 
/kernel_imx/drivers/gpu/drm/panel/panel-epson-s1d13c00.c 請根據spi driver去建立一個空殼的driver,並吐出 debug message(附在文章最後面) build成功會產出\out\target\product\evk_8mm\obj\KERNEL_OBJ\drivers\gpu\drm\panel\panel-epson-s1d13c00.o
     

 

[make 方法]
.make -j8 bootimage
.make -j8 dtboimage

 

成功 build出image後,可以利用以下fastboot 指令更新兩個 img到裝置上

[更新方法]

.adb reboot bootloader
.fastboot flash boot_a boot.img
.fastboot flash dtbo_a dtbo-imx8mm-epson.img

.fastboot reboot

[驗證]

如果driver有成功 probe到的話,就可以看到driver吐的debug message 如下

[    1.404802] slram: not enough parameters.
[    1.411411] s1d13c00 spi1.0: [mark]s1d13c00_probe+
[    1.416238] s1d13c00 spi1.0: [mark]s1d13c00_probe -
[    1.421155] spi_imx 30830000.ecspi: probed

image

且用 adb root, adb shell, 切換到 cd sys/bus/spi/devices/spi1.0 目錄,cat cat modalias 就可以看到 spi:s1d13c00 已經成功掛上去evk上

cd sys/bus/spi/devices/spi1.0
evk_8mm:/sys/bus/spi/devices/spi1.0 # cat modalias
spi:s1d13c00

image

因為目前s1d13c00 只是空殼,所以就算你 沒有接 spi pin到evk上,還是能成功掛上去

之後如果有拿到真正的硬體,就可以實際接上去做開發.

 

最後附上 panel-epson-s1d13c00.c.

============

#include <linux/gpio/consumer.h>

#include <linux/regulator/consumer.h>

#include <linux/spi/spi.h>

 

#include <drm/drmP.h>

#include <drm/drm_panel.h>

 

#include <video/mipi_display.h>

 

 

 

struct s1d13c00 {

struct drm_panel panel;

struct spi_device *spi;

struct gpio_desc *reset;

struct backlight_device *backlight;

struct regulator *power;

};

 

 

 

static inline struct s1d13c00 *panel_to_s1d13c00(struct drm_panel *panel)

{

return container_of(panel, struct s1d13c00, panel);

}

 

 

static const struct drm_display_mode default_mode = {

.clock = 7000,

.hdisplay = 240,

.hsync_start = 240 + 38,

.hsync_end = 240 + 38 + 10,

.htotal = 240 + 38 + 10 + 10,

.vdisplay = 320,

.vsync_start = 320 + 8,

.vsync_end = 320 + 8 + 4,

.vtotal = 320 + 8 + 4 + 4,

.vrefresh = 60,

};

 

static int s1d13c00_get_modes(struct drm_panel *panel)

{

struct drm_connector *connector = panel->connector;

struct drm_display_mode *mode;

struct s1d13c00 *ctx = panel_to_s1d13c00(panel);

 

dev_err(ctx->panel.dev, "[mark]s1d13c00_get_modes+\n");

mode = drm_mode_duplicate(panel->drm, &default_mode);

if (!mode) {

dev_err(panel->drm->dev, "failed to add mode %ux%ux@%u\n",

default_mode.hdisplay, default_mode.vdisplay,

default_mode.vrefresh);

return -ENOMEM;

}

 

drm_mode_set_name(mode);

 

mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;

drm_mode_probed_add(connector, mode);

 

panel->connector->display_info.width_mm = 22;

panel->connector->display_info.height_mm = 33;

 

return 1;

}

 

static int s1d13c00_enable(struct drm_panel *panel)

{

struct s1d13c00 *ctx = panel_to_s1d13c00(panel);

 

dev_err(ctx->panel.dev, "[mark]s1d13c00_enable+\n");

return 0;

}

 

static int s1d13c00_disable(struct drm_panel *panel)

{

struct s1d13c00 *ctx = panel_to_s1d13c00(panel);

 

dev_err(ctx->panel.dev, "[mark]s1d13c00_disable+\n");

 

return 0;

}

 

static int s1d13c00_prepare(struct drm_panel *panel)

{

struct s1d13c00 *ctx = panel_to_s1d13c00(panel);

int ret;

dev_err(ctx->panel.dev, "[mark]s1d13c00_prepare+\n");

 

ret=0;

return ret;

}

 

static int s1d13c00_unprepare(struct drm_panel *panel)

{

struct s1d13c00 *ctx = panel_to_s1d13c00(panel);

//int ret;

 

dev_err(ctx->panel.dev, "[mark]s1d13c00_unprepare+\n");

 

return 0;

}

 

 

 

static const struct drm_panel_funcs s1d13c00_drm_funcs = {

.disable = s1d13c00_disable,

.enable = s1d13c00_enable,

.get_modes = s1d13c00_get_modes,

.prepare = s1d13c00_prepare,

.unprepare = s1d13c00_unprepare,

};

 

 

static int s1d13c00_probe(struct spi_device *spi)

{

struct device_node *backlight;

struct s1d13c00 *ctx;

int ret;

 

dev_err(&spi->dev, "[mark]s1d13c00_probe+\n");

ctx = devm_kzalloc(&spi->dev, sizeof(*ctx), GFP_KERNEL);

if(!ctx)

return -ENOMEM;;

spi_set_drvdata(spi, ctx);

ctx->spi=spi;

ctx->panel.dev = &spi->dev;

ctx->panel.funcs = &s1d13c00_drm_funcs;

#if 0

ctx->power = devm_regulator_get(&spi->dev, "power");

if (IS_ERR(ctx->power))

return PTR_ERR(ctx->power);

ctx->reset = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_LOW);

if (IS_ERR(ctx->reset)) {

dev_err(&spi->dev, "Couldn't get our reset line\n");

return PTR_ERR(ctx->reset);

}

backlight = of_parse_phandle(spi->dev.of_node, "backlight", 0);

if (backlight) {

ctx->backlight = of_find_backlight_by_node(backlight);

of_node_put(backlight);

 

if (!ctx->backlight)

return -EPROBE_DEFER;

}

#endif

ret = drm_panel_add(&ctx->panel);

if (ret < 0)

goto err_free_backlight;

  dev_err(&spi->dev, "[mark]s1d13c00_probe -\n");

return 0;

err_free_backlight:

if (ctx->backlight)

put_device(&ctx->backlight->dev);

dev_err(&spi->dev, "[mark]s1d13c00_probe -\n");

ret=0;

return ret;

}

 

static int s1d13c00_remove(struct spi_device *spi)

{

struct s1d13c00 *ctx = spi_get_drvdata(spi);

 

drm_panel_detach(&ctx->panel);

drm_panel_remove(&ctx->panel);

 

if (ctx->backlight)

put_device(&ctx->backlight->dev);

dev_err(&spi->dev, "[mark]s1d13c00_remove-\n");

return 0;

}

 

static const struct of_device_id s1d13c00_of_match[] = {

{ .compatible = "epson,s1d13c00" },

{ }

};

MODULE_DEVICE_TABLE(of, s1d13c00_of_match);

 

static struct spi_driver s1d13c00_driver = {

.probe = s1d13c00_probe,

.remove = s1d13c00_remove,

.driver = {

.name = "s1d13c00",

.of_match_table = s1d13c00_of_match,

},

};

 

module_spi_driver(s1d13c00_driver);

 

MODULE_AUTHOR("Mark Yang <mark_yang@usiglobal.com>");

MODULE_DESCRIPTION("EPSON s1d13c00 Memory Dsiplay Driver");

MODULE_LICENSE("GPL v2");

=================

arrow
arrow

    CuteParrot 發表在 痞客邦 留言(1) 人氣()