[PATCH v10 2/2] drm/bridge: Add I2C based driver for ps8640 bridge
From: p.zabel@pengutronix.de (Philipp Zabel)
Date: 2016-02-19 09:15:15
Also in:
dri-devel, linux-devicetree, linux-mediatek, lkml
Am Freitag, den 19.02.2016, 16:31 +0800 schrieb Jitao Shi:
quoted hunk ↗ jump to hunk
This patch adds drm_bridge driver for parade DSI to eDP bridge chip. Signed-off-by: Jitao Shi <redacted> --- Changes since v9: - replace dev_kmalloc with devm_kmalloc in ps8640_get_modes - remove ps_bridge->dsi = devm_kzalloc(dev, sizeof(struct mipi_dsi_device), GFP_KERNEL); - fix wrong condition in ps8640_validate_firmware() The following patches are needed to support dsi host through none dsi bus: https://patchwork.kernel.org/patch/8289181/ ("drm/dsi: check for CONFIG_OF when defining") https://patchwork.kernel.org/patch/8289051/ ("drm/dsi: Use mipi_dsi_device_register_full for DSI device") https://patchwork.kernel.org/patch/8289081/ ("drm/dsi: Try to match non-DT DSI devices") https://patchwork.kernel.org/patch/8289121/ ("drm/dsi: Add routine to unregister a DSI device") https://patchwork.kernel.org/patch/8289091/ ("drm/dsi: Get DSI host by DT device node") --- drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/parade-ps8640.c | 1057 ++++++++++++++++++++++++++++++++ 3 files changed, 1069 insertions(+) create mode 100644 drivers/gpu/drm/bridge/parade-ps8640.cdiff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 27e2022..b4edd8c 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig@@ -40,4 +40,15 @@ config DRM_PARADE_PS8622 ---help--- Parade eDP-LVDS bridge chip driver. +config DRM_PARADE_PS8640 + tristate "Parade PS8640 MIPI DSI to eDP Converter" + depends on OF && I2C + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_PANEL + ---help--- + Choose this option if you have PS8640 for display + The PS8640 is a high-performance and low-power + MIPI DSI to eDP converter + endmenudiff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index f13c33d..fbe38dc 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile@@ -4,3 +4,4 @@ obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o +obj-$(CONFIG_DRM_PARADE_PS8640) += parade-ps8640.odiff --git a/drivers/gpu/drm/bridge/parade-ps8640.c b/drivers/gpu/drm/bridge/parade-ps8640.c new file mode 100644 index 0000000..5f27548 --- /dev/null +++ b/drivers/gpu/drm/bridge/parade-ps8640.c@@ -0,0 +1,1057 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/firmware.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/of_graph.h> +#include <linux/regulator/consumer.h> +#include <asm/unaligned.h> +#include <drm/drm_panel.h> + +#include <drmP.h> +#include <drm_atomic_helper.h> +#include <drm_crtc_helper.h> +#include <drm_crtc.h> +#include <drm_edid.h> +#include <drm_mipi_dsi.h> + +#define PAGE2_SPI_CFG3 0x82 +#define I2C_TO_SPI_RESET 0x20 +#define PAGE2_ROMADD_BYTE1 0x8e +#define PAGE2_ROMADD_BYTE2 0x8f +#define PAGE2_SWSPI_WDATA 0x90 +#define PAGE2_SWSPI_RDATA 0x91 +#define PAGE2_SWSPI_LEN 0x92 +#define PAGE2_SWSPI_CTL 0x93 +#define TRIGGER_NO_READBACK 0x05 +#define TRIGGER_READBACK 0x01 +#define PAGE2_SPI_STATUS 0x9e +#define PAGE2_GPIO_L 0xa6 +#define PAGE2_GPIO_H 0xa7 +#define PS_GPIO9 BIT(1) +#define PAGE2_IROM_CTRL 0xb0 +#define IROM_ENABLE 0xc0 +#define IROM_DISABLE 0x80 +#define PAGE2_SW_REST 0xbc +#define SPI_SW_RESET BIT(7) +#define MPU_SW_RESET BIT(6) +#define PAGE2_ENCTLSPI_WR 0xda +#define PAGE2_I2C_BYPASS 0xea +#define I2C_BYPASS_EN 0xd0 +#define PAGE3_SET_ADD 0xfe +#define PAGE3_SET_VAL 0xff +#define VDO_CTL_ADD 0x13 +#define VDO_DIS 0x18 +#define VDO_EN 0x1c +#define PAGE4_REV_L 0xf0 +#define PAGE4_REV_H 0xf1 +#define PAGE4_CHIP_L 0xf2 +#define PAGE4_CHIP_H 0xf3 + +/* Firmware */ +#define SPI_MAX_RETRY_CNT 8 +#define PS_FW_NAME "ps864x_fw.bin" + +#define FW_CHIP_ID_OFFSET 0 +#define FW_VERSION_OFFSET 2 +#define EDID_I2C_ADDR 0x50 + +#define WRITE_STATUS_REG_CMD 0x01 +#define READ_STATUS_REG_CMD 0x05 +#define CLEAR_ALL_PROTECT 0x00 +#define BLK_PROTECT_BITS 0x0c +#define STATUS_REG_PROTECT BIT(7) +#define WRITE_ENABLE_CMD 0x06 +#define CHIP_ERASE_CMD 0xc7 + +#define bridge_to_ps8640(e) container_of(e, struct ps8640, bridge) +#define connector_to_ps8640(e) container_of(e, struct ps8640, connector) + +struct ps8640_info { + u8 family_id; + u8 variant_id; + u16 version; +}; + +struct ps8640 { + struct drm_connector connector; + struct drm_bridge bridge; + struct edid *edid; + struct mipi_dsi_device dsi; + struct i2c_client *page[8]; + struct i2c_client *ddc_i2c; + struct regulator_bulk_data supplies[2]; + struct drm_panel *panel; + struct gpio_desc *gpio_rst_n; + struct gpio_desc *gpio_slp_n; + struct gpio_desc *gpio_mode_sel_n; + bool enabled; + + /* firmware file info */ + bool in_fw_update; + struct ps8640_info info; +}; + +static const u8 enc_ctrl_code[6] = {0xaa, 0x55, 0x50, 0x41, 0x52, 0x44}; + +static int ps8640_read(struct i2c_client *client, u8 reg, u8 *data, + u16 data_len) +{ + int ret; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = ®, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = data_len, + .buf = data, + } + }; + + ret = i2c_transfer(client->adapter, msgs, 2); + + if (ret == 2) + return 0; + if (ret < 0) + return ret; + else + return -EIO; +} + +static int ps8640_write_bytes(struct i2c_client *client, u8 *data, + u16 data_len) +{ + int ret; + struct i2c_msg msg; + + msg.addr = client->addr; + msg.flags = 0; + msg.len = data_len; + msg.buf = data; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret == 1) + return 0; + if (ret < 0) + return ret; + else + return -EIO; +} + +static int ps8640_write_byte(struct i2c_client *client, u8 reg, u8 data) +{ + int ret; + struct i2c_msg msg; + u8 buf[] = {reg, data}; + + msg.addr = client->addr; + msg.flags = 0; + msg.len = sizeof(buf); + msg.buf = buf; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret == 1) + return 0; + if (ret < 0) + return ret; + else + return -EIO; +} + +static void ps8640_get_mcu_fw_version(struct ps8640 *ps_bridge) +{ + struct i2c_client *client = ps_bridge->page[5]; + u8 fw_ver[2]; + + ps8640_read(client, 0x4, fw_ver, 2); + ps_bridge->info.version = (fw_ver[0] << 8) | fw_ver[1]; + + DRM_INFO_ONCE("ps8640 rom fw version %d.%d\n", fw_ver[0], fw_ver[1]); +} + +static int ps8640_bridge_enable(struct ps8640 *ps_bridge) +{ + struct i2c_client *client = ps_bridge->page[3]; + u8 vdo_ctrl_buf[3] = {PAGE3_SET_ADD, VDO_CTL_ADD, VDO_EN}; + + return ps8640_write_bytes(client, vdo_ctrl_buf, 3); +} + +static int ps8640_bridge_disable(struct ps8640 *ps_bridge) +{ + struct i2c_client *client = ps_bridge->page[3]; + u8 vdo_ctrl_buf[3] = {PAGE3_SET_ADD, VDO_CTL_ADD, VDO_DIS}; + + return ps8640_write_bytes(client, vdo_ctrl_buf, 3); +} + +static void ps8640_pre_enable(struct drm_bridge *bridge) +{ + struct ps8640 *ps_bridge = bridge_to_ps8640(bridge); + struct i2c_client *client = ps_bridge->page[2]; + int err, retry_cnt = 0; + u8 set_vdo_done; + + if (ps_bridge->in_fw_update) + return; + + if (ps_bridge->enabled) + return; + + err = drm_panel_prepare(ps_bridge->panel); + if (err < 0) { + DRM_ERROR("failed to prepare panel: %d\n", err); + return; + } + + gpiod_set_value(ps_bridge->gpio_slp_n, 1);
Is this part:
+ gpiod_set_value(ps_bridge->gpio_rst_n, 0);
+
+ err = regulator_bulk_enable(ARRAY_SIZE(ps_bridge->supplies),
+ ps_bridge->supplies);
+ if (err < 0) {
+ DRM_ERROR("cannot enable regulators %d\n", err);
+ goto err_panel_unprepare;
+ }
+
+ usleep_range(500, 700);
+ gpiod_set_value(ps_bridge->gpio_rst_n, 1);.. until here an assertion of the reset for 500 ?s plus whatever the regulator delay is (if any)? It looks like the reset actually is active low. If so, I think this code should rather be: gpiod_set_value(ps_bridge->gpio_rst_n, 1); regulator_bulk_enable(); usleep_range(); gpiod_set_value(ps_bridge->gpio_rst_n, 0); And the device tree should contain a reset-gpios property with the GPIO_ACTIVE_LOW flag set. Same goes for sleep GPIO, if it is active low, too. best regards Philipp