在這一篇文章開始,開始有一點點"門檻"了,不過不用擔心,撇開一些專業知識,基本上照著做就可以加入一個新的 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 裝置.
確定有支援spi後,我們可以從 IMX8MMEVKHUG.pdf 文件中 ,查到evk板上expansion connector 的腳位定義
可以看到"擴充接頭",有預留 ecspi2給使用者接 spi裝置上去開發,因為每個人開發的spi 硬體都不一樣,所以目前我們先不實際接HW上去,先單純嘗試把一個"空殼的spi driver"掛上去
目前我們的目標是在 ecspi2 上,掛上一個 spi driver 上去
這邊我想要掛上去的 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)
收集完相關資訊後,我們可以開始來改code了,我將修改的檔案整理到下列的表格中
檔名 | 修改內容 | 說明 |
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_cs: ecspi2cs { |
|
/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 { spidev0: spi@0 { |
|
/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
且用 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
因為目前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");
=================
留言列表