* [PATCH 00/16] ARM: stm32mp: add MIPI DSI support
@ 2025-06-05 21:07 Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 01/16] driver: bus: embed bus driver node into bus Ahmad Fatoum
` (15 more replies)
0 siblings, 16 replies; 17+ messages in thread
From: Ahmad Fatoum @ 2025-06-05 21:07 UTC (permalink / raw)
To: barebox
This enables barebox to make use of the MIPI DSI display on the DK2.
The RK35xx also has a Designware MIPI-DSI host and there's already VOP2
support, so that will likely follow in future.
Ahmad Fatoum (16):
driver: bus: embed bus driver node into bus
driver: switch busses to device class
driver: factor out bus definitions into separate header
driver: bus: add helpers for finding devices in busses
drive: bus: make use of new bus_find_device helper
of: implement of_alias_from_compatible
video: vpl: fix potential read of uninitialized variable
video: vpl: factor out vpl_for_each
video: vpl: handle missing struct vpl::ioctl gracefully
video: vpl: add vpl_bridge abstraction
video: factor out drm_mode_vrefresh
video: add base MIPI DSI support
video: add Designware MIPI-DSI support
video: add STM32 MIPI DSI video driver
video: add support for Orise Technology otm8009a panel
ARM: stm32mp: dk2: enable MIPI-DSI display by default
arch/arm/boards/stm32mp15xx-dkx/Makefile | 1 +
arch/arm/boards/stm32mp15xx-dkx/board.c | 3 +
.../defaultenv-stm32mp15xx-dkx/init/splash | 18 +
arch/arm/configs/stm32mp_defconfig | 18 +-
commands/devinfo.c | 2 +-
common/tlv/bus.c | 4 +-
drivers/amba/bus.c | 2 +-
drivers/base/bus.c | 68 +-
drivers/base/driver.c | 2 +-
drivers/efi/efi-device.c | 17 +-
drivers/i2c/i2c.c | 6 +-
drivers/mci/mci-core.c | 14 +-
drivers/net/phy/mdio_bus.c | 6 +-
drivers/net/phy/phy.c | 7 +-
drivers/of/base.c | 31 +
drivers/tee/tee_core.c | 7 +-
drivers/tee/tee_private.h | 2 -
drivers/usb/gadget/udc/core.c | 3 +
drivers/video/Kconfig | 35 +
drivers/video/Makefile | 5 +
drivers/video/backlight.c | 7 +-
drivers/video/drm/Makefile | 2 +
drivers/video/drm/drm_modes.c | 67 +
drivers/video/dw_mipi_dsi.c | 1042 ++++++++++
drivers/video/mipi_dsi.c | 1730 +++++++++++++++++
drivers/video/panel-orisetech-otm8009a.c | 505 +++++
drivers/video/rockchip/rockchip_drm_vop2.c | 13 -
drivers/video/stm32_dsi.c | 457 +++++
drivers/video/vpl.c | 91 +-
drivers/w1/w1.c | 4 +-
include/driver.h | 33 +-
include/i2c/i2c.h | 5 +-
include/linux/device.h | 22 -
include/linux/device/bus.h | 80 +
include/linux/gpio/consumer.h | 3 +
include/linux/phy.h | 6 +-
include/module.h | 26 +
include/of.h | 1 +
include/video/backlight.h | 15 +-
include/video/drm/drm_connector.h | 2 +
include/video/drm/drm_modes.h | 2 +
include/video/dw_mipi_dsi.h | 81 +
include/video/mipi_dsi.h | 618 ++++++
include/video/vpl.h | 15 +
44 files changed, 4946 insertions(+), 132 deletions(-)
create mode 100755 arch/arm/boards/stm32mp15xx-dkx/defaultenv-stm32mp15xx-dkx/init/splash
create mode 100644 drivers/video/drm/Makefile
create mode 100644 drivers/video/drm/drm_modes.c
create mode 100644 drivers/video/dw_mipi_dsi.c
create mode 100644 drivers/video/mipi_dsi.c
create mode 100644 drivers/video/panel-orisetech-otm8009a.c
create mode 100644 drivers/video/stm32_dsi.c
create mode 100644 include/linux/device/bus.h
create mode 100644 include/video/dw_mipi_dsi.h
create mode 100644 include/video/mipi_dsi.h
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 01/16] driver: bus: embed bus driver node into bus
2025-06-05 21:07 [PATCH 00/16] ARM: stm32mp: add MIPI DSI support Ahmad Fatoum
@ 2025-06-05 21:07 ` Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 02/16] driver: switch busses to device class Ahmad Fatoum
` (14 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ahmad Fatoum @ 2025-06-05 21:07 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
Instead of keeping the bus device in a separate allocation, fold it into
the bus struct itself.
The benefit is that this would allow us to arrive at the bus from the
device and thus be able to chain them using device classes.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
commands/devinfo.c | 2 +-
common/tlv/bus.c | 4 ++--
drivers/amba/bus.c | 2 +-
drivers/base/bus.c | 7 +++----
drivers/base/driver.c | 2 +-
include/driver.h | 2 +-
6 files changed, 9 insertions(+), 10 deletions(-)
diff --git a/commands/devinfo.c b/commands/devinfo.c
index b1918787a3b6..3c791e4464ac 100644
--- a/commands/devinfo.c
+++ b/commands/devinfo.c
@@ -84,7 +84,7 @@ static int do_devinfo(int argc, char *argv[])
devinfo(dev);
- if (dev->parent && (!dev->bus || dev->bus->dev != dev->parent))
+ if (dev->parent && (!dev->bus || &dev->bus->dev != dev->parent))
printf("Parent: %s\n", dev_name(dev->parent));
first = true;
diff --git a/common/tlv/bus.c b/common/tlv/bus.c
index ab0f4f8b82aa..62d7b7504576 100644
--- a/common/tlv/bus.c
+++ b/common/tlv/bus.c
@@ -29,7 +29,7 @@ struct tlv_device *tlv_register_device(struct tlv_header *header,
devinfo_add(dev, tlv_devinfo);
dev->platform_data = header;
tlvdev->magic = be32_to_cpu(header->magic);
- dev->parent = parent ?: tlv_bus.dev;
+ dev->parent = parent ?: &tlv_bus.dev;
dev->id = DEVICE_ID_SINGLE;
if (parent)
@@ -122,7 +122,7 @@ static int tlv_bus_register(void)
if (ret)
return ret;
- devinfo_add(tlv_bus.dev, tlv_bus_info);
+ devinfo_add(&tlv_bus.dev, tlv_bus_info);
return 0;
}
diff --git a/drivers/amba/bus.c b/drivers/amba/bus.c
index 8d6525cca967..4e10dd72368f 100644
--- a/drivers/amba/bus.c
+++ b/drivers/amba/bus.c
@@ -143,7 +143,7 @@ int amba_device_add(struct amba_device *dev)
cid = amba_device_get_cid(tmp, size);
if (IS_ENABLED(CONFIG_ARM_AMBA_DABT_MASK) && data_abort_unmask()) {
- dev_err(amba_bustype.dev,
+ dev_err(&amba_bustype.dev,
"data abort during MMIO read of PID/CID for %pOF\n",
dev->dev.of_node);
ret = -EFAULT;
diff --git a/drivers/base/bus.c b/drivers/base/bus.c
index 58fcd2e032da..6a4e654b3cea 100644
--- a/drivers/base/bus.c
+++ b/drivers/base/bus.c
@@ -30,11 +30,10 @@ int bus_register(struct bus_type *bus)
if (get_bus_by_name(bus->name))
return -EEXIST;
- bus->dev = xzalloc(sizeof(*bus->dev));
- dev_set_name(bus->dev, bus->name);
- bus->dev->id = DEVICE_ID_SINGLE;
+ dev_set_name(&bus->dev, bus->name);
+ bus->dev.id = DEVICE_ID_SINGLE;
- ret = register_device(bus->dev);
+ ret = register_device(&bus->dev);
if (ret)
return ret;
diff --git a/drivers/base/driver.c b/drivers/base/driver.c
index 2192ba9812cb..21bf88a74e50 100644
--- a/drivers/base/driver.c
+++ b/drivers/base/driver.c
@@ -269,7 +269,7 @@ int register_device(struct device *new_device)
if (new_device->bus) {
if (!new_device->parent)
- new_device->parent = new_device->bus->dev;
+ new_device->parent = &new_device->bus->dev;
list_add_tail(&new_device->bus_list, &new_device->bus->device_list);
diff --git a/include/driver.h b/include/driver.h
index e9a919f9bbb5..c6b7b37aa00f 100644
--- a/include/driver.h
+++ b/include/driver.h
@@ -358,7 +358,7 @@ struct bus_type {
int (*probe)(struct device *dev);
void (*remove)(struct device *dev);
- struct device *dev;
+ struct device dev;
struct list_head list;
struct list_head device_list;
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 02/16] driver: switch busses to device class
2025-06-05 21:07 [PATCH 00/16] ARM: stm32mp: add MIPI DSI support Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 01/16] driver: bus: embed bus driver node into bus Ahmad Fatoum
@ 2025-06-05 21:07 ` Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 03/16] driver: factor out bus definitions into separate header Ahmad Fatoum
` (13 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ahmad Fatoum @ 2025-06-05 21:07 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
We have a lot of ad-hoc bus/adapter lists. Let's replace them with
device classes.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
drivers/base/bus.c | 5 ++---
drivers/i2c/i2c.c | 6 +++---
drivers/mci/mci-core.c | 14 +++++++++-----
drivers/net/phy/mdio_bus.c | 6 +++---
drivers/tee/tee_core.c | 7 +++----
drivers/tee/tee_private.h | 2 --
drivers/usb/gadget/udc/core.c | 3 +++
drivers/video/backlight.c | 7 +++----
drivers/w1/w1.c | 4 ++--
include/driver.h | 5 ++---
include/i2c/i2c.h | 5 ++---
include/linux/phy.h | 6 ++----
include/video/backlight.h | 1 -
13 files changed, 34 insertions(+), 37 deletions(-)
diff --git a/drivers/base/bus.c b/drivers/base/bus.c
index 6a4e654b3cea..fde27dd008e8 100644
--- a/drivers/base/bus.c
+++ b/drivers/base/bus.c
@@ -8,8 +8,7 @@
#include <errno.h>
#include <of.h>
-LIST_HEAD(bus_list);
-EXPORT_SYMBOL(bus_list);
+DEFINE_DEV_CLASS(bus_class, "bus");
static struct bus_type *get_bus_by_name(const char *name)
{
@@ -41,7 +40,7 @@ int bus_register(struct bus_type *bus)
INIT_LIST_HEAD(&bus->device_list);
INIT_LIST_HEAD(&bus->driver_list);
- list_add_tail(&bus->list, &bus_list);
+ class_add_device(&bus_class, &bus->dev);
return 0;
}
diff --git a/drivers/i2c/i2c.c b/drivers/i2c/i2c.c
index 49b87bb47c00..7ebd9c11e600 100644
--- a/drivers/i2c/i2c.c
+++ b/drivers/i2c/i2c.c
@@ -42,7 +42,7 @@ struct boardinfo {
};
static LIST_HEAD(board_list);
-LIST_HEAD(i2c_adapter_list);
+DEFINE_DEV_CLASS(i2c_adapter_class, "i2c_adapter");
/**
* i2c_transfer - execute a single or combined I2C message
@@ -483,7 +483,7 @@ static void i2c_hw_rescan(struct device *dev)
{
struct i2c_adapter *adap;
- list_for_each_entry(adap, &i2c_adapter_list, list) {
+ for_each_i2c_adapter(adap) {
if (dev != adap->dev.parent)
continue;
of_i2c_register_devices(adap);
@@ -742,7 +742,7 @@ int i2c_add_numbered_adapter(struct i2c_adapter *adapter)
if (ret)
return ret;
- list_add_tail(&adapter->list, &i2c_adapter_list);
+ class_add_device(&i2c_adapter_class, &adapter->dev);
slice_init(&adapter->slice, dev_name(&adapter->dev));
diff --git a/drivers/mci/mci-core.c b/drivers/mci/mci-core.c
index 23cde58c0c5b..4ce5327a7047 100644
--- a/drivers/mci/mci-core.c
+++ b/drivers/mci/mci-core.c
@@ -40,7 +40,11 @@ static inline u32 unstuff_bits(const u32 *resp, int start, int size)
return __res & __mask;
}
-LIST_HEAD(mci_list);
+static DEFINE_DEV_CLASS(mmc_class, "mmc");
+
+#define for_each_mci(mci) \
+ class_for_each_container_of_device(&mmc_class, mci, dev)
+
/**
* @file
@@ -3033,7 +3037,7 @@ static int mci_hw_detect(struct device *dev)
{
struct mci *mci;
- list_for_each_entry(mci, &mci_list, list) {
+ for_each_mci(mci) {
if (dev == mci->host->hw_dev)
return mci_detect_card(mci->host);
}
@@ -3111,7 +3115,7 @@ int mci_register(struct mci_host *host)
of_register_fixup(of_broken_cd_fixup, host);
}
- list_add_tail(&mci->list, &mci_list);
+ class_add_device(&mmc_class, &mci->dev);
return 0;
@@ -3245,7 +3249,7 @@ struct mci *mci_get_device_by_name(const char *name)
{
struct mci *mci;
- list_for_each_entry(mci, &mci_list, list) {
+ for_each_mci(mci) {
if (!mci->cdevname)
continue;
if (!strcmp(mci->cdevname, name))
@@ -3259,7 +3263,7 @@ struct mci *mci_get_rpmb_dev(unsigned int id)
{
struct mci *mci;
- list_for_each_entry(mci, &mci_list, list) {
+ for_each_mci(mci) {
if (mci->host->of_id != id)
continue;
diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c
index a6671a2680ad..a7eda3f84a11 100644
--- a/drivers/net/phy/mdio_bus.c
+++ b/drivers/net/phy/mdio_bus.c
@@ -28,7 +28,7 @@
#define DEFAULT_GPIO_RESET_ASSERT 1000 /* us */
#define DEFAULT_GPIO_RESET_DEASSERT 1000 /* us */
-LIST_HEAD(mii_bus_list);
+DEFINE_DEV_CLASS(mii_class, "mii");
static struct phy_device *mdio_device_create(struct mii_bus *bus, int addr)
{
@@ -312,7 +312,7 @@ int mdiobus_register(struct mii_bus *bus)
if (bus->reset)
bus->reset(bus);
- list_add_tail(&bus->list, &mii_bus_list);
+ class_add_device(&mii_class, &bus->dev);
pr_info("%s: probed\n", dev_name(&bus->dev));
@@ -342,7 +342,7 @@ void mdiobus_unregister(struct mii_bus *bus)
slice_exit(&bus->slice);
- list_del(&bus->list);
+ list_del_init(&bus->dev.class_list);
}
EXPORT_SYMBOL(mdiobus_unregister);
diff --git a/drivers/tee/tee_core.c b/drivers/tee/tee_core.c
index 593e85313b0a..653b04ff06af 100644
--- a/drivers/tee/tee_core.c
+++ b/drivers/tee/tee_core.c
@@ -16,7 +16,7 @@
#define TEE_IOCTL_PARAM_SIZE(x) (sizeof(struct tee_param) * (x))
-static LIST_HEAD(tee_clients);
+static DEFINE_DEV_CLASS(tee_client_class, "tee_client");
struct tee_context *teedev_open(struct tee_device *teedev)
{
@@ -591,7 +591,7 @@ int tee_device_register(struct tee_device *teedev)
goto out;
}
- list_add_tail(&teedev->list, &tee_clients);
+ class_add_device(&tee_client_class, &teedev->dev);
teedev->flags |= TEE_DEVICE_FLAG_REGISTERED;
return 0;
@@ -640,7 +640,6 @@ void tee_device_unregister(struct tee_device *teedev)
if (!teedev)
return;
- list_del(&teedev->list);
if (IS_ENABLED(CONFIG_OPTEE_DEVFS))
devfs_remove(&teedev->cdev);
unregister_device(&teedev->dev);
@@ -687,7 +686,7 @@ tee_client_open_context(struct tee_context *start,
if (start)
startdev = &start->teedev->dev;
- list_for_each_entry(teedev, &tee_clients, list) {
+ class_for_each_container_of_device(&tee_client_class, teedev, dev) {
struct device *dev = &teedev->dev;
struct tee_context *ctx ;
diff --git a/drivers/tee/tee_private.h b/drivers/tee/tee_private.h
index 045f2df9f3b4..e349613d7868 100644
--- a/drivers/tee/tee_private.h
+++ b/drivers/tee/tee_private.h
@@ -19,7 +19,6 @@ struct tee_context;
* struct tee_device - TEE Device representation
* @name: name of device
* @desc: description of device
- * @id: unique id of device
* @flags: represented by TEE_DEVICE_FLAG_REGISTERED above
* @dev: embedded basic device structure
* @cdev: embedded cdev
@@ -29,7 +28,6 @@ struct tee_context;
struct tee_device {
char name[TEE_MAX_DEV_NAME_LEN];
const struct tee_desc *desc;
- struct list_head list;
unsigned int flags;
struct device dev;
diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c
index 4416902bc143..14bd5f493350 100644
--- a/drivers/usb/gadget/udc/core.c
+++ b/drivers/usb/gadget/udc/core.c
@@ -46,6 +46,7 @@ struct usb_udc {
};
static LIST_HEAD(udc_list);
+static DEFINE_DEV_CLASS(udc_class, "udc");
/* Protects udc_list, udc->driver, driver->is_bound, and related calls */
static DEFINE_MUTEX(udc_lock);
@@ -1194,6 +1195,8 @@ int usb_add_gadget(struct usb_gadget *gadget)
if (ret)
goto err_free_id;
+ class_add_device(&udc_class, &udc->dev);
+
dev_add_param_uint32(&gadget->dev, "product", NULL, NULL,
&gadget->product_id, "0x%04x", NULL);
dev_add_param_uint32(&gadget->dev, "vendor", NULL, NULL,
diff --git a/drivers/video/backlight.c b/drivers/video/backlight.c
index 523dd7e9f324..51d245a80fdb 100644
--- a/drivers/video/backlight.c
+++ b/drivers/video/backlight.c
@@ -1,10 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <common.h>
#include <driver.h>
-#include <linux/list.h>
#include <video/backlight.h>
-static LIST_HEAD(backlights);
+static DEFINE_DEV_CLASS(backlight_class, "backlight");
int backlight_set_brightness(struct backlight_device *bl, unsigned brightness)
{
@@ -84,7 +83,7 @@ int backlight_register(struct backlight_device *bl)
dev_add_param_uint32(&bl->dev, "slew_time_ms", NULL, NULL,
&bl->slew_time_ms, "%u", NULL);
- list_add_tail(&bl->list, &backlights);
+ class_add_device(&backlight_class, &bl->dev);
return ret;
}
@@ -95,7 +94,7 @@ struct backlight_device *of_backlight_find(struct device_node *node)
of_device_ensure_probed(node);
- list_for_each_entry(bl, &backlights, list)
+ class_for_each_container_of_device(&backlight_class, bl, dev)
if (bl->node == node)
return bl;
diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c
index 15ada42ef0ba..61f21ff446b8 100644
--- a/drivers/w1/w1.c
+++ b/drivers/w1/w1.c
@@ -10,7 +10,7 @@
#include "w1.h"
-static LIST_HEAD(w1_buses);
+static DEFINE_DEV_CLASS(w1_bus_class, "w1_bus");
static void w1_pre_write(struct w1_bus *bus);
static void w1_post_write(struct w1_bus *bus);
@@ -608,7 +608,7 @@ int w1_bus_register(struct w1_bus *bus)
if (!bus->max_slave_count)
bus->max_slave_count = 10;
- list_add_tail(&bus->list, &w1_buses);
+ class_add_device(&w1_bus_class, &bus->dev);
dev_set_name(&bus->dev, "w1_bus");
bus->dev.id = DEVICE_ID_DYNAMIC;
diff --git a/include/driver.h b/include/driver.h
index c6b7b37aa00f..581f59754bab 100644
--- a/include/driver.h
+++ b/include/driver.h
@@ -360,7 +360,6 @@ struct bus_type {
struct device dev;
- struct list_head list;
struct list_head device_list;
struct list_head driver_list;
};
@@ -374,11 +373,11 @@ static inline int driver_match_device(const struct driver *drv,
return drv->bus->match ? drv->bus->match(dev, drv) : true;
}
-extern struct list_head bus_list;
+extern struct class bus_class;
/* Iterate over all buses
*/
-#define for_each_bus(bus) list_for_each_entry(bus, &bus_list, list)
+#define for_each_bus(bus) class_for_each_container_of_device(&bus_class, bus, dev)
/* Iterate over all devices of a bus
*/
diff --git a/include/i2c/i2c.h b/include/i2c/i2c.h
index 9094003b686a..5c0174b0ddc7 100644
--- a/include/i2c/i2c.h
+++ b/include/i2c/i2c.h
@@ -125,7 +125,6 @@ struct i2c_adapter {
struct slice slice;
int nr; /* bus number */
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
- struct list_head list;
int retries;
void *algo_data;
@@ -304,9 +303,9 @@ int of_i2c_device_enable_and_register_by_alias(const char *alias);
void i2c_parse_fw_timings(struct device *dev, struct i2c_timings *t,
bool use_defaults);
-extern struct list_head i2c_adapter_list;
+extern struct class i2c_adapter_class;
#define for_each_i2c_adapter(adap) \
- list_for_each_entry(adap, &i2c_adapter_list, list)
+ class_for_each_container_of_device(&i2c_adapter_class, adap, dev)
/* For devices that use several addresses, use i2c_new_dummy() to make
* client handles for the extra addresses.
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 5cbd21f91a50..04b82d63a51e 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -240,8 +240,6 @@ struct mii_bus {
/* PHY addresses to be ignored when probing */
u32 phy_mask;
- struct list_head list;
-
bool is_multiplexed;
struct slice slice;
@@ -257,12 +255,12 @@ int mdiobus_register(struct mii_bus *bus);
void mdiobus_unregister(struct mii_bus *bus);
struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr);
-extern struct list_head mii_bus_list;
+extern struct class mii_class;
int mdiobus_detect(struct device *dev);
#define for_each_mii_bus(mii) \
- list_for_each_entry(mii, &mii_bus_list, list)
+ class_for_each_container_of_device(&mii_class, mii, dev)
struct mii_bus *mdiobus_get_bus(int busnum);
diff --git a/include/video/backlight.h b/include/video/backlight.h
index 2dd63202cd6e..dcb8e8c5e10e 100644
--- a/include/video/backlight.h
+++ b/include/video/backlight.h
@@ -11,7 +11,6 @@ struct backlight_device {
int brightness_default;
int slew_time_ms; /* time to stretch brightness changes */
int (*brightness_set)(struct backlight_device *, int brightness);
- struct list_head list;
struct device dev;
struct device_node *node;
};
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 03/16] driver: factor out bus definitions into separate header
2025-06-05 21:07 [PATCH 00/16] ARM: stm32mp: add MIPI DSI support Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 01/16] driver: bus: embed bus driver node into bus Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 02/16] driver: switch busses to device class Ahmad Fatoum
@ 2025-06-05 21:07 ` Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 04/16] driver: bus: add helpers for finding devices in busses Ahmad Fatoum
` (12 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ahmad Fatoum @ 2025-06-05 21:07 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
driver.h combines a lot of driver model related definitions making it
inconvenient to include from other headers. Let's split out the bus type
parts.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
include/driver.h | 32 +----------------------------
include/linux/device/bus.h | 42 ++++++++++++++++++++++++++++++++++++++
2 files changed, 43 insertions(+), 31 deletions(-)
create mode 100644 include/linux/device/bus.h
diff --git a/include/driver.h b/include/driver.h
index 581f59754bab..a2337f2f4f95 100644
--- a/include/driver.h
+++ b/include/driver.h
@@ -12,6 +12,7 @@
#include <linux/printk.h>
#include <linux/module.h>
#include <device.h>
+#include <linux/device/bus.h>
#include <of.h>
#include <init.h>
#include <errno.h>
@@ -352,43 +353,12 @@ ssize_t mem_copy(struct device *dev, void *dst, const void *src,
int generic_memmap_ro(struct cdev *dev, void **map, int flags);
int generic_memmap_rw(struct cdev *dev, void **map, int flags);
-struct bus_type {
- char *name;
- int (*match)(struct device *dev, const struct driver *drv);
- int (*probe)(struct device *dev);
- void (*remove)(struct device *dev);
-
- struct device dev;
-
- struct list_head device_list;
- struct list_head driver_list;
-};
-
-int bus_register(struct bus_type *bus);
-int device_match(struct device *dev, const struct driver *drv);
-
static inline int driver_match_device(const struct driver *drv,
struct device *dev)
{
return drv->bus->match ? drv->bus->match(dev, drv) : true;
}
-extern struct class bus_class;
-
-/* Iterate over all buses
- */
-#define for_each_bus(bus) class_for_each_container_of_device(&bus_class, bus, dev)
-
-/* Iterate over all devices of a bus
- */
-#define bus_for_each_device(bus, dev) list_for_each_entry(dev, &(bus)->device_list, bus_list)
-
-/* Iterate over all drivers of a bus
- */
-#define bus_for_each_driver(bus, drv) list_for_each_entry(drv, &(bus)->driver_list, bus_list)
-
-extern struct bus_type platform_bus;
-
int platform_driver_register(struct driver *drv);
/* register_driver_macro() - Helper macro for drivers that don't do
diff --git a/include/linux/device/bus.h b/include/linux/device/bus.h
new file mode 100644
index 000000000000..8837ce3c2ed2
--- /dev/null
+++ b/include/linux/device/bus.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * (C) 2007 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de>
+ */
+
+#ifndef __BUS_H
+#define __BUS_H
+
+#include <device.h>
+
+struct bus_type {
+ char *name;
+ int (*match)(struct device *dev, const struct driver *drv);
+ int (*probe)(struct device *dev);
+ void (*remove)(struct device *dev);
+
+ struct device dev;
+
+ struct list_head device_list;
+ struct list_head driver_list;
+};
+
+int bus_register(struct bus_type *bus);
+int device_match(struct device *dev, const struct driver *drv);
+
+extern struct class bus_class;
+
+/* Iterate over all buses
+ */
+#define for_each_bus(bus) class_for_each_container_of_device(&bus_class, bus, dev)
+
+/* Iterate over all devices of a bus
+ */
+#define bus_for_each_device(bus, dev) list_for_each_entry(dev, &(bus)->device_list, bus_list)
+
+/* Iterate over all drivers of a bus
+ */
+#define bus_for_each_driver(bus, drv) list_for_each_entry(drv, &(bus)->driver_list, bus_list)
+
+extern struct bus_type platform_bus;
+
+#endif
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 04/16] driver: bus: add helpers for finding devices in busses
2025-06-05 21:07 [PATCH 00/16] ARM: stm32mp: add MIPI DSI support Ahmad Fatoum
` (2 preceding siblings ...)
2025-06-05 21:07 ` [PATCH 03/16] driver: factor out bus definitions into separate header Ahmad Fatoum
@ 2025-06-05 21:07 ` Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 05/16] drive: bus: make use of new bus_find_device helper Ahmad Fatoum
` (11 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ahmad Fatoum @ 2025-06-05 21:07 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
To make porting Linux drivers easier, let's import some bus lookup
functions that are going to be used in later commits.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
drivers/base/bus.c | 56 ++++++++++++++++++++++++++++++++++++++
include/linux/device.h | 22 ---------------
include/linux/device/bus.h | 38 ++++++++++++++++++++++++++
3 files changed, 94 insertions(+), 22 deletions(-)
diff --git a/drivers/base/bus.c b/drivers/base/bus.c
index fde27dd008e8..8edd8cb01fae 100644
--- a/drivers/base/bus.c
+++ b/drivers/base/bus.c
@@ -95,3 +95,59 @@ int device_match_of_modalias(struct device *dev, const struct driver *drv)
return false;
}
+
+static struct device *__bus_for_each_dev(const struct bus_type *bus, struct device *start, void *data,
+ int (*fn)(struct device *dev, void *data), int *result)
+{
+ struct device *dev;
+ int ret;
+
+ bus_for_each_device(bus, dev) {
+ if (start) {
+ if (dev == start)
+ start = NULL;
+ continue;
+ }
+
+ ret = fn(dev, data);
+ if (ret) {
+ if (result)
+ *result = ret;
+ return dev;
+ }
+ }
+
+ return NULL;
+}
+
+int bus_for_each_dev(const struct bus_type *bus, struct device *start, void *data,
+ int (*fn)(struct device *dev, void *data))
+{
+ int ret = 0;
+ __bus_for_each_dev(bus, start, data, fn, &ret);
+ return ret;
+}
+
+struct check_match_data {
+ device_match_t match;
+};
+
+struct device *bus_find_device(const struct bus_type *bus, struct device *start,
+ const void *data, device_match_t match)
+{
+ return __bus_for_each_dev(bus, start, (void *)data,
+ (int (*)(struct device *dev, void *data))match,
+ NULL);
+}
+
+int device_match_name(struct device *dev, const void *name)
+{
+ return !strcmp(dev_name(dev), name);
+}
+EXPORT_SYMBOL_GPL(device_match_name);
+
+int device_match_of_node(struct device *dev, const void *np)
+{
+ return np && dev->of_node == np;
+}
+EXPORT_SYMBOL_GPL(device_match_of_node);
diff --git a/include/linux/device.h b/include/linux/device.h
index d4098b85239f..e3934b505fe0 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -52,28 +52,6 @@ static inline void __iomem *devm_ioremap(struct device *dev,
return IOMEM(start);
}
-static inline int bus_for_each_dev(const struct bus_type *bus, struct device *start, void *data,
- int (*fn)(struct device *dev, void *data))
-{
- struct device *dev;
- int ret;
-
- bus_for_each_device(bus, dev) {
- if (start) {
- if (dev == start)
- start = NULL;
- continue;
- }
-
- ret = fn(dev, data);
- if (ret)
- return ret;
- }
-
- return 0;
-}
-
-
/**
* dev_set_drvdata - set driver private data for device
* @dev: device instance
diff --git a/include/linux/device/bus.h b/include/linux/device/bus.h
index 8837ce3c2ed2..2364645bad4a 100644
--- a/include/linux/device/bus.h
+++ b/include/linux/device/bus.h
@@ -37,6 +37,44 @@ extern struct class bus_class;
*/
#define bus_for_each_driver(bus, drv) list_for_each_entry(drv, &(bus)->driver_list, bus_list)
+int bus_for_each_dev(const struct bus_type *bus, struct device *start, void *data,
+ int (*fn)(struct device *dev, void *data));
+
+/* Generic device matching functions that all busses can use to match with */
+int device_match_name(struct device *dev, const void *name);
+int device_match_of_node(struct device *dev, const void *np);
+
+/* Matching function type for drivers/base APIs to find a specific device */
+typedef int (*device_match_t)(struct device *dev, const void *data);
+
+struct device *bus_find_device(const struct bus_type *bus, struct device *start,
+ const void *data, device_match_t match);
+/**
+ * bus_find_device_by_name - device iterator for locating a particular device
+ * of a specific name.
+ * @bus: bus type
+ * @start: Device to begin with
+ * @name: name of the device to match
+ */
+static inline struct device *bus_find_device_by_name(const struct bus_type *bus,
+ struct device *start,
+ const char *name)
+{
+ return bus_find_device(bus, start, name, device_match_name);
+}
+
+/**
+ * bus_find_device_by_of_node : device iterator for locating a particular device
+ * matching the of_node.
+ * @bus: bus type
+ * @np: of_node of the device to match.
+ */
+static inline struct device *
+bus_find_device_by_of_node(const struct bus_type *bus, const struct device_node *np)
+{
+ return bus_find_device(bus, NULL, np, device_match_of_node);
+}
+
extern struct bus_type platform_bus;
#endif
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 05/16] drive: bus: make use of new bus_find_device helper
2025-06-05 21:07 [PATCH 00/16] ARM: stm32mp: add MIPI DSI support Ahmad Fatoum
` (3 preceding siblings ...)
2025-06-05 21:07 ` [PATCH 04/16] driver: bus: add helpers for finding devices in busses Ahmad Fatoum
@ 2025-06-05 21:07 ` Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 06/16] of: implement of_alias_from_compatible Ahmad Fatoum
` (10 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ahmad Fatoum @ 2025-06-05 21:07 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
Instead of opencoding the iteration, let's make use of the new
helpers to lookup devices on a given bus.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
drivers/efi/efi-device.c | 17 ++++++++++-------
drivers/net/phy/phy.c | 7 +++----
2 files changed, 13 insertions(+), 11 deletions(-)
diff --git a/drivers/efi/efi-device.c b/drivers/efi/efi-device.c
index d5eda66cd55a..23fcef7957fe 100644
--- a/drivers/efi/efi-device.c
+++ b/drivers/efi/efi-device.c
@@ -30,17 +30,20 @@ static int efi_locate_handle(enum efi_locate_search_type search_type,
buffer);
}
+static int efi_device_match_handle(struct device *dev, const void *handle)
+{
+ struct efi_device *efidev = container_of(dev, struct efi_device, dev);
+ return efidev->handle == handle;
+}
+
static struct efi_device *efi_find_device(efi_handle_t handle)
{
struct device *dev;
- struct efi_device *efidev;
- bus_for_each_device(&efi_bus, dev) {
- efidev = container_of(dev, struct efi_device, dev);
-
- if (efidev->handle == handle)
- return efidev;
- }
+ dev = bus_find_device(&efi_bus, NULL, efi_device_match_handle,
+ efi_device_match_handle);
+ if (dev)
+ return container_of(dev, struct efi_device, dev);
return NULL;
}
diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index d9f50f45f441..9635c816c330 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -374,10 +374,9 @@ static struct phy_device *of_mdio_find_phy(struct eth_device *edev)
}
}
- bus_for_each_device(&mdio_bus_type, dev) {
- if (dev->of_node == phy_node)
- return container_of(dev, struct phy_device, dev);
- }
+ dev = bus_find_device_by_of_node(&mdio_bus_type, phy_node);
+ if (dev)
+ return container_of(dev, struct phy_device, dev);
return NULL;
}
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 06/16] of: implement of_alias_from_compatible
2025-06-05 21:07 [PATCH 00/16] ARM: stm32mp: add MIPI DSI support Ahmad Fatoum
` (4 preceding siblings ...)
2025-06-05 21:07 ` [PATCH 05/16] drive: bus: make use of new bus_find_device helper Ahmad Fatoum
@ 2025-06-05 21:07 ` Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 07/16] video: vpl: fix potential read of uninitialized variable Ahmad Fatoum
` (9 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ahmad Fatoum @ 2025-06-05 21:07 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
This is going to be used in the incoming MIPI DSI support.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
drivers/of/base.c | 31 +++++++++++++++++++++++++++++++
include/of.h | 1 +
2 files changed, 32 insertions(+)
diff --git a/drivers/of/base.c b/drivers/of/base.c
index 1439e55a0aac..eb1f0b44f22d 100644
--- a/drivers/of/base.c
+++ b/drivers/of/base.c
@@ -3569,6 +3569,37 @@ int of_graph_port_is_available(struct device_node *node)
}
EXPORT_SYMBOL(of_graph_port_is_available);
+/**
+ * of_alias_from_compatible - Lookup appropriate alias for a device node
+ * depending on compatible
+ * @node: pointer to a device tree node
+ * @alias: Pointer to buffer that alias value will be copied into
+ * @len: Length of alias value
+ *
+ * Based on the value of the compatible property, this routine will attempt
+ * to choose an appropriate alias value for a particular device tree node.
+ * It does this by stripping the manufacturer prefix (as delimited by a ',')
+ * from the first entry in the compatible list property.
+ *
+ * Note: The matching on just the "product" side of the compatible is a relic
+ * from I2C and SPI. Please do not add any new user.
+ *
+ * Return: This routine returns 0 on success, <0 on failure.
+ */
+int of_alias_from_compatible(const struct device_node *node, char *alias, int len)
+{
+ const char *compatible, *p;
+ int cplen;
+
+ compatible = of_get_property(node, "compatible", &cplen);
+ if (!compatible || strlen(compatible) > cplen)
+ return -ENODEV;
+ p = strchr(compatible, ',');
+ strscpy(alias, p ? p + 1 : compatible, len);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(of_alias_from_compatible);
+
/**
* of_get_machine_compatible - get first compatible string from the root node.
*
diff --git a/include/of.h b/include/of.h
index 2258cd501b72..fa2401fc8ded 100644
--- a/include/of.h
+++ b/include/of.h
@@ -196,6 +196,7 @@ extern struct device_node *of_copy_node(struct device_node *parent,
extern struct device_node *of_dup(const struct device_node *root);
extern void of_delete_node(struct device_node *node);
+extern int of_alias_from_compatible(const struct device_node *node, char *alias, int len);
extern const char *of_get_machine_compatible(void);
extern int of_machine_is_compatible(const char *compat);
extern int of_device_is_compatible(const struct device_node *device,
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 07/16] video: vpl: fix potential read of uninitialized variable
2025-06-05 21:07 [PATCH 00/16] ARM: stm32mp: add MIPI DSI support Ahmad Fatoum
` (5 preceding siblings ...)
2025-06-05 21:07 ` [PATCH 06/16] of: implement of_alias_from_compatible Ahmad Fatoum
@ 2025-06-05 21:07 ` Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 08/16] video: vpl: factor out vpl_for_each Ahmad Fatoum
` (8 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ahmad Fatoum @ 2025-06-05 21:07 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
remote_port_id is passed to the ioctl callback, so make sure it has a
value if the endpoint has no reg property.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
drivers/video/vpl.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/video/vpl.c b/drivers/video/vpl.c
index 35fb342a8f25..6fcba0901165 100644
--- a/drivers/video/vpl.c
+++ b/drivers/video/vpl.c
@@ -70,7 +70,7 @@ int vpl_ioctl(struct vpl *vpl, unsigned int port,
for_each_child_of_node(node, endpoint) {
struct device_node *remote, *remote_parent;
struct vpl *remote_vpl;
- u32 remote_port_id;
+ u32 remote_port_id = 0;
remote = of_graph_get_remote_port(endpoint);
if (!remote) {
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 08/16] video: vpl: factor out vpl_for_each
2025-06-05 21:07 [PATCH 00/16] ARM: stm32mp: add MIPI DSI support Ahmad Fatoum
` (6 preceding siblings ...)
2025-06-05 21:07 ` [PATCH 07/16] video: vpl: fix potential read of uninitialized variable Ahmad Fatoum
@ 2025-06-05 21:07 ` Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 09/16] video: vpl: handle missing struct vpl::ioctl gracefully Ahmad Fatoum
` (7 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ahmad Fatoum @ 2025-06-05 21:07 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
We currently resolve the video pipeline on ioctl instead of at probe
time. In preparation for allowing resolution at probe time, provide a
helper function for resolving the video pipeline without calling the
ioctl right away.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
drivers/video/vpl.c | 27 ++++++++++++++++++++++++---
1 file changed, 24 insertions(+), 3 deletions(-)
diff --git a/drivers/video/vpl.c b/drivers/video/vpl.c
index 6fcba0901165..0b86ef9f7e81 100644
--- a/drivers/video/vpl.c
+++ b/drivers/video/vpl.c
@@ -53,8 +53,9 @@ struct vpl *of_vpl_get(struct device_node *node, int port)
return of_find_vpl(node);
}
-int vpl_ioctl(struct vpl *vpl, unsigned int port,
- unsigned int cmd, void *ptr)
+static int vpl_foreach_endpoint(struct vpl *vpl, unsigned int port,
+ int (*fn)(struct vpl *, unsigned port, void *data),
+ void *data)
{
struct device_node *node, *endpoint;
int ret;
@@ -96,10 +97,30 @@ int vpl_ioctl(struct vpl *vpl, unsigned int port,
}
pr_debug("%s: looked up %pOF: %pS\n", __func__, remote, remote_vpl->ioctl);
- ret = remote_vpl->ioctl(remote_vpl, remote_port_id, cmd, ptr);
+ ret = fn(remote_vpl, remote_port_id, data);
if (ret)
return ret;
}
return 0;
}
+
+struct vpl_ioctl {
+ unsigned cmd;
+ void *ptr;
+};
+
+static int vpl_remote_ioctl(struct vpl *vpl, unsigned port, void *_data)
+{
+ struct vpl_ioctl *data = _data;
+
+ return vpl->ioctl(vpl, port, data->cmd, data->ptr);
+}
+
+int vpl_ioctl(struct vpl *vpl, unsigned int port,
+ unsigned int cmd, void *ptr)
+{
+ struct vpl_ioctl data = { .cmd = cmd, .ptr = ptr };
+
+ return vpl_foreach_endpoint(vpl, port, vpl_remote_ioctl, &data);
+}
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 09/16] video: vpl: handle missing struct vpl::ioctl gracefully
2025-06-05 21:07 [PATCH 00/16] ARM: stm32mp: add MIPI DSI support Ahmad Fatoum
` (7 preceding siblings ...)
2025-06-05 21:07 ` [PATCH 08/16] video: vpl: factor out vpl_for_each Ahmad Fatoum
@ 2025-06-05 21:07 ` Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 10/16] video: vpl: add vpl_bridge abstraction Ahmad Fatoum
` (6 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ahmad Fatoum @ 2025-06-05 21:07 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
Display controllers don't usually implement the ioctl callback, because
they are invoked via the register_framebuffer API and only need to call
other drivers' ioctl and their own is never called.
Yet, when by mistake an ioctl is directed at a display controller, we
currently crash. Let's handle this case gracefully instead.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
drivers/video/vpl.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/video/vpl.c b/drivers/video/vpl.c
index 0b86ef9f7e81..da8bd93b7d6a 100644
--- a/drivers/video/vpl.c
+++ b/drivers/video/vpl.c
@@ -106,6 +106,7 @@ static int vpl_foreach_endpoint(struct vpl *vpl, unsigned int port,
}
struct vpl_ioctl {
+ int err;
unsigned cmd;
void *ptr;
};
@@ -114,6 +115,11 @@ static int vpl_remote_ioctl(struct vpl *vpl, unsigned port, void *_data)
{
struct vpl_ioctl *data = _data;
+ if (!vpl->ioctl) {
+ data->err = -EOPNOTSUPP;
+ return 0;
+ }
+
return vpl->ioctl(vpl, port, data->cmd, data->ptr);
}
@@ -122,5 +128,5 @@ int vpl_ioctl(struct vpl *vpl, unsigned int port,
{
struct vpl_ioctl data = { .cmd = cmd, .ptr = ptr };
- return vpl_foreach_endpoint(vpl, port, vpl_remote_ioctl, &data);
+ return vpl_foreach_endpoint(vpl, port, vpl_remote_ioctl, &data) ?: data.err;
}
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 10/16] video: vpl: add vpl_bridge abstraction
2025-06-05 21:07 [PATCH 00/16] ARM: stm32mp: add MIPI DSI support Ahmad Fatoum
` (8 preceding siblings ...)
2025-06-05 21:07 ` [PATCH 09/16] video: vpl: handle missing struct vpl::ioctl gracefully Ahmad Fatoum
@ 2025-06-05 21:07 ` Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 11/16] video: factor out drm_mode_vrefresh Ahmad Fatoum
` (5 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ahmad Fatoum @ 2025-06-05 21:07 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
Linux drivers can make use of devm_drm_of_get_bridge to get a handle on
the next bridge. Add similar functionality to barebox as well.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
drivers/video/vpl.c | 60 ++++++++++++++++++++++++++++++++++++++++++---
include/video/vpl.h | 15 ++++++++++++
2 files changed, 72 insertions(+), 3 deletions(-)
diff --git a/drivers/video/vpl.c b/drivers/video/vpl.c
index da8bd93b7d6a..0235c1e790bd 100644
--- a/drivers/video/vpl.c
+++ b/drivers/video/vpl.c
@@ -53,14 +53,15 @@ struct vpl *of_vpl_get(struct device_node *node, int port)
return of_find_vpl(node);
}
-static int vpl_foreach_endpoint(struct vpl *vpl, unsigned int port,
+static int vpl_foreach_endpoint(struct vpl *vpl, unsigned int port, int endpoint_id,
int (*fn)(struct vpl *, unsigned port, void *data),
void *data)
{
struct device_node *node, *endpoint;
int ret;
- pr_debug("%s: %pOF port %d\n", __func__, vpl->node, port);
+ pr_debug("%s: %pOF port %d endpoint %d\n", __func__, vpl->node,
+ port, endpoint_id);
node = of_graph_get_port_by_id(vpl->node, port);
if (!node) {
@@ -73,6 +74,16 @@ static int vpl_foreach_endpoint(struct vpl *vpl, unsigned int port,
struct vpl *remote_vpl;
u32 remote_port_id = 0;
+ if (endpoint_id >= 0) {
+ u32 local_endpoint_id = 0;
+ of_property_read_u32(endpoint, "reg", &local_endpoint_id);
+ if (local_endpoint_id != endpoint_id) {
+ pr_debug("%s: skipping endpoint %pOF with id %d\n",
+ __func__, endpoint, local_endpoint_id);
+ continue;
+ }
+ }
+
remote = of_graph_get_remote_port(endpoint);
if (!remote) {
pr_debug("%s: no remote for endpoint %pOF\n", __func__, endpoint);
@@ -128,5 +139,48 @@ int vpl_ioctl(struct vpl *vpl, unsigned int port,
{
struct vpl_ioctl data = { .cmd = cmd, .ptr = ptr };
- return vpl_foreach_endpoint(vpl, port, vpl_remote_ioctl, &data) ?: data.err;
+ return vpl_foreach_endpoint(vpl, port, -1, vpl_remote_ioctl, &data) ?: data.err;
+}
+
+static int vpl_alloc_bridge(struct vpl *vpl, unsigned port, void *_bridge)
+{
+ struct vpl_bridge **bridge = _bridge;
+
+ (*bridge) = malloc(sizeof(**bridge));
+ (*bridge)->vpl = vpl;
+ (*bridge)->port = port;
+
+ return 1;
+}
+
+struct vpl_bridge *devm_vpl_of_get_bridge(struct device *dev, struct device_node *np,
+ unsigned int port, unsigned int endpoint)
+{
+ struct vpl_bridge *bridge = NULL;
+ struct vpl *vpl;
+ int ret;
+
+ vpl = of_find_vpl(np);
+ if (!vpl)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ ret = vpl_foreach_endpoint(vpl, port, endpoint, vpl_alloc_bridge, &bridge);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ return bridge ?: ERR_PTR(-EINVAL);
+}
+
+int vpl_bridge_ioctl(struct vpl_bridge *bridge, unsigned int cmd, void *ptr)
+{
+ struct vpl *vpl;
+
+ if (IS_ERR_OR_NULL(bridge))
+ return PTR_ERR_OR_ZERO(bridge);
+
+ vpl = bridge->vpl;
+ if (!vpl->ioctl)
+ return -EOPNOTSUPP;
+
+ return vpl->ioctl(vpl, bridge->port, cmd, ptr);
}
diff --git a/include/video/vpl.h b/include/video/vpl.h
index 5ea3d6dcf202..a8fda3e6294b 100644
--- a/include/video/vpl.h
+++ b/include/video/vpl.h
@@ -20,9 +20,24 @@ struct vpl {
struct list_head list;
};
+/**
+ * struct vpl_bridge - bridge control structure
+ */
+struct vpl_bridge {
+ /** @vpl: pointer to vpl instance of bridge */
+ struct vpl *vpl;
+ /** @port: remote input port on bridge */
+ int port;
+};
+
int vpl_register(struct vpl *);
int vpl_ioctl(struct vpl *, unsigned int port,
unsigned int cmd, void *ptr);
+struct vpl_bridge;
+int vpl_bridge_ioctl(struct vpl_bridge *,
+ unsigned int cmd, void *ptr);
+struct vpl_bridge *devm_vpl_of_get_bridge(struct device *dev, struct device_node *np,
+ unsigned int port, unsigned int endpoint);
struct vpl *of_vpl_get(struct device_node *node, int port);
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 11/16] video: factor out drm_mode_vrefresh
2025-06-05 21:07 [PATCH 00/16] ARM: stm32mp: add MIPI DSI support Ahmad Fatoum
` (9 preceding siblings ...)
2025-06-05 21:07 ` [PATCH 10/16] video: vpl: add vpl_bridge abstraction Ahmad Fatoum
@ 2025-06-05 21:07 ` Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 12/16] video: add base MIPI DSI support Ahmad Fatoum
` (4 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ahmad Fatoum @ 2025-06-05 21:07 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
This function is going to be used for the incoming MIPI-DSI support, so
let's make it reusable.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
drivers/video/Makefile | 1 +
drivers/video/drm/Makefile | 2 +
drivers/video/drm/drm_modes.c | 67 ++++++++++++++++++++++
drivers/video/rockchip/rockchip_drm_vop2.c | 13 -----
include/video/drm/drm_modes.h | 2 +
5 files changed, 72 insertions(+), 13 deletions(-)
create mode 100644 drivers/video/drm/Makefile
create mode 100644 drivers/video/drm/drm_modes.c
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index f851837ebcdb..dde1da1bb98b 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_VIDEO) += fb.o mode-helpers.o
+obj-$(CONFIG_VIDEO) += drm/
obj-$(CONFIG_DRIVER_VIDEO_EDID) += edid.o
obj-$(CONFIG_OFDEVICE) += of_display_timing.o
obj-$(CONFIG_DRIVER_VIDEO_BACKLIGHT) += backlight.o
diff --git a/drivers/video/drm/Makefile b/drivers/video/drm/Makefile
new file mode 100644
index 000000000000..06d586eb2184
--- /dev/null
+++ b/drivers/video/drm/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_VIDEO) += drm_modes.o
diff --git a/drivers/video/drm/drm_modes.c b/drivers/video/drm/drm_modes.c
new file mode 100644
index 000000000000..00fcfb9070ae
--- /dev/null
+++ b/drivers/video/drm/drm_modes.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright © 1997-2003 by The XFree86 Project, Inc.
+ * Copyright © 2007 Dave Airlie
+ * Copyright © 2007-2008 Intel Corporation
+ * Jesse Barnes <jesse.barnes@intel.com>
+ * Copyright 2005-2006 Luc Verhaegen
+ * Copyright (c) 2001, Andy Ritger aritger@nvidia.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Except as contained in this notice, the name of the copyright holder(s)
+ * and author(s) shall not be used in advertising or otherwise to promote
+ * the sale, use or other dealings in this Software without prior written
+ * authorization from the copyright holder(s) and author(s).
+ */
+
+#include <linux/ctype.h>
+#include <linux/export.h>
+#include <linux/list.h>
+#include <of.h>
+
+#include <video/drm/drm_modes.h>
+
+/**
+ * drm_mode_vrefresh - get the vrefresh of a mode
+ * @mode: mode
+ *
+ * Returns:
+ * @modes's vrefresh rate in Hz, rounded to the nearest integer. Calculates the
+ * value first if it is not yet set.
+ */
+int drm_mode_vrefresh(const struct drm_display_mode *mode)
+{
+ unsigned int num, den;
+
+ if (mode->htotal == 0 || mode->vtotal == 0)
+ return 0;
+
+ num = mode->clock;
+ den = mode->htotal * mode->vtotal;
+
+ if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+ num *= 2;
+ if (mode->flags & DRM_MODE_FLAG_DBLSCAN)
+ den *= 2;
+ if (mode->vscan > 1)
+ den *= mode->vscan;
+
+ return DIV_ROUND_CLOSEST_ULL(mul_u32_u32(num, 1000), den);
+}
+EXPORT_SYMBOL(drm_mode_vrefresh);
diff --git a/drivers/video/rockchip/rockchip_drm_vop2.c b/drivers/video/rockchip/rockchip_drm_vop2.c
index b49053c6e915..d81e0357528b 100644
--- a/drivers/video/rockchip/rockchip_drm_vop2.c
+++ b/drivers/video/rockchip/rockchip_drm_vop2.c
@@ -975,19 +975,6 @@ static int us_to_vertical_line(struct drm_display_mode *mode, int us)
return us * mode->clock / mode->htotal / 1000;
}
-static int drm_mode_vrefresh(const struct drm_display_mode *mode)
-{
- unsigned int num, den;
-
- if (mode->htotal == 0 || mode->vtotal == 0)
- return 0;
-
- num = mode->clock;
- den = mode->htotal * mode->vtotal;
-
- return DIV_ROUND_CLOSEST_ULL(mul_u32_u32(num, 1000), den);
-}
-
static void vop2_crtc_atomic_enable(struct vop2_video_port *vp,
struct drm_display_mode *mode,
struct rockchip_crtc_state *vcstate)
diff --git a/include/video/drm/drm_modes.h b/include/video/drm/drm_modes.h
index afce8a25a9c4..003c76cab6e0 100644
--- a/include/video/drm/drm_modes.h
+++ b/include/video/drm/drm_modes.h
@@ -406,4 +406,6 @@ struct drm_display_mode {
enum drm_mode_status status;
};
+int drm_mode_vrefresh(const struct drm_display_mode *mode);
+
#endif /* __DRM_MODES_H__ */
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 12/16] video: add base MIPI DSI support
2025-06-05 21:07 [PATCH 00/16] ARM: stm32mp: add MIPI DSI support Ahmad Fatoum
` (10 preceding siblings ...)
2025-06-05 21:07 ` [PATCH 11/16] video: factor out drm_mode_vrefresh Ahmad Fatoum
@ 2025-06-05 21:07 ` Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 13/16] video: add Designware MIPI-DSI support Ahmad Fatoum
` (3 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ahmad Fatoum @ 2025-06-05 21:07 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
The generic MIPI DSI support is very straight forward and is mostly
helper functions to talk with a panel as well as a mipi-dsi bus to
connect hosts with panels. Import the support from Linux v6.15.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
drivers/video/Kconfig | 7 +
drivers/video/Makefile | 1 +
drivers/video/mipi_dsi.c | 1730 ++++++++++++++++++++++++++++++++++++++
include/video/mipi_dsi.h | 618 ++++++++++++++
4 files changed, 2356 insertions(+)
create mode 100644 drivers/video/mipi_dsi.c
create mode 100644 include/video/mipi_dsi.h
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index ef19948219f3..4ad7cd6b25d4 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -119,6 +119,13 @@ config DRIVER_VIDEO_EDID
config DRIVER_VIDEO_MIPI_DBI
bool
+config DRIVER_VIDEO_MIPI_DSI
+ bool
+ help
+ Support MIPI DSI interface for driving a MIPI compatible device.
+ The MIPI Display Serial Interface (MIPI DSI) defines a high-speed
+ serial interface between a host processor and a display module.
+
config DRIVER_VIDEO_BACKLIGHT
bool "Add backlight support"
help
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index dde1da1bb98b..9a222fb370aa 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_DRIVER_VIDEO_MTL017) += mtl017.o
obj-$(CONFIG_DRIVER_VIDEO_TC358767) += tc358767.o
obj-$(CONFIG_DRIVER_VIDEO_SIMPLE_PANEL) += simple-panel.o
obj-$(CONFIG_DRIVER_VIDEO_MIPI_DBI) += mipi_dbi.o
+obj-$(CONFIG_DRIVER_VIDEO_MIPI_DSI) += mipi_dsi.o
obj-$(CONFIG_DRIVER_VIDEO_PANEL_ILITEK_ILI9341) += panel-ilitek-ili9341.o
obj-$(CONFIG_DRIVER_VIDEO_PANEL_MIPI_DBI) += panel-mipi-dbi.o
obj-$(CONFIG_DRIVER_VIDEO_PANEL_SITRONIX_ST7789V) += panel-sitronix-st7789v.o
diff --git a/drivers/video/mipi_dsi.c b/drivers/video/mipi_dsi.c
new file mode 100644
index 000000000000..91dcd5b10fc0
--- /dev/null
+++ b/drivers/video/mipi_dsi.c
@@ -0,0 +1,1730 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MIPI DSI Bus
+ *
+ * Copyright (C) 2012-2013, Samsung Electronics, Co., Ltd.
+ * Copyright (C) 2019 STMicroelectronics - All Rights Reserved
+ * Andrzej Hajda <a.hajda@samsung.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Mipi_dsi.c contains a set of dsi helpers.
+ * This file is inspired from the drm helper file drivers/gpu/drm/drm_mipi_dsi.c
+ * (kernel linux).
+ *
+ */
+
+#include <video/mipi_display.h>
+#include <video/mipi_dsi.h>
+#include <linux/export.h>
+#include <of_device.h>
+
+/**
+ * DOC: dsi helpers
+ *
+ * These functions contain some common logic and helpers to deal with MIPI DSI
+ * peripherals.
+ *
+ * Helpers are provided for a number of standard MIPI DSI command as well as a
+ * subset of the MIPI DCS command set.
+ */
+
+static int mipi_dsi_device_match(struct device *dev, const struct device_driver *drv)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(dev);
+
+ /* attempt OF style match */
+ if (of_driver_match_device(dev, drv))
+ return 1;
+
+ /* compare DSI device and driver names */
+ if (!strcmp(dsi->name, drv->name))
+ return 1;
+
+ return 0;
+}
+
+struct bus_type mipi_dsi_bus_type = {
+ .name = "mipi-dsi",
+ .match = mipi_dsi_device_match,
+};
+
+/**
+ * of_find_mipi_dsi_device_by_node() - find the MIPI DSI device matching a
+ * device tree node
+ * @np: device tree node
+ *
+ * Return: A pointer to the MIPI DSI device corresponding to @np or NULL if no
+ * such device exists (or has not been registered yet).
+ */
+struct mipi_dsi_device *of_find_mipi_dsi_device_by_node(struct device_node *np)
+{
+ struct device *dev;
+
+ dev = bus_find_device_by_of_node(&mipi_dsi_bus_type, np);
+
+ return dev ? to_mipi_dsi_device(dev) : NULL;
+}
+EXPORT_SYMBOL(of_find_mipi_dsi_device_by_node);
+
+static struct mipi_dsi_device *mipi_dsi_device_alloc(struct mipi_dsi_host *host)
+{
+ struct mipi_dsi_device *dsi;
+
+ dsi = calloc(sizeof(*dsi), 1);
+ if (!dsi)
+ return ERR_PTR(-ENOMEM);
+
+ dsi->host = host;
+ dsi->dev.bus = &mipi_dsi_bus_type;
+ dsi->dev.parent = host->dev;
+
+ return dsi;
+}
+
+static int mipi_dsi_device_add(struct mipi_dsi_device *dsi)
+{
+ dsi->dev.id = DEVICE_ID_SINGLE;
+ dev_set_name(&dsi->dev, "%s", dsi->name);
+
+ return register_device(&dsi->dev);
+}
+
+#if IS_ENABLED(CONFIG_OF)
+static struct mipi_dsi_device *
+of_mipi_dsi_device_add(struct mipi_dsi_host *host, struct device_node *node)
+{
+ struct mipi_dsi_device_info info = { };
+ int ret;
+ u32 reg;
+
+ if (of_alias_from_compatible(node, info.type, sizeof(info.type)) < 0) {
+ dev_err(host->dev, "modalias failure on %pOF\n", node);
+ return ERR_PTR(-EINVAL);
+ }
+
+ ret = of_property_read_u32(node, "reg", ®);
+ if (ret) {
+ dev_err(host->dev, "device node %pOF has no valid reg property: %d\n",
+ node, ret);
+ return ERR_PTR(-EINVAL);
+ }
+
+ info.channel = reg;
+ info.node = of_node_get(node);
+
+ return mipi_dsi_device_register_full(host, &info);
+}
+#else
+static struct mipi_dsi_device *
+of_mipi_dsi_device_add(struct mipi_dsi_host *host, struct device_node *node)
+{
+ return ERR_PTR(-ENODEV);
+}
+#endif
+
+/**
+ * mipi_dsi_device_register_full - create a MIPI DSI device
+ * @host: DSI host to which this device is connected
+ * @info: pointer to template containing DSI device information
+ *
+ * Create a MIPI DSI device by using the device information provided by
+ * mipi_dsi_device_info template
+ *
+ * Returns:
+ * A pointer to the newly created MIPI DSI device, or, a pointer encoded
+ * with an error
+ */
+struct mipi_dsi_device *
+mipi_dsi_device_register_full(struct mipi_dsi_host *host,
+ const struct mipi_dsi_device_info *info)
+{
+ struct mipi_dsi_device *dsi;
+ int ret;
+
+ if (!info) {
+ dev_err(host->dev, "invalid mipi_dsi_device_info pointer\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (info->channel > 3) {
+ dev_err(host->dev, "invalid virtual channel: %u\n", info->channel);
+ return ERR_PTR(-EINVAL);
+ }
+
+ dsi = mipi_dsi_device_alloc(host);
+ if (IS_ERR(dsi)) {
+ dev_err(host->dev, "failed to allocate DSI device %ld\n",
+ PTR_ERR(dsi));
+ return dsi;
+ }
+
+ dsi->dev.of_node = info->node;
+ dsi->channel = info->channel;
+ strscpy(dsi->name, info->type, sizeof(dsi->name));
+
+ ret = mipi_dsi_device_add(dsi);
+ if (ret) {
+ dev_err(host->dev, "failed to add DSI device %d\n", ret);
+ kfree(dsi);
+ return ERR_PTR(ret);
+ }
+
+ return dsi;
+}
+EXPORT_SYMBOL(mipi_dsi_device_register_full);
+
+static LIST_HEAD(host_list);
+
+/**
+ * of_find_mipi_dsi_host_by_node() - find the MIPI DSI host matching a
+ * device tree node
+ * @node: device tree node
+ *
+ * Returns:
+ * A pointer to the MIPI DSI host corresponding to @node or NULL if no
+ * such device exists (or has not been registered yet).
+ */
+struct mipi_dsi_host *of_find_mipi_dsi_host_by_node(struct device_node *node)
+{
+ struct mipi_dsi_host *host;
+
+ list_for_each_entry(host, &host_list, list) {
+ if (host->dev->of_node == node) {
+ return host;
+ }
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL(of_find_mipi_dsi_host_by_node);
+
+int mipi_dsi_host_register(struct mipi_dsi_host *host)
+{
+ struct device_node *node;
+
+ for_each_available_child_of_node(host->dev->of_node, node) {
+ /* skip nodes without reg property */
+ if (!of_property_present(node, "reg"))
+ continue;
+ of_mipi_dsi_device_add(host, node);
+ }
+
+ list_add_tail(&host->list, &host_list);
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_host_register);
+
+/**
+ * mipi_dsi_attach - attach a DSI device to its DSI host
+ * @dsi: DSI peripheral
+ */
+int mipi_dsi_attach(struct mipi_dsi_device *dsi)
+{
+ const struct mipi_dsi_host_ops *ops = dsi->host->ops;
+ int ret;
+
+ if (!ops || !ops->attach)
+ return -ENOSYS;
+
+ ret = ops->attach(dsi->host, dsi);
+ if (ret)
+ return ret;
+
+ dsi->attached = true;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_attach);
+
+/**
+ * mipi_dsi_detach - detach a DSI device from its DSI host
+ * @dsi: DSI peripheral
+ */
+int mipi_dsi_detach(struct mipi_dsi_device *dsi)
+{
+ const struct mipi_dsi_host_ops *ops = dsi->host->ops;
+
+ if (WARN_ON(!dsi->attached))
+ return -EINVAL;
+
+ if (!ops || !ops->detach)
+ return -ENOSYS;
+
+ dsi->attached = false;
+
+ return ops->detach(dsi->host, dsi);
+}
+EXPORT_SYMBOL(mipi_dsi_detach);
+
+/**
+ * mipi_dsi_device_transfer - transfer message to a DSI device
+ * @dsi: DSI peripheral
+ * @msg: message
+ */
+static ssize_t mipi_dsi_device_transfer(struct mipi_dsi_device *dsi,
+ struct mipi_dsi_msg *msg)
+{
+ const struct mipi_dsi_host_ops *ops = dsi->host->ops;
+
+ if (!ops || !ops->transfer)
+ return -ENOSYS;
+
+ if (dsi->mode_flags & MIPI_DSI_MODE_LPM)
+ msg->flags |= MIPI_DSI_MSG_USE_LPM;
+
+ return ops->transfer(dsi->host, msg);
+}
+
+/**
+ * mipi_dsi_packet_format_is_short - check if a packet is of the short format
+ * @type: MIPI DSI data type of the packet
+ *
+ * Return: true if the packet for the given data type is a short packet, false
+ * otherwise.
+ */
+bool mipi_dsi_packet_format_is_short(u8 type)
+{
+ switch (type) {
+ case MIPI_DSI_V_SYNC_START:
+ case MIPI_DSI_V_SYNC_END:
+ case MIPI_DSI_H_SYNC_START:
+ case MIPI_DSI_H_SYNC_END:
+ case MIPI_DSI_COMPRESSION_MODE:
+ case MIPI_DSI_END_OF_TRANSMISSION:
+ case MIPI_DSI_COLOR_MODE_OFF:
+ case MIPI_DSI_COLOR_MODE_ON:
+ case MIPI_DSI_SHUTDOWN_PERIPHERAL:
+ case MIPI_DSI_TURN_ON_PERIPHERAL:
+ case MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM:
+ case MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM:
+ case MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM:
+ case MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM:
+ case MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM:
+ case MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM:
+ case MIPI_DSI_DCS_SHORT_WRITE:
+ case MIPI_DSI_DCS_SHORT_WRITE_PARAM:
+ case MIPI_DSI_DCS_READ:
+ case MIPI_DSI_EXECUTE_QUEUE:
+ case MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE:
+ return true;
+ }
+
+ return false;
+}
+EXPORT_SYMBOL(mipi_dsi_packet_format_is_short);
+
+/**
+ * mipi_dsi_packet_format_is_long - check if a packet is of the long format
+ * @type: MIPI DSI data type of the packet
+ *
+ * Return: true if the packet for the given data type is a long packet, false
+ * otherwise.
+ */
+bool mipi_dsi_packet_format_is_long(u8 type)
+{
+ switch (type) {
+ case MIPI_DSI_NULL_PACKET:
+ case MIPI_DSI_BLANKING_PACKET:
+ case MIPI_DSI_GENERIC_LONG_WRITE:
+ case MIPI_DSI_DCS_LONG_WRITE:
+ case MIPI_DSI_PICTURE_PARAMETER_SET:
+ case MIPI_DSI_COMPRESSED_PIXEL_STREAM:
+ case MIPI_DSI_LOOSELY_PACKED_PIXEL_STREAM_YCBCR20:
+ case MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR24:
+ case MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR16:
+ case MIPI_DSI_PACKED_PIXEL_STREAM_30:
+ case MIPI_DSI_PACKED_PIXEL_STREAM_36:
+ case MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR12:
+ case MIPI_DSI_PACKED_PIXEL_STREAM_16:
+ case MIPI_DSI_PACKED_PIXEL_STREAM_18:
+ case MIPI_DSI_PIXEL_STREAM_3BYTE_18:
+ case MIPI_DSI_PACKED_PIXEL_STREAM_24:
+ return true;
+ }
+
+ return false;
+}
+EXPORT_SYMBOL(mipi_dsi_packet_format_is_long);
+
+/**
+ * mipi_dsi_create_packet - create a packet from a message according to the
+ * DSI protocol
+ * @packet: pointer to a DSI packet structure
+ * @msg: message to translate into a packet
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_create_packet(struct mipi_dsi_packet *packet,
+ const struct mipi_dsi_msg *msg)
+{
+ if (!packet || !msg)
+ return -EINVAL;
+
+ /* do some minimum sanity checking */
+ if (!mipi_dsi_packet_format_is_short(msg->type) &&
+ !mipi_dsi_packet_format_is_long(msg->type))
+ return -EINVAL;
+
+ if (msg->channel > 3)
+ return -EINVAL;
+
+ memset(packet, 0, sizeof(*packet));
+ packet->header[0] = ((msg->channel & 0x3) << 6) | (msg->type & 0x3f);
+
+ /* TODO: compute ECC if hardware support is not available */
+
+ /*
+ * Long write packets contain the word count in header bytes 1 and 2.
+ * The payload follows the header and is word count bytes long.
+ *
+ * Short write packets encode up to two parameters in header bytes 1
+ * and 2.
+ */
+ if (mipi_dsi_packet_format_is_long(msg->type)) {
+ packet->header[1] = (msg->tx_len >> 0) & 0xff;
+ packet->header[2] = (msg->tx_len >> 8) & 0xff;
+
+ packet->payload_length = msg->tx_len;
+ packet->payload = msg->tx_buf;
+ } else {
+ const u8 *tx = msg->tx_buf;
+
+ packet->header[1] = (msg->tx_len > 0) ? tx[0] : 0;
+ packet->header[2] = (msg->tx_len > 1) ? tx[1] : 0;
+ }
+
+ packet->size = sizeof(packet->header) + packet->payload_length;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_create_packet);
+
+/**
+ * mipi_dsi_shutdown_peripheral() - sends a Shutdown Peripheral command
+ * @dsi: DSI peripheral device
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_shutdown_peripheral(struct mipi_dsi_device *dsi)
+{
+ struct mipi_dsi_msg msg = {
+ .channel = dsi->channel,
+ .type = MIPI_DSI_SHUTDOWN_PERIPHERAL,
+ .tx_buf = (u8 [2]) { 0, 0 },
+ .tx_len = 2,
+ };
+ int ret = mipi_dsi_device_transfer(dsi, &msg);
+
+ return (ret < 0) ? ret : 0;
+}
+EXPORT_SYMBOL(mipi_dsi_shutdown_peripheral);
+
+/**
+ * mipi_dsi_turn_on_peripheral() - sends a Turn On Peripheral command
+ * @dsi: DSI peripheral device
+ *
+ * This function is deprecated. Use mipi_dsi_turn_on_peripheral_multi() instead.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_turn_on_peripheral(struct mipi_dsi_device *dsi)
+{
+ struct mipi_dsi_msg msg = {
+ .channel = dsi->channel,
+ .type = MIPI_DSI_TURN_ON_PERIPHERAL,
+ .tx_buf = (u8 [2]) { 0, 0 },
+ .tx_len = 2,
+ };
+ int ret = mipi_dsi_device_transfer(dsi, &msg);
+
+ return (ret < 0) ? ret : 0;
+}
+EXPORT_SYMBOL(mipi_dsi_turn_on_peripheral);
+
+/*
+ * mipi_dsi_set_maximum_return_packet_size() - specify the maximum size of
+ * the payload in a long packet transmitted from the peripheral back to the
+ * host processor
+ * @dsi: DSI peripheral device
+ * @value: the maximum size of the payload
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_set_maximum_return_packet_size(struct mipi_dsi_device *dsi,
+ u16 value)
+{
+ u8 tx[2] = { value & 0xff, value >> 8 };
+ struct mipi_dsi_msg msg = {
+ .channel = dsi->channel,
+ .type = MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE,
+ .tx_len = sizeof(tx),
+ .tx_buf = tx,
+ };
+ int ret = mipi_dsi_device_transfer(dsi, &msg);
+
+ return (ret < 0) ? ret : 0;
+}
+EXPORT_SYMBOL(mipi_dsi_set_maximum_return_packet_size);
+
+/**
+ * mipi_dsi_compression_mode_ext() - enable/disable DSC on the peripheral
+ * @dsi: DSI peripheral device
+ * @enable: Whether to enable or disable the DSC
+ * @algo: Selected compression algorithm
+ * @pps_selector: Select PPS from the table of pre-stored or uploaded PPS entries
+ *
+ * Enable or disable Display Stream Compression on the peripheral.
+ * This function is deprecated. Use mipi_dsi_compression_mode_ext_multi() instead.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_compression_mode_ext(struct mipi_dsi_device *dsi, bool enable,
+ enum mipi_dsi_compression_algo algo,
+ unsigned int pps_selector)
+{
+ u8 tx[2] = { };
+ struct mipi_dsi_msg msg = {
+ .channel = dsi->channel,
+ .type = MIPI_DSI_COMPRESSION_MODE,
+ .tx_len = sizeof(tx),
+ .tx_buf = tx,
+ };
+ int ret;
+
+ if (algo > 3 || pps_selector > 3)
+ return -EINVAL;
+
+ tx[0] = (enable << 0) |
+ (algo << 1) |
+ (pps_selector << 4);
+
+ ret = mipi_dsi_device_transfer(dsi, &msg);
+
+ return (ret < 0) ? ret : 0;
+}
+EXPORT_SYMBOL(mipi_dsi_compression_mode_ext);
+
+/**
+ * mipi_dsi_compression_mode() - enable/disable DSC on the peripheral
+ * @dsi: DSI peripheral device
+ * @enable: Whether to enable or disable the DSC
+ *
+ * Enable or disable Display Stream Compression on the peripheral using the
+ * default Picture Parameter Set and VESA DSC 1.1 algorithm.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_compression_mode(struct mipi_dsi_device *dsi, bool enable)
+{
+ return mipi_dsi_compression_mode_ext(dsi, enable, MIPI_DSI_COMPRESSION_DSC, 0);
+}
+EXPORT_SYMBOL(mipi_dsi_compression_mode);
+
+/**
+ * mipi_dsi_generic_write() - transmit data using a generic write packet
+ * @dsi: DSI peripheral device
+ * @payload: buffer containing the payload
+ * @size: size of payload buffer
+ *
+ * This function will automatically choose the right data type depending on
+ * the payload length.
+ *
+ * Return: The number of bytes transmitted on success or a negative error code
+ * on failure.
+ */
+ssize_t mipi_dsi_generic_write(struct mipi_dsi_device *dsi, const void *payload,
+ size_t size)
+{
+ struct mipi_dsi_msg msg = {
+ .channel = dsi->channel,
+ .tx_buf = payload,
+ .tx_len = size
+ };
+
+ switch (size) {
+ case 0:
+ msg.type = MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM;
+ break;
+
+ case 1:
+ msg.type = MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM;
+ break;
+
+ case 2:
+ msg.type = MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM;
+ break;
+
+ default:
+ msg.type = MIPI_DSI_GENERIC_LONG_WRITE;
+ break;
+ }
+
+ return mipi_dsi_device_transfer(dsi, &msg);
+}
+EXPORT_SYMBOL(mipi_dsi_generic_write);
+
+/**
+ * mipi_dsi_generic_write_chatty() - mipi_dsi_generic_write() w/ an error log
+ * @dsi: DSI peripheral device
+ * @payload: buffer containing the payload
+ * @size: size of payload buffer
+ *
+ * Like mipi_dsi_generic_write() but includes a dev_err()
+ * call for you and returns 0 upon success, not the number of bytes sent.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_generic_write_chatty(struct mipi_dsi_device *dsi,
+ const void *payload, size_t size)
+{
+ struct device *dev = &dsi->dev;
+ ssize_t ret;
+
+ ret = mipi_dsi_generic_write(dsi, payload, size);
+ if (ret < 0) {
+ dev_err(dev, "sending generic data %*ph failed: %zd\n",
+ (int)size, payload, ret);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_generic_write_chatty);
+
+/**
+ * mipi_dsi_generic_write_multi() - mipi_dsi_generic_write_chatty() w/ accum_err
+ * @ctx: Context for multiple DSI transactions
+ * @payload: buffer containing the payload
+ * @size: size of payload buffer
+ *
+ * Like mipi_dsi_generic_write_chatty() but deals with errors in a way that
+ * makes it convenient to make several calls in a row.
+ */
+void mipi_dsi_generic_write_multi(struct mipi_dsi_multi_context *ctx,
+ const void *payload, size_t size)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ ssize_t ret;
+
+ if (ctx->accum_err)
+ return;
+
+ ret = mipi_dsi_generic_write(dsi, payload, size);
+ if (ret < 0) {
+ ctx->accum_err = ret;
+ dev_err(dev, "sending generic data %*ph failed: %d\n",
+ (int)size, payload, ctx->accum_err);
+ }
+}
+EXPORT_SYMBOL(mipi_dsi_generic_write_multi);
+
+/**
+ * mipi_dsi_generic_read() - receive data using a generic read packet
+ * @dsi: DSI peripheral device
+ * @params: buffer containing the request parameters
+ * @num_params: number of request parameters
+ * @data: buffer in which to return the received data
+ * @size: size of receive buffer
+ *
+ * This function will automatically choose the right data type depending on
+ * the number of parameters passed in.
+ *
+ * Return: The number of bytes successfully read or a negative error code on
+ * failure.
+ */
+ssize_t mipi_dsi_generic_read(struct mipi_dsi_device *dsi, const void *params,
+ size_t num_params, void *data, size_t size)
+{
+ struct mipi_dsi_msg msg = {
+ .channel = dsi->channel,
+ .tx_len = num_params,
+ .tx_buf = params,
+ .rx_len = size,
+ .rx_buf = data
+ };
+
+ switch (num_params) {
+ case 0:
+ msg.type = MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM;
+ break;
+
+ case 1:
+ msg.type = MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM;
+ break;
+
+ case 2:
+ msg.type = MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return mipi_dsi_device_transfer(dsi, &msg);
+}
+EXPORT_SYMBOL(mipi_dsi_generic_read);
+
+/**
+ * mipi_dsi_dcs_write_buffer() - transmit a DCS command with payload
+ * @dsi: DSI peripheral device
+ * @data: buffer containing data to be transmitted
+ * @len: size of transmission buffer
+ *
+ * This function will automatically choose the right data type depending on
+ * the command payload length.
+ *
+ * Return: The number of bytes successfully transmitted or a negative error
+ * code on failure.
+ */
+ssize_t mipi_dsi_dcs_write_buffer(struct mipi_dsi_device *dsi,
+ const void *data, size_t len)
+{
+ struct mipi_dsi_msg msg = {
+ .channel = dsi->channel,
+ .tx_buf = data,
+ .tx_len = len
+ };
+
+ switch (len) {
+ case 0:
+ return -EINVAL;
+
+ case 1:
+ msg.type = MIPI_DSI_DCS_SHORT_WRITE;
+ break;
+
+ case 2:
+ msg.type = MIPI_DSI_DCS_SHORT_WRITE_PARAM;
+ break;
+
+ default:
+ msg.type = MIPI_DSI_DCS_LONG_WRITE;
+ break;
+ }
+
+ return mipi_dsi_device_transfer(dsi, &msg);
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_write_buffer);
+
+/**
+ * mipi_dsi_dcs_write_buffer_chatty - mipi_dsi_dcs_write_buffer() w/ an error log
+ * @dsi: DSI peripheral device
+ * @data: buffer containing data to be transmitted
+ * @len: size of transmission buffer
+ *
+ * Like mipi_dsi_dcs_write_buffer() but includes a dev_err()
+ * call for you and returns 0 upon success, not the number of bytes sent.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_write_buffer_chatty(struct mipi_dsi_device *dsi,
+ const void *data, size_t len)
+{
+ struct device *dev = &dsi->dev;
+ ssize_t ret;
+
+ ret = mipi_dsi_dcs_write_buffer(dsi, data, len);
+ if (ret < 0) {
+ dev_err(dev, "sending dcs data %*ph failed: %zd\n",
+ (int)len, data, ret);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_write_buffer_chatty);
+
+/**
+ * mipi_dsi_dcs_write_buffer_multi - mipi_dsi_dcs_write_buffer_chatty() w/ accum_err
+ * @ctx: Context for multiple DSI transactions
+ * @data: buffer containing data to be transmitted
+ * @len: size of transmission buffer
+ *
+ * Like mipi_dsi_dcs_write_buffer_chatty() but deals with errors in a way that
+ * makes it convenient to make several calls in a row.
+ */
+void mipi_dsi_dcs_write_buffer_multi(struct mipi_dsi_multi_context *ctx,
+ const void *data, size_t len)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ ssize_t ret;
+
+ if (ctx->accum_err)
+ return;
+
+ ret = mipi_dsi_dcs_write_buffer(dsi, data, len);
+ if (ret < 0) {
+ ctx->accum_err = ret;
+ dev_err(dev, "sending dcs data %*ph failed: %d\n",
+ (int)len, data, ctx->accum_err);
+ }
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_write_buffer_multi);
+
+/**
+ * mipi_dsi_dcs_write() - send DCS write command
+ * @dsi: DSI peripheral device
+ * @cmd: DCS command
+ * @data: buffer containing the command payload
+ * @len: command payload length
+ *
+ * This function will automatically choose the right data type depending on
+ * the command payload length.
+ *
+ * Return: The number of bytes successfully transmitted or a negative error
+ * code on failure.
+ */
+ssize_t mipi_dsi_dcs_write(struct mipi_dsi_device *dsi, u8 cmd,
+ const void *data, size_t len)
+{
+ ssize_t err;
+ size_t size;
+ u8 stack_tx[8];
+ u8 *tx;
+
+ size = 1 + len;
+ if (len > ARRAY_SIZE(stack_tx) - 1) {
+ tx = kmalloc(size, GFP_KERNEL);
+ if (!tx)
+ return -ENOMEM;
+ } else {
+ tx = stack_tx;
+ }
+
+ /* concatenate the DCS command byte and the payload */
+ tx[0] = cmd;
+ if (data)
+ memcpy(&tx[1], data, len);
+
+ err = mipi_dsi_dcs_write_buffer(dsi, tx, size);
+
+ if (tx != stack_tx)
+ kfree(tx);
+
+ return err;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_write);
+
+/**
+ * mipi_dsi_dcs_read() - send DCS read request command
+ * @dsi: DSI peripheral device
+ * @cmd: DCS command
+ * @data: buffer in which to receive data
+ * @len: size of receive buffer
+ *
+ * Return: The number of bytes read or a negative error code on failure.
+ */
+ssize_t mipi_dsi_dcs_read(struct mipi_dsi_device *dsi, u8 cmd, void *data,
+ size_t len)
+{
+ struct mipi_dsi_msg msg = {
+ .channel = dsi->channel,
+ .type = MIPI_DSI_DCS_READ,
+ .tx_buf = &cmd,
+ .tx_len = 1,
+ .rx_buf = data,
+ .rx_len = len
+ };
+
+ return mipi_dsi_device_transfer(dsi, &msg);
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_read);
+
+/**
+ * mipi_dsi_dcs_nop() - send DCS nop packet
+ * @dsi: DSI peripheral device
+ *
+ * This function is deprecated. Use mipi_dsi_dcs_nop_multi() instead.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_nop(struct mipi_dsi_device *dsi)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_NOP, NULL, 0);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_nop);
+
+/**
+ * mipi_dsi_dcs_soft_reset() - perform a software reset of the display module
+ * @dsi: DSI peripheral device
+ *
+ * This function is deprecated. Use mipi_dsi_dcs_soft_reset_multi() instead.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_soft_reset(struct mipi_dsi_device *dsi)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SOFT_RESET, NULL, 0);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_soft_reset);
+
+/**
+ * mipi_dsi_dcs_get_power_mode() - query the display module's current power
+ * mode
+ * @dsi: DSI peripheral device
+ * @mode: return location for the current power mode
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_get_power_mode(struct mipi_dsi_device *dsi, u8 *mode)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_read(dsi, MIPI_DCS_GET_POWER_MODE, mode,
+ sizeof(*mode));
+ if (err <= 0) {
+ if (err == 0)
+ err = -ENODATA;
+
+ return err;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_get_power_mode);
+
+/**
+ * mipi_dsi_dcs_get_pixel_format() - gets the pixel format for the RGB image
+ * data used by the interface
+ * @dsi: DSI peripheral device
+ * @format: return location for the pixel format
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_get_pixel_format(struct mipi_dsi_device *dsi, u8 *format)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_read(dsi, MIPI_DCS_GET_PIXEL_FORMAT, format,
+ sizeof(*format));
+ if (err <= 0) {
+ if (err == 0)
+ err = -ENODATA;
+
+ return err;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_get_pixel_format);
+
+/**
+ * mipi_dsi_dcs_enter_sleep_mode() - disable all unnecessary blocks inside the
+ * display module except interface communication
+ * @dsi: DSI peripheral device
+ *
+ * This function is deprecated. Use mipi_dsi_dcs_enter_sleep_mode_multi() instead.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_enter_sleep_mode(struct mipi_dsi_device *dsi)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_ENTER_SLEEP_MODE, NULL, 0);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_enter_sleep_mode);
+
+/**
+ * mipi_dsi_dcs_exit_sleep_mode() - enable all blocks inside the display
+ * module
+ * @dsi: DSI peripheral device
+ *
+ * This function is deprecated. Use mipi_dsi_dcs_exit_sleep_mode_multi() instead.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_exit_sleep_mode(struct mipi_dsi_device *dsi)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_EXIT_SLEEP_MODE, NULL, 0);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_exit_sleep_mode);
+
+/**
+ * mipi_dsi_dcs_set_display_off() - stop displaying the image data on the
+ * display device
+ * @dsi: DSI peripheral device
+ *
+ * This function is deprecated. Use mipi_dsi_dcs_set_display_off_multi() instead.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_set_display_off(struct mipi_dsi_device *dsi)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_OFF, NULL, 0);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_display_off);
+
+/**
+ * mipi_dsi_dcs_set_display_on() - start displaying the image data on the
+ * display device
+ * @dsi: DSI peripheral device
+ *
+ * This function is deprecated. Use mipi_dsi_dcs_set_display_on_multi() instead.
+ *
+ * Return: 0 on success or a negative error code on failure
+ */
+int mipi_dsi_dcs_set_display_on(struct mipi_dsi_device *dsi)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_ON, NULL, 0);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_display_on);
+
+/**
+ * mipi_dsi_dcs_set_column_address() - define the column extent of the frame
+ * memory accessed by the host processor
+ * @dsi: DSI peripheral device
+ * @start: first column of frame memory
+ * @end: last column of frame memory
+ *
+ * This function is deprecated. Use mipi_dsi_dcs_set_column_address_multi()
+ * instead.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_set_column_address(struct mipi_dsi_device *dsi, u16 start,
+ u16 end)
+{
+ u8 payload[4] = { start >> 8, start & 0xff, end >> 8, end & 0xff };
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_COLUMN_ADDRESS, payload,
+ sizeof(payload));
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_column_address);
+
+/**
+ * mipi_dsi_dcs_set_page_address() - define the page extent of the frame
+ * memory accessed by the host processor
+ * @dsi: DSI peripheral device
+ * @start: first page of frame memory
+ * @end: last page of frame memory
+ *
+ * This function is deprecated. Use mipi_dsi_dcs_set_page_address_multi()
+ * instead.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_set_page_address(struct mipi_dsi_device *dsi, u16 start,
+ u16 end)
+{
+ u8 payload[4] = { start >> 8, start & 0xff, end >> 8, end & 0xff };
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_PAGE_ADDRESS, payload,
+ sizeof(payload));
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_page_address);
+
+/**
+ * mipi_dsi_dcs_set_tear_on() - turn on the display module's Tearing Effect
+ * output signal on the TE signal line.
+ * @dsi: DSI peripheral device
+ * @mode: the Tearing Effect Output Line mode
+ *
+ * This function is deprecated. Use mipi_dsi_dcs_set_tear_on_multi() instead.
+ *
+ * Return: 0 on success or a negative error code on failure
+ */
+int mipi_dsi_dcs_set_tear_on(struct mipi_dsi_device *dsi,
+ enum mipi_dsi_dcs_tear_mode mode)
+{
+ u8 value = mode;
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_TEAR_ON, &value,
+ sizeof(value));
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_tear_on);
+
+/**
+ * mipi_dsi_dcs_set_pixel_format() - sets the pixel format for the RGB image
+ * data used by the interface
+ * @dsi: DSI peripheral device
+ * @format: pixel format
+ *
+ * This function is deprecated. Use mipi_dsi_dcs_set_pixel_format_multi()
+ * instead.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_set_pixel_format(struct mipi_dsi_device *dsi, u8 format)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_PIXEL_FORMAT, &format,
+ sizeof(format));
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_pixel_format);
+
+/**
+ * mipi_dsi_dcs_set_tear_scanline() - set the scanline to use as trigger for
+ * the Tearing Effect output signal of the display module
+ * @dsi: DSI peripheral device
+ * @scanline: scanline to use as trigger
+ *
+ * This function is deprecated. Use mipi_dsi_dcs_set_tear_scanline_multi()
+ * instead.
+ *
+ * Return: 0 on success or a negative error code on failure
+ */
+int mipi_dsi_dcs_set_tear_scanline(struct mipi_dsi_device *dsi, u16 scanline)
+{
+ u8 payload[2] = { scanline >> 8, scanline & 0xff };
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_TEAR_SCANLINE, payload,
+ sizeof(payload));
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_tear_scanline);
+
+/**
+ * mipi_dsi_dcs_set_display_brightness() - sets the brightness value of the
+ * display
+ * @dsi: DSI peripheral device
+ * @brightness: brightness value
+ *
+ * This function is deprecated. Use mipi_dsi_dcs_set_display_brightness_multi()
+ * instead.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_set_display_brightness(struct mipi_dsi_device *dsi,
+ u16 brightness)
+{
+ u8 payload[2] = { brightness & 0xff, brightness >> 8 };
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
+ payload, sizeof(payload));
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_display_brightness);
+
+/**
+ * mipi_dsi_dcs_get_display_brightness() - gets the current brightness value
+ * of the display
+ * @dsi: DSI peripheral device
+ * @brightness: brightness value
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_get_display_brightness(struct mipi_dsi_device *dsi,
+ u16 *brightness)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_read(dsi, MIPI_DCS_GET_DISPLAY_BRIGHTNESS,
+ brightness, sizeof(*brightness));
+ if (err <= 0) {
+ if (err == 0)
+ err = -ENODATA;
+
+ return err;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_get_display_brightness);
+
+/**
+ * mipi_dsi_dcs_set_display_brightness_large() - sets the 16-bit brightness value
+ * of the display
+ * @dsi: DSI peripheral device
+ * @brightness: brightness value
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_set_display_brightness_large(struct mipi_dsi_device *dsi,
+ u16 brightness)
+{
+ u8 payload[2] = { brightness >> 8, brightness & 0xff };
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
+ payload, sizeof(payload));
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_display_brightness_large);
+
+/**
+ * mipi_dsi_dcs_get_display_brightness_large() - gets the current 16-bit
+ * brightness value of the display
+ * @dsi: DSI peripheral device
+ * @brightness: brightness value
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_get_display_brightness_large(struct mipi_dsi_device *dsi,
+ u16 *brightness)
+{
+ u8 brightness_be[2];
+ ssize_t err;
+
+ err = mipi_dsi_dcs_read(dsi, MIPI_DCS_GET_DISPLAY_BRIGHTNESS,
+ brightness_be, sizeof(brightness_be));
+ if (err <= 0) {
+ if (err == 0)
+ err = -ENODATA;
+
+ return err;
+ }
+
+ *brightness = (brightness_be[0] << 8) | brightness_be[1];
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_get_display_brightness_large);
+
+/**
+ * mipi_dsi_compression_mode_ext_multi() - enable/disable DSC on the peripheral
+ * @ctx: Context for multiple DSI transactions
+ * @enable: Whether to enable or disable the DSC
+ * @algo: Selected compression algorithm
+ * @pps_selector: Select PPS from the table of pre-stored or uploaded PPS entries
+ *
+ * Like mipi_dsi_compression_mode_ext() but deals with errors in a way that
+ * makes it convenient to make several calls in a row.
+ */
+void mipi_dsi_compression_mode_ext_multi(struct mipi_dsi_multi_context *ctx,
+ bool enable,
+ enum mipi_dsi_compression_algo algo,
+ unsigned int pps_selector)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ ssize_t ret;
+
+ if (ctx->accum_err)
+ return;
+
+ ret = mipi_dsi_compression_mode_ext(dsi, enable, algo, pps_selector);
+ if (ret < 0) {
+ ctx->accum_err = ret;
+ dev_err(dev, "sending COMPRESSION_MODE failed: %d\n",
+ ctx->accum_err);
+ }
+}
+EXPORT_SYMBOL(mipi_dsi_compression_mode_ext_multi);
+
+/**
+ * mipi_dsi_compression_mode_multi() - enable/disable DSC on the peripheral
+ * @ctx: Context for multiple DSI transactions
+ * @enable: Whether to enable or disable the DSC
+ *
+ * Enable or disable Display Stream Compression on the peripheral using the
+ * default Picture Parameter Set and VESA DSC 1.1 algorithm.
+ */
+void mipi_dsi_compression_mode_multi(struct mipi_dsi_multi_context *ctx,
+ bool enable)
+{
+ return mipi_dsi_compression_mode_ext_multi(ctx, enable,
+ MIPI_DSI_COMPRESSION_DSC, 0);
+}
+EXPORT_SYMBOL(mipi_dsi_compression_mode_multi);
+
+/**
+ * mipi_dsi_dcs_nop_multi() - send DCS NOP packet
+ * @ctx: Context for multiple DSI transactions
+ *
+ * Like mipi_dsi_dcs_nop() but deals with errors in a way that
+ * makes it convenient to make several calls in a row.
+ */
+void mipi_dsi_dcs_nop_multi(struct mipi_dsi_multi_context *ctx)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ ssize_t ret;
+
+ if (ctx->accum_err)
+ return;
+
+ ret = mipi_dsi_dcs_nop(dsi);
+ if (ret < 0) {
+ ctx->accum_err = ret;
+ dev_err(dev, "sending DCS NOP failed: %d\n",
+ ctx->accum_err);
+ }
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_nop_multi);
+
+/**
+ * mipi_dsi_dcs_enter_sleep_mode_multi() - send DCS ENTER_SLEEP_MODE packet
+ * @ctx: Context for multiple DSI transactions
+ *
+ * Like mipi_dsi_dcs_enter_sleep_mode() but deals with errors in a way that
+ * makes it convenient to make several calls in a row.
+ */
+void mipi_dsi_dcs_enter_sleep_mode_multi(struct mipi_dsi_multi_context *ctx)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ ssize_t ret;
+
+ if (ctx->accum_err)
+ return;
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret < 0) {
+ ctx->accum_err = ret;
+ dev_err(dev, "sending DCS ENTER_SLEEP_MODE failed: %d\n",
+ ctx->accum_err);
+ }
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_enter_sleep_mode_multi);
+
+/**
+ * mipi_dsi_dcs_exit_sleep_mode_multi() - send DCS EXIT_SLEEP_MODE packet
+ * @ctx: Context for multiple DSI transactions
+ *
+ * Like mipi_dsi_dcs_exit_sleep_mode() but deals with errors in a way that
+ * makes it convenient to make several calls in a row.
+ */
+void mipi_dsi_dcs_exit_sleep_mode_multi(struct mipi_dsi_multi_context *ctx)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ ssize_t ret;
+
+ if (ctx->accum_err)
+ return;
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret < 0) {
+ ctx->accum_err = ret;
+ dev_err(dev, "sending DCS EXIT_SLEEP_MODE failed: %d\n",
+ ctx->accum_err);
+ }
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_exit_sleep_mode_multi);
+
+/**
+ * mipi_dsi_dcs_set_display_off_multi() - send DCS SET_DISPLAY_OFF packet
+ * @ctx: Context for multiple DSI transactions
+ *
+ * Like mipi_dsi_dcs_set_display_off() but deals with errors in a way that
+ * makes it convenient to make several calls in a row.
+ */
+void mipi_dsi_dcs_set_display_off_multi(struct mipi_dsi_multi_context *ctx)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ ssize_t ret;
+
+ if (ctx->accum_err)
+ return;
+
+ ret = mipi_dsi_dcs_set_display_off(dsi);
+ if (ret < 0) {
+ ctx->accum_err = ret;
+ dev_err(dev, "sending DCS SET_DISPLAY_OFF failed: %d\n",
+ ctx->accum_err);
+ }
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_display_off_multi);
+
+/**
+ * mipi_dsi_dcs_set_display_on_multi() - send DCS SET_DISPLAY_ON packet
+ * @ctx: Context for multiple DSI transactions
+ *
+ * Like mipi_dsi_dcs_set_display_on() but deals with errors in a way that
+ * makes it convenient to make several calls in a row.
+ */
+void mipi_dsi_dcs_set_display_on_multi(struct mipi_dsi_multi_context *ctx)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ ssize_t ret;
+
+ if (ctx->accum_err)
+ return;
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret < 0) {
+ ctx->accum_err = ret;
+ dev_err(dev, "sending DCS SET_DISPLAY_ON failed: %d\n",
+ ctx->accum_err);
+ }
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_display_on_multi);
+
+/**
+ * mipi_dsi_dcs_set_tear_on_multi() - send DCS SET_TEAR_ON packet
+ * @ctx: Context for multiple DSI transactions
+ * @mode: the Tearing Effect Output Line mode
+ *
+ * Like mipi_dsi_dcs_set_tear_on() but deals with errors in a way that
+ * makes it convenient to make several calls in a row.
+ */
+void mipi_dsi_dcs_set_tear_on_multi(struct mipi_dsi_multi_context *ctx,
+ enum mipi_dsi_dcs_tear_mode mode)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ ssize_t ret;
+
+ if (ctx->accum_err)
+ return;
+
+ ret = mipi_dsi_dcs_set_tear_on(dsi, mode);
+ if (ret < 0) {
+ ctx->accum_err = ret;
+ dev_err(dev, "sending DCS SET_TEAR_ON failed: %d\n",
+ ctx->accum_err);
+ }
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_tear_on_multi);
+
+/**
+ * mipi_dsi_turn_on_peripheral_multi() - sends a Turn On Peripheral command
+ * @ctx: Context for multiple DSI transactions
+ *
+ * Like mipi_dsi_turn_on_peripheral() but deals with errors in a way that
+ * makes it convenient to make several calls in a row.
+ */
+void mipi_dsi_turn_on_peripheral_multi(struct mipi_dsi_multi_context *ctx)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ int ret;
+
+ if (ctx->accum_err)
+ return;
+
+ ret = mipi_dsi_turn_on_peripheral(dsi);
+ if (ret < 0) {
+ ctx->accum_err = ret;
+ dev_err(dev, "Failed to turn on peripheral: %d\n",
+ ctx->accum_err);
+ }
+}
+EXPORT_SYMBOL(mipi_dsi_turn_on_peripheral_multi);
+
+/**
+ * mipi_dsi_dcs_set_tear_off_multi() - turn off the display module's Tearing Effect
+ * output signal on the TE signal line
+ * @ctx: Context for multiple DSI transactions
+ */
+void mipi_dsi_dcs_set_tear_off_multi(struct mipi_dsi_multi_context *ctx)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ ssize_t err;
+
+ if (ctx->accum_err)
+ return;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_TEAR_OFF, NULL, 0);
+ if (err < 0) {
+ ctx->accum_err = err;
+ dev_err(dev, "Failed to set tear off: %d\n",
+ ctx->accum_err);
+ }
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_tear_off_multi);
+
+/**
+ * mipi_dsi_dcs_soft_reset_multi() - perform a software reset of the display module
+ * @ctx: Context for multiple DSI transactions
+ *
+ * Like mipi_dsi_dcs_soft_reset() but deals with errors in a way that
+ * makes it convenient to make several calls in a row.
+ */
+void mipi_dsi_dcs_soft_reset_multi(struct mipi_dsi_multi_context *ctx)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ int ret;
+
+ if (ctx->accum_err)
+ return;
+
+ ret = mipi_dsi_dcs_soft_reset(dsi);
+ if (ret < 0) {
+ ctx->accum_err = ret;
+ dev_err(dev, "Failed to mipi_dsi_dcs_soft_reset: %d\n",
+ ctx->accum_err);
+ }
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_soft_reset_multi);
+
+/**
+ * mipi_dsi_dcs_set_display_brightness_multi() - sets the brightness value of
+ * the display
+ * @ctx: Context for multiple DSI transactions
+ * @brightness: brightness value
+ *
+ * Like mipi_dsi_dcs_set_display_brightness() but deals with errors in a way that
+ * makes it convenient to make several calls in a row.
+ */
+void mipi_dsi_dcs_set_display_brightness_multi(struct mipi_dsi_multi_context *ctx,
+ u16 brightness)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ int ret;
+
+ if (ctx->accum_err)
+ return;
+
+ ret = mipi_dsi_dcs_set_display_brightness(dsi, brightness);
+ if (ret < 0) {
+ ctx->accum_err = ret;
+ dev_err(dev, "Failed to write display brightness: %d\n",
+ ctx->accum_err);
+ }
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_display_brightness_multi);
+
+/**
+ * mipi_dsi_dcs_set_pixel_format_multi() - sets the pixel format for the RGB image
+ * data used by the interface
+ * @ctx: Context for multiple DSI transactions
+ * @format: pixel format
+ *
+ * Like mipi_dsi_dcs_set_pixel_format() but deals with errors in a way that
+ * makes it convenient to make several calls in a row.
+ */
+void mipi_dsi_dcs_set_pixel_format_multi(struct mipi_dsi_multi_context *ctx,
+ u8 format)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ int ret;
+
+ if (ctx->accum_err)
+ return;
+
+ ret = mipi_dsi_dcs_set_pixel_format(dsi, format);
+ if (ret < 0) {
+ ctx->accum_err = ret;
+ dev_err(dev, "Failed to set pixel format: %d\n",
+ ctx->accum_err);
+ }
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_pixel_format_multi);
+
+/**
+ * mipi_dsi_dcs_set_column_address_multi() - define the column extent of the
+ * frame memory accessed by the host processor
+ * @ctx: Context for multiple DSI transactions
+ * @start: first column of frame memory
+ * @end: last column of frame memory
+ *
+ * Like mipi_dsi_dcs_set_column_address() but deals with errors in a way that
+ * makes it convenient to make several calls in a row.
+ */
+void mipi_dsi_dcs_set_column_address_multi(struct mipi_dsi_multi_context *ctx,
+ u16 start, u16 end)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ int ret;
+
+ if (ctx->accum_err)
+ return;
+
+ ret = mipi_dsi_dcs_set_column_address(dsi, start, end);
+ if (ret < 0) {
+ ctx->accum_err = ret;
+ dev_err(dev, "Failed to set column address: %d\n",
+ ctx->accum_err);
+ }
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_column_address_multi);
+
+/**
+ * mipi_dsi_dcs_set_page_address_multi() - define the page extent of the
+ * frame memory accessed by the host processor
+ * @ctx: Context for multiple DSI transactions
+ * @start: first page of frame memory
+ * @end: last page of frame memory
+ *
+ * Like mipi_dsi_dcs_set_page_address() but deals with errors in a way that
+ * makes it convenient to make several calls in a row.
+ */
+void mipi_dsi_dcs_set_page_address_multi(struct mipi_dsi_multi_context *ctx,
+ u16 start, u16 end)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ int ret;
+
+ if (ctx->accum_err)
+ return;
+
+ ret = mipi_dsi_dcs_set_page_address(dsi, start, end);
+ if (ret < 0) {
+ ctx->accum_err = ret;
+ dev_err(dev, "Failed to set page address: %d\n",
+ ctx->accum_err);
+ }
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_page_address_multi);
+
+/**
+ * mipi_dsi_dcs_set_tear_scanline_multi() - set the scanline to use as trigger for
+ * the Tearing Effect output signal of the display module
+ * @ctx: Context for multiple DSI transactions
+ * @scanline: scanline to use as trigger
+ *
+ * Like mipi_dsi_dcs_set_tear_scanline() but deals with errors in a way that
+ * makes it convenient to make several calls in a row.
+ */
+void mipi_dsi_dcs_set_tear_scanline_multi(struct mipi_dsi_multi_context *ctx,
+ u16 scanline)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ int ret;
+
+ if (ctx->accum_err)
+ return;
+
+ ret = mipi_dsi_dcs_set_tear_scanline(dsi, scanline);
+ if (ret < 0) {
+ ctx->accum_err = ret;
+ dev_err(dev, "Failed to set tear scanline: %d\n",
+ ctx->accum_err);
+ }
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_tear_scanline_multi);
+
+static int mipi_dsi_drv_probe(struct device *dev)
+{
+ struct mipi_dsi_driver *drv = to_mipi_dsi_driver(dev->driver);
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(dev);
+
+ return drv->probe(dsi);
+}
+
+static void mipi_dsi_drv_remove(struct device *dev)
+{
+ struct mipi_dsi_driver *drv = to_mipi_dsi_driver(dev->driver);
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(dev);
+
+ drv->remove(dsi);
+}
+
+/**
+ * mipi_dsi_driver_register() - register a driver for DSI devices
+ * @drv: DSI driver structure
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_driver_register(struct mipi_dsi_driver *drv)
+{
+ drv->driver.bus = &mipi_dsi_bus_type;
+
+ if (drv->probe)
+ drv->driver.probe = mipi_dsi_drv_probe;
+ if (drv->remove)
+ drv->driver.remove = mipi_dsi_drv_remove;
+
+ return driver_register(&drv->driver);
+}
+EXPORT_SYMBOL(mipi_dsi_driver_register_full);
+
+/**
+ * mipi_dsi_driver_unregister() - unregister a driver for DSI devices
+ * @drv: DSI driver structure
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+void mipi_dsi_driver_unregister(struct mipi_dsi_driver *drv)
+{
+ driver_unregister(&drv->driver);
+}
+EXPORT_SYMBOL(mipi_dsi_driver_unregister);
+
+static int __init mipi_dsi_bus_init(void)
+{
+ return bus_register(&mipi_dsi_bus_type);
+}
+postcore_initcall(mipi_dsi_bus_init);
+
+MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>");
+MODULE_DESCRIPTION("MIPI DSI Bus");
+MODULE_LICENSE("GPL and additional rights");
diff --git a/include/video/mipi_dsi.h b/include/video/mipi_dsi.h
new file mode 100644
index 000000000000..24dadd49b40c
--- /dev/null
+++ b/include/video/mipi_dsi.h
@@ -0,0 +1,618 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * MIPI DSI Bus
+ *
+ * Copyright (C) 2012-2013, Samsung Electronics, Co., Ltd.
+ * Copyright (C) 2018 STMicroelectronics - All Rights Reserved
+ * Author(s): Andrzej Hajda <a.hajda@samsung.com>
+ * Yannick Fertre <yannick.fertre@st.com>
+ * Philippe Cornu <philippe.cornu@st.com>
+ *
+ * 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.
+ */
+#ifndef MIPI_DSI_H
+#define MIPI_DSI_H
+
+#include <video/mipi_display.h>
+#include <linux/container_of.h>
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+
+struct mipi_dsi_host;
+struct mipi_dsi_device;
+struct drm_display_mode;
+
+/* request ACK from peripheral */
+#define MIPI_DSI_MSG_REQ_ACK BIT(0)
+/* use Low Power Mode to transmit message */
+#define MIPI_DSI_MSG_USE_LPM BIT(1)
+
+/**
+ * struct mipi_dsi_msg - read/write DSI buffer
+ * @channel: virtual channel id
+ * @type: payload data type
+ * @flags: flags controlling this message transmission
+ * @tx_len: length of @tx_buf
+ * @tx_buf: data to be written
+ * @rx_len: length of @rx_buf
+ * @rx_buf: data to be read, or NULL
+ */
+struct mipi_dsi_msg {
+ u8 channel;
+ u8 type;
+ u16 flags;
+
+ size_t tx_len;
+ const void *tx_buf;
+
+ size_t rx_len;
+ void *rx_buf;
+};
+
+bool mipi_dsi_packet_format_is_short(u8 type);
+bool mipi_dsi_packet_format_is_long(u8 type);
+
+/**
+ * struct mipi_dsi_packet - represents a MIPI DSI packet in protocol format
+ * @size: size (in bytes) of the packet
+ * @header: the four bytes that make up the header (Data ID, Word Count or
+ * Packet Data, and ECC)
+ * @payload_length: number of bytes in the payload
+ * @payload: a pointer to a buffer containing the payload, if any
+ */
+struct mipi_dsi_packet {
+ size_t size;
+ u8 header[4];
+ size_t payload_length;
+ const u8 *payload;
+};
+
+int mipi_dsi_create_packet(struct mipi_dsi_packet *packet,
+ const struct mipi_dsi_msg *msg);
+
+/**
+ * struct mipi_dsi_host_ops - DSI bus operations
+ * @attach: attach DSI device to DSI host
+ * @detach: detach DSI device from DSI host
+ * @transfer: transmit a DSI packet
+ *
+ * DSI packets transmitted by .transfer() are passed in as mipi_dsi_msg
+ * structures. This structure contains information about the type of packet
+ * being transmitted as well as the transmit and receive buffers. When an
+ * error is encountered during transmission, this function will return a
+ * negative error code. On success it shall return the number of bytes
+ * transmitted for write packets or the number of bytes received for read
+ * packets.
+ *
+ * Note that typically DSI packet transmission is atomic, so the .transfer()
+ * function will seldomly return anything other than the number of bytes
+ * contained in the transmit buffer on success.
+ */
+struct mipi_dsi_host_ops {
+ int (*attach)(struct mipi_dsi_host *host,
+ struct mipi_dsi_device *dsi);
+ int (*detach)(struct mipi_dsi_host *host,
+ struct mipi_dsi_device *dsi);
+ ssize_t (*transfer)(struct mipi_dsi_host *host,
+ const struct mipi_dsi_msg *msg);
+};
+
+/**
+ * struct mipi_dsi_host - DSI host device
+ * @dev: driver model device node for this DSI host
+ * @ops: DSI host operations
+ * @list: list management
+ */
+struct mipi_dsi_host {
+ struct device *dev;
+ const struct mipi_dsi_host_ops *ops;
+ struct list_head list;
+};
+
+int mipi_dsi_host_register(struct mipi_dsi_host *host);
+struct mipi_dsi_host *of_find_mipi_dsi_host_by_node(struct device_node *node);
+
+/* DSI mode flags */
+
+/* video mode */
+#define MIPI_DSI_MODE_VIDEO BIT(0)
+/* video burst mode */
+#define MIPI_DSI_MODE_VIDEO_BURST BIT(1)
+/* video pulse mode */
+#define MIPI_DSI_MODE_VIDEO_SYNC_PULSE BIT(2)
+/* enable auto vertical count mode */
+#define MIPI_DSI_MODE_VIDEO_AUTO_VERT BIT(3)
+/* enable hsync-end packets in vsync-pulse and v-porch area */
+#define MIPI_DSI_MODE_VIDEO_HSE BIT(4)
+/* disable hfront-porch area */
+#define MIPI_DSI_MODE_VIDEO_HFP BIT(5)
+/* disable hback-porch area */
+#define MIPI_DSI_MODE_VIDEO_HBP BIT(6)
+/* disable hsync-active area */
+#define MIPI_DSI_MODE_VIDEO_HSA BIT(7)
+/* flush display FIFO on vsync pulse */
+#define MIPI_DSI_MODE_VSYNC_FLUSH BIT(8)
+/* disable EoT packets in HS mode */
+#define MIPI_DSI_MODE_NO_EOT_PACKET BIT(9)
+/* device supports non-continuous clock behavior (DSI spec 5.6.1) */
+#define MIPI_DSI_CLOCK_NON_CONTINUOUS BIT(10)
+/* transmit data in low power */
+#define MIPI_DSI_MODE_LPM BIT(11)
+
+enum mipi_dsi_pixel_format {
+ MIPI_DSI_FMT_RGB888,
+ MIPI_DSI_FMT_RGB666,
+ MIPI_DSI_FMT_RGB666_PACKED,
+ MIPI_DSI_FMT_RGB565,
+};
+
+#define DSI_DEV_NAME_SIZE 20
+
+/**
+ * struct mipi_dsi_device_info - template for creating a mipi_dsi_device
+ * @type: DSI peripheral chip type
+ * @channel: DSI virtual channel assigned to peripheral
+ * @node: pointer to OF device node or NULL
+ *
+ * This is populated and passed to mipi_dsi_device_new to create a new
+ * DSI device
+ */
+struct mipi_dsi_device_info {
+ char type[DSI_DEV_NAME_SIZE];
+ u32 channel;
+ struct device_node *node;
+};
+
+/**
+ * struct mipi_dsi_device - DSI peripheral device
+ * @host: DSI host for this peripheral
+ * @dev: driver model device node for this peripheral
+ * @attached: the DSI device has been successfully attached
+ * @name: DSI peripheral chip type
+ * @channel: virtual channel assigned to the peripheral
+ * @format: pixel format for video mode
+ * @lanes: number of active data lanes
+ * @mode_flags: DSI operation mode related flags
+ */
+struct mipi_dsi_device {
+ struct mipi_dsi_host *host;
+ struct device dev;
+ bool attached;
+
+ char name[DSI_DEV_NAME_SIZE];
+ unsigned int channel;
+ unsigned int lanes;
+ enum mipi_dsi_pixel_format format;
+ unsigned long mode_flags;
+};
+
+/**
+ * struct mipi_dsi_multi_context - Context to call multiple MIPI DSI funcs in a row
+ */
+struct mipi_dsi_multi_context {
+ /**
+ * @dsi: Pointer to the MIPI DSI device
+ */
+ struct mipi_dsi_device *dsi;
+
+ /**
+ * @accum_err: Storage for the accumulated error over the multiple calls
+ *
+ * Init to 0. If a function encounters an error then the error code
+ * will be stored here. If you call a function and this points to a
+ * non-zero value then the function will be a noop. This allows calling
+ * a function many times in a row and just checking the error at the
+ * end to see if any of them failed.
+ */
+ int accum_err;
+};
+
+#define to_mipi_dsi_device(__dev) container_of_const(__dev, struct mipi_dsi_device, dev)
+
+/**
+ * mipi_dsi_pixel_format_to_bpp - obtain the number of bits per pixel for any
+ * given pixel format defined by the MIPI DSI
+ * specification
+ * @fmt: MIPI DSI pixel format
+ *
+ * Returns: The number of bits per pixel of the given pixel format.
+ */
+static inline int mipi_dsi_pixel_format_to_bpp(enum mipi_dsi_pixel_format fmt)
+{
+ switch (fmt) {
+ case MIPI_DSI_FMT_RGB888:
+ case MIPI_DSI_FMT_RGB666:
+ return 24;
+
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ return 18;
+
+ case MIPI_DSI_FMT_RGB565:
+ return 16;
+ }
+
+ return -EINVAL;
+}
+
+enum mipi_dsi_compression_algo {
+ MIPI_DSI_COMPRESSION_DSC = 0,
+ MIPI_DSI_COMPRESSION_VENDOR = 3,
+ /* other two values are reserved, DSI 1.3 */
+};
+
+struct mipi_dsi_device *
+mipi_dsi_device_register_full(struct mipi_dsi_host *host,
+ const struct mipi_dsi_device_info *info);
+void mipi_dsi_device_unregister(struct mipi_dsi_device *dsi);
+struct mipi_dsi_device *of_find_mipi_dsi_device_by_node(struct device_node *np);
+
+/**
+ * mipi_dsi_attach - attach a DSI device to its DSI host
+ * @dsi: DSI peripheral
+ */
+int mipi_dsi_attach(struct mipi_dsi_device *dsi);
+
+/**
+ * mipi_dsi_detach - detach a DSI device from its DSI host
+ * @dsi: DSI peripheral
+ */
+int mipi_dsi_detach(struct mipi_dsi_device *dsi);
+int mipi_dsi_shutdown_peripheral(struct mipi_dsi_device *dsi);
+int mipi_dsi_turn_on_peripheral(struct mipi_dsi_device *dsi);
+int mipi_dsi_set_maximum_return_packet_size(struct mipi_dsi_device *dsi,
+ u16 value);
+
+int mipi_dsi_compression_mode(struct mipi_dsi_device *dsi, bool enable);
+int mipi_dsi_compression_mode_ext(struct mipi_dsi_device *dsi, bool enable,
+ enum mipi_dsi_compression_algo algo,
+ unsigned int pps_selector);
+
+void mipi_dsi_compression_mode_ext_multi(struct mipi_dsi_multi_context *ctx,
+ bool enable,
+ enum mipi_dsi_compression_algo algo,
+ unsigned int pps_selector);
+void mipi_dsi_compression_mode_multi(struct mipi_dsi_multi_context *ctx,
+ bool enable);
+
+ssize_t mipi_dsi_generic_write(struct mipi_dsi_device *dsi, const void *payload,
+ size_t size);
+int mipi_dsi_generic_write_chatty(struct mipi_dsi_device *dsi,
+ const void *payload, size_t size);
+void mipi_dsi_generic_write_multi(struct mipi_dsi_multi_context *ctx,
+ const void *payload, size_t size);
+ssize_t mipi_dsi_generic_read(struct mipi_dsi_device *dsi, const void *params,
+ size_t num_params, void *data, size_t size);
+#define mipi_dsi_mdelay(ctx, delay) \
+ do { \
+ if (!(ctx)->accum_err) \
+ mdelay(delay); \
+ } while (0)
+
+#define mipi_dsi_udelay_range(ctx, min, max) \
+ do { \
+ (void)(max); \
+ if (!(ctx)->accum_err) \
+ udelay(min); \
+ } while (0)
+
+
+
+/**
+ * enum mipi_dsi_dcs_tear_mode - Tearing Effect Output Line mode
+ * @MIPI_DSI_DCS_TEAR_MODE_VBLANK: the TE output line consists of V-Blanking
+ * information only
+ * @MIPI_DSI_DCS_TEAR_MODE_VHBLANK : the TE output line consists of both
+ * V-Blanking and H-Blanking information
+ */
+enum mipi_dsi_dcs_tear_mode {
+ MIPI_DSI_DCS_TEAR_MODE_VBLANK,
+ MIPI_DSI_DCS_TEAR_MODE_VHBLANK,
+};
+
+#define MIPI_DSI_DCS_POWER_MODE_DISPLAY BIT(2)
+#define MIPI_DSI_DCS_POWER_MODE_NORMAL BIT(3)
+#define MIPI_DSI_DCS_POWER_MODE_SLEEP BIT(4)
+#define MIPI_DSI_DCS_POWER_MODE_PARTIAL BIT(5)
+#define MIPI_DSI_DCS_POWER_MODE_IDLE BIT(6)
+
+/**
+ * mipi_dsi_dcs_write_buffer() - transmit a DCS command with payload
+ * @dsi: DSI peripheral device
+ * @data: buffer containing data to be transmitted
+ * @len: size of transmission buffer
+ *
+ * This function will automatically choose the right data type depending on
+ * the command payload length.
+ *
+ * Return: The number of bytes successfully transmitted or a negative error
+ * code on failure.
+ */
+ssize_t mipi_dsi_dcs_write_buffer(struct mipi_dsi_device *dsi,
+ const void *data, size_t len);
+
+int mipi_dsi_dcs_write_buffer_chatty(struct mipi_dsi_device *dsi,
+ const void *data, size_t len);
+void mipi_dsi_dcs_write_buffer_multi(struct mipi_dsi_multi_context *ctx,
+ const void *data, size_t len);
+
+/**
+ * mipi_dsi_dcs_write() - send DCS write command
+ * @dsi: DSI peripheral device
+ * @cmd: DCS command
+ * @data: buffer containing the command payload
+ * @len: command payload length
+ *
+ * This function will automatically choose the right data type depending on
+ * the command payload length.
+
+ * code on failure.
+ */
+ssize_t mipi_dsi_dcs_write(struct mipi_dsi_device *dsi, u8 cmd,
+ const void *data, size_t len);
+
+/**
+ * mipi_dsi_dcs_read() - send DCS read request command
+ * @dsi: DSI peripheral device
+ * @cmd: DCS command
+ * @data: buffer in which to receive data
+ * @len: size of receive buffer
+ *
+ * Return: The number of bytes read or a negative error code on failure.
+ */
+ssize_t mipi_dsi_dcs_read(struct mipi_dsi_device *dsi, u8 cmd, void *data,
+ size_t len);
+
+/**
+ * mipi_dsi_dcs_nop() - send DCS nop packet
+ * @dsi: DSI peripheral device
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_nop(struct mipi_dsi_device *dsi);
+
+/**
+ * mipi_dsi_dcs_soft_reset() - perform a software reset of the display module
+ * @dsi: DSI peripheral device
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_soft_reset(struct mipi_dsi_device *dsi);
+
+/**
+ * mipi_dsi_dcs_get_power_mode() - query the display module's current power
+ * mode
+ * @dsi: DSI peripheral device
+ * @mode: return location for the current power mode
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_get_power_mode(struct mipi_dsi_device *dsi, u8 *mode);
+
+/**
+ * mipi_dsi_dcs_get_pixel_format() - gets the pixel format for the RGB image
+ * data used by the interface
+ * @dsi: DSI peripheral device
+ * @format: return location for the pixel format
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_get_pixel_format(struct mipi_dsi_device *dsi, u8 *format);
+
+/**
+ * mipi_dsi_dcs_enter_sleep_mode() - disable all unnecessary blocks inside the
+ * display module except interface communication
+ * @dsi: DSI peripheral device
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_enter_sleep_mode(struct mipi_dsi_device *dsi);
+
+/**
+ * mipi_dsi_dcs_exit_sleep_mode() - enable all blocks inside the display
+ * module
+ * @dsi: DSI peripheral device
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_exit_sleep_mode(struct mipi_dsi_device *dsi);
+
+/**
+ * mipi_dsi_dcs_set_display_off() - stop displaying the image data on the
+ * display device
+ * @dsi: DSI peripheral device
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_set_display_off(struct mipi_dsi_device *dsi);
+
+/**
+ * mipi_dsi_dcs_set_display_on() - start displaying the image data on the
+ * display device
+ * @dsi: DSI peripheral device
+ *
+ * Return: 0 on success or a negative error code on failure
+ */
+int mipi_dsi_dcs_set_display_on(struct mipi_dsi_device *dsi);
+
+/**
+ * mipi_dsi_dcs_set_column_address() - define the column extent of the frame
+ * memory accessed by the host processor
+ * @dsi: DSI peripheral device
+ * @start: first column of frame memory
+ * @end: last column of frame memory
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_set_column_address(struct mipi_dsi_device *dsi, u16 start,
+ u16 end);
+/**
+ * mipi_dsi_dcs_set_page_address() - define the page extent of the frame
+ * memory accessed by the host processor
+ * @dsi: DSI peripheral device
+ * @start: first page of frame memory
+ * @end: last page of frame memory
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_set_page_address(struct mipi_dsi_device *dsi, u16 start,
+ u16 end);
+
+/**
+ * mipi_dsi_dcs_set_tear_off() - turn off the display module's Tearing Effect
+ * output signal on the TE signal line
+ * @dsi: DSI peripheral device
+ *
+ * Return: 0 on success or a negative error code on failure
+ */
+int mipi_dsi_dcs_set_tear_off(struct mipi_dsi_device *dsi);
+
+/**
+ * mipi_dsi_dcs_set_tear_on() - turn on the display module's Tearing Effect
+ * output signal on the TE signal line.
+ * @dsi: DSI peripheral device
+ * @mode: the Tearing Effect Output Line mode
+ *
+ * Return: 0 on success or a negative error code on failure
+ */
+int mipi_dsi_dcs_set_tear_on(struct mipi_dsi_device *dsi,
+ enum mipi_dsi_dcs_tear_mode mode);
+
+/**
+ * mipi_dsi_dcs_set_pixel_format() - sets the pixel format for the RGB image
+ * data used by the interface
+ * @dsi: DSI peripheral device
+ * @format: pixel format
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_set_pixel_format(struct mipi_dsi_device *dsi, u8 format);
+
+/**
+ * mipi_dsi_dcs_set_tear_scanline() - set the scanline to use as trigger for
+ * the Tearing Effect output signal of the display module
+ * @dsi: DSI peripheral device
+ * @scanline: scanline to use as trigger
+ *
+ * Return: 0 on success or a negative error code on failure
+ */
+int mipi_dsi_dcs_set_tear_scanline(struct mipi_dsi_device *dsi, u16 scanline);
+
+/**
+ * mipi_dsi_dcs_set_display_brightness() - sets the brightness value of the
+ * display
+ * @dsi: DSI peripheral device
+ * @brightness: brightness value
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_set_display_brightness(struct mipi_dsi_device *dsi,
+ u16 brightness);
+
+/**
+ * mipi_dsi_dcs_get_display_brightness() - gets the current brightness value
+ * of the display
+ * @dsi: DSI peripheral device
+ * @brightness: brightness value
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_get_display_brightness(struct mipi_dsi_device *dsi,
+ u16 *brightness);
+int mipi_dsi_dcs_set_display_brightness_large(struct mipi_dsi_device *dsi,
+ u16 brightness);
+int mipi_dsi_dcs_get_display_brightness_large(struct mipi_dsi_device *dsi,
+ u16 *brightness);
+
+void mipi_dsi_dcs_nop_multi(struct mipi_dsi_multi_context *ctx);
+void mipi_dsi_dcs_enter_sleep_mode_multi(struct mipi_dsi_multi_context *ctx);
+void mipi_dsi_dcs_exit_sleep_mode_multi(struct mipi_dsi_multi_context *ctx);
+void mipi_dsi_dcs_set_display_off_multi(struct mipi_dsi_multi_context *ctx);
+void mipi_dsi_dcs_set_display_on_multi(struct mipi_dsi_multi_context *ctx);
+void mipi_dsi_dcs_set_tear_on_multi(struct mipi_dsi_multi_context *ctx,
+ enum mipi_dsi_dcs_tear_mode mode);
+void mipi_dsi_turn_on_peripheral_multi(struct mipi_dsi_multi_context *ctx);
+void mipi_dsi_dcs_soft_reset_multi(struct mipi_dsi_multi_context *ctx);
+void mipi_dsi_dcs_set_display_brightness_multi(struct mipi_dsi_multi_context *ctx,
+ u16 brightness);
+void mipi_dsi_dcs_set_pixel_format_multi(struct mipi_dsi_multi_context *ctx,
+ u8 format);
+void mipi_dsi_dcs_set_column_address_multi(struct mipi_dsi_multi_context *ctx,
+ u16 start, u16 end);
+void mipi_dsi_dcs_set_page_address_multi(struct mipi_dsi_multi_context *ctx,
+ u16 start, u16 end);
+void mipi_dsi_dcs_set_tear_scanline_multi(struct mipi_dsi_multi_context *ctx,
+ u16 scanline);
+void mipi_dsi_dcs_set_tear_off_multi(struct mipi_dsi_multi_context *ctx);
+
+/**
+ * mipi_dsi_generic_write_seq_multi - transmit data using a generic write packet
+ *
+ * This macro will print errors for you and error handling is optimized for
+ * callers that call this multiple times in a row.
+ *
+ * @ctx: Context for multiple DSI transactions
+ * @seq: buffer containing the payload
+ */
+#define mipi_dsi_generic_write_seq_multi(ctx, seq...) \
+ do { \
+ static const u8 d[] = { seq }; \
+ mipi_dsi_generic_write_multi(ctx, d, ARRAY_SIZE(d)); \
+ } while (0)
+
+/**
+ * mipi_dsi_dcs_write_seq_multi - transmit a DCS command with payload
+ *
+ * This macro will print errors for you and error handling is optimized for
+ * callers that call this multiple times in a row.
+ *
+ * @ctx: Context for multiple DSI transactions
+ * @cmd: Command
+ * @seq: buffer containing data to be transmitted
+ */
+#define mipi_dsi_dcs_write_seq_multi(ctx, cmd, seq...) \
+ do { \
+ static const u8 d[] = { cmd, seq }; \
+ mipi_dsi_dcs_write_buffer_multi(ctx, d, ARRAY_SIZE(d)); \
+ } while (0)
+
+/**
+ * struct mipi_dsi_driver - DSI driver
+ * @driver: device driver model driver
+ * @probe: callback for device binding
+ * @remove: callback for device unbinding
+ */
+struct mipi_dsi_driver {
+ struct device_driver driver;
+ int(*probe)(struct mipi_dsi_device *dsi);
+ void (*remove)(struct mipi_dsi_device *dsi);
+};
+
+static inline struct mipi_dsi_driver *
+to_mipi_dsi_driver(struct device_driver *driver)
+{
+ return container_of(driver, struct mipi_dsi_driver, driver);
+}
+
+static inline void *mipi_dsi_get_drvdata(const struct mipi_dsi_device *dsi)
+{
+ return dev_get_drvdata(&dsi->dev);
+}
+
+static inline void mipi_dsi_set_drvdata(struct mipi_dsi_device *dsi, void *data)
+{
+ dev_set_drvdata(&dsi->dev, data);
+}
+
+int mipi_dsi_driver_register(struct mipi_dsi_driver *driver);
+void mipi_dsi_driver_unregister(struct mipi_dsi_driver *driver);
+
+#define module_mipi_dsi_driver(__mipi_dsi_driver) \
+ module_driver(__mipi_dsi_driver, mipi_dsi_driver_register, \
+ mipi_dsi_driver_unregister)
+
+extern struct bus_type mipi_dsi_bus_type;
+
+#endif /* MIPI_DSI_H */
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 13/16] video: add Designware MIPI-DSI support
2025-06-05 21:07 [PATCH 00/16] ARM: stm32mp: add MIPI DSI support Ahmad Fatoum
` (11 preceding siblings ...)
2025-06-05 21:07 ` [PATCH 12/16] video: add base MIPI DSI support Ahmad Fatoum
@ 2025-06-05 21:07 ` Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 14/16] video: add STM32 MIPI DSI video driver Ahmad Fatoum
` (2 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ahmad Fatoum @ 2025-06-05 21:07 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
The Designware MIPI-DSI controller is used on both STM32MP1 and Rockchip
RK35x both of which already have a display controller supported in
barebox.
Port the Linux driver in preparation for enabling MIPI-DSI on the
STM32MP1.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
drivers/video/Kconfig | 10 +
drivers/video/Makefile | 1 +
drivers/video/dw_mipi_dsi.c | 1042 +++++++++++++++++++++++++++++++++++
include/video/dw_mipi_dsi.h | 81 +++
4 files changed, 1134 insertions(+)
create mode 100644 drivers/video/dw_mipi_dsi.c
create mode 100644 include/video/dw_mipi_dsi.h
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 4ad7cd6b25d4..818ff4a0f1c8 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -215,4 +215,14 @@ config DRIVER_VIDEO_DW_HDMI
controller. To make use of this driver you need platform specific
glue code to be selected as well.
+config DRIVER_VIDEO_DW_MIPI_DSI
+ tristate "Designware MIPI-DSI support"
+ select DRIVER_VIDEO_MIPI_DSI
+ select VIDEO_VPL
+ select OFTREE
+ help
+ Say Y here if you want to enable support for the Designware MIPI-DSI
+ controller. To make use of this driver you need platform specific
+ glue code to be selected as well.
+
endif
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 9a222fb370aa..73d135776102 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -33,5 +33,6 @@ obj-$(CONFIG_DRIVER_VIDEO_EFI_GOP) += efi_gop.o
obj-$(CONFIG_DRIVER_VIDEO_FB_SSD1307) += ssd1307fb.o
obj-$(CONFIG_BACKLIGHT_RAVE_SP) += rave-sp-backlight.o
obj-$(CONFIG_DRIVER_VIDEO_DW_HDMI) += dw-hdmi.o
+obj-$(CONFIG_DRIVER_VIDEO_DW_MIPI_DSI) += dw_mipi_dsi.o
obj-$(CONFIG_DRIVER_VIDEO_BOCHS) += bochs/
obj-$(CONFIG_DRIVER_VIDEO_ROCKCHIP) += rockchip/
diff --git a/drivers/video/dw_mipi_dsi.c b/drivers/video/dw_mipi_dsi.c
new file mode 100644
index 000000000000..5205457512f6
--- /dev/null
+++ b/drivers/video/dw_mipi_dsi.c
@@ -0,0 +1,1042 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2016, Fuzhou Rockchip Electronics Co., Ltd
+ * Copyright (C) 2019, STMicroelectronics - All Rights Reserved
+ * Author(s): Philippe Cornu <philippe.cornu@st.com> for STMicroelectronics.
+ * Yannick Fertre <yannick.fertre@st.com> for STMicroelectronics.
+ *
+ * This generic Synopsys DesignWare MIPI DSI host driver is inspired from
+ * the Linux Kernel driver drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c.
+ */
+
+#include <linux/clk.h>
+#include <linux/printk.h>
+#include <errno.h>
+#include <fb.h>
+#include <of_graph.h>
+#include <linux/io.h>
+#include <linux/bitops.h>
+#include <clock.h>
+#include <linux/iopoll.h>
+#include <linux/time.h>
+#include <linux/reset.h>
+#include <video/vpl.h>
+#include <video/videomode.h>
+#include <video/mipi_dsi.h>
+#include <video/dw_mipi_dsi.h>
+
+#define DRM_DEV_ERROR dev_err
+#define DRM_DEBUG_DRIVER pr_debug
+
+#define HWVER_131 0x31333100 /* IP version 1.31 */
+
+#define DSI_VERSION 0x00
+#define VERSION GENMASK(31, 8)
+
+#define DSI_PWR_UP 0x04
+#define RESET 0
+#define POWERUP BIT(0)
+
+#define DSI_CLKMGR_CFG 0x08
+#define TO_CLK_DIVISION(div) (((div) & 0xff) << 8)
+#define TX_ESC_CLK_DIVISION(div) ((div) & 0xff)
+
+#define DSI_DPI_VCID 0x0c
+#define DPI_VCID(vcid) ((vcid) & 0x3)
+
+#define DSI_DPI_COLOR_CODING 0x10
+#define LOOSELY18_EN BIT(8)
+#define DPI_COLOR_CODING_16BIT_1 0x0
+#define DPI_COLOR_CODING_16BIT_2 0x1
+#define DPI_COLOR_CODING_16BIT_3 0x2
+#define DPI_COLOR_CODING_18BIT_1 0x3
+#define DPI_COLOR_CODING_18BIT_2 0x4
+#define DPI_COLOR_CODING_24BIT 0x5
+
+#define DSI_DPI_CFG_POL 0x14
+#define COLORM_ACTIVE_LOW BIT(4)
+#define SHUTD_ACTIVE_LOW BIT(3)
+#define HSYNC_ACTIVE_LOW BIT(2)
+#define VSYNC_ACTIVE_LOW BIT(1)
+#define DATAEN_ACTIVE_LOW BIT(0)
+
+#define DSI_DPI_LP_CMD_TIM 0x18
+#define OUTVACT_LPCMD_TIME(p) (((p) & 0xff) << 16)
+#define INVACT_LPCMD_TIME(p) ((p) & 0xff)
+
+#define DSI_DBI_VCID 0x1c
+#define DSI_DBI_CFG 0x20
+#define DSI_DBI_PARTITIONING_EN 0x24
+#define DSI_DBI_CMDSIZE 0x28
+
+#define DSI_PCKHDL_CFG 0x2c
+#define CRC_RX_EN BIT(4)
+#define ECC_RX_EN BIT(3)
+#define BTA_EN BIT(2)
+#define EOTP_RX_EN BIT(1)
+#define EOTP_TX_EN BIT(0)
+
+#define DSI_GEN_VCID 0x30
+
+#define DSI_MODE_CFG 0x34
+#define ENABLE_VIDEO_MODE 0
+#define ENABLE_CMD_MODE BIT(0)
+
+#define DSI_VID_MODE_CFG 0x38
+#define ENABLE_LOW_POWER (0x3f << 8)
+#define ENABLE_LOW_POWER_MASK (0x3f << 8)
+#define VID_MODE_TYPE_NON_BURST_SYNC_PULSES 0x0
+#define VID_MODE_TYPE_NON_BURST_SYNC_EVENTS 0x1
+#define VID_MODE_TYPE_BURST 0x2
+#define VID_MODE_TYPE_MASK 0x3
+#define ENABLE_LOW_POWER_CMD BIT(15)
+#define VID_MODE_VPG_ENABLE BIT(16)
+#define VID_MODE_VPG_MODE BIT(20)
+#define VID_MODE_VPG_HORIZONTAL BIT(24)
+
+#define DSI_VID_PKT_SIZE 0x3c
+#define VID_PKT_SIZE(p) ((p) & 0x3fff)
+
+#define DSI_VID_NUM_CHUNKS 0x40
+#define VID_NUM_CHUNKS(c) ((c) & 0x1fff)
+
+#define DSI_VID_NULL_SIZE 0x44
+#define VID_NULL_SIZE(b) ((b) & 0x1fff)
+
+#define DSI_VID_HSA_TIME 0x48
+#define DSI_VID_HBP_TIME 0x4c
+#define DSI_VID_HLINE_TIME 0x50
+#define DSI_VID_VSA_LINES 0x54
+#define DSI_VID_VBP_LINES 0x58
+#define DSI_VID_VFP_LINES 0x5c
+#define DSI_VID_VACTIVE_LINES 0x60
+#define DSI_EDPI_CMD_SIZE 0x64
+
+#define DSI_CMD_MODE_CFG 0x68
+#define MAX_RD_PKT_SIZE_LP BIT(24)
+#define DCS_LW_TX_LP BIT(19)
+#define DCS_SR_0P_TX_LP BIT(18)
+#define DCS_SW_1P_TX_LP BIT(17)
+#define DCS_SW_0P_TX_LP BIT(16)
+#define GEN_LW_TX_LP BIT(14)
+#define GEN_SR_2P_TX_LP BIT(13)
+#define GEN_SR_1P_TX_LP BIT(12)
+#define GEN_SR_0P_TX_LP BIT(11)
+#define GEN_SW_2P_TX_LP BIT(10)
+#define GEN_SW_1P_TX_LP BIT(9)
+#define GEN_SW_0P_TX_LP BIT(8)
+#define ACK_RQST_EN BIT(1)
+#define TEAR_FX_EN BIT(0)
+
+#define CMD_MODE_ALL_LP (MAX_RD_PKT_SIZE_LP | \
+ DCS_LW_TX_LP | \
+ DCS_SR_0P_TX_LP | \
+ DCS_SW_1P_TX_LP | \
+ DCS_SW_0P_TX_LP | \
+ GEN_LW_TX_LP | \
+ GEN_SR_2P_TX_LP | \
+ GEN_SR_1P_TX_LP | \
+ GEN_SR_0P_TX_LP | \
+ GEN_SW_2P_TX_LP | \
+ GEN_SW_1P_TX_LP | \
+ GEN_SW_0P_TX_LP)
+
+#define DSI_GEN_HDR 0x6c
+#define DSI_GEN_PLD_DATA 0x70
+
+#define DSI_CMD_PKT_STATUS 0x74
+#define GEN_RD_CMD_BUSY BIT(6)
+#define GEN_PLD_R_FULL BIT(5)
+#define GEN_PLD_R_EMPTY BIT(4)
+#define GEN_PLD_W_FULL BIT(3)
+#define GEN_PLD_W_EMPTY BIT(2)
+#define GEN_CMD_FULL BIT(1)
+#define GEN_CMD_EMPTY BIT(0)
+
+#define DSI_TO_CNT_CFG 0x78
+#define HSTX_TO_CNT(p) (((p) & 0xffff) << 16)
+#define LPRX_TO_CNT(p) ((p) & 0xffff)
+
+#define DSI_HS_RD_TO_CNT 0x7c
+#define DSI_LP_RD_TO_CNT 0x80
+#define DSI_HS_WR_TO_CNT 0x84
+#define DSI_LP_WR_TO_CNT 0x88
+#define DSI_BTA_TO_CNT 0x8c
+
+#define DSI_LPCLK_CTRL 0x94
+#define AUTO_CLKLANE_CTRL BIT(1)
+#define PHY_TXREQUESTCLKHS BIT(0)
+
+#define DSI_PHY_TMR_LPCLK_CFG 0x98
+#define PHY_CLKHS2LP_TIME(lbcc) (((lbcc) & 0x3ff) << 16)
+#define PHY_CLKLP2HS_TIME(lbcc) ((lbcc) & 0x3ff)
+
+#define DSI_PHY_TMR_CFG 0x9c
+#define PHY_HS2LP_TIME(lbcc) (((lbcc) & 0xff) << 24)
+#define PHY_LP2HS_TIME(lbcc) (((lbcc) & 0xff) << 16)
+#define MAX_RD_TIME(lbcc) ((lbcc) & 0x7fff)
+#define PHY_HS2LP_TIME_V131(lbcc) (((lbcc) & 0x3ff) << 16)
+#define PHY_LP2HS_TIME_V131(lbcc) ((lbcc) & 0x3ff)
+
+#define DSI_PHY_RSTZ 0xa0
+#define PHY_DISFORCEPLL 0
+#define PHY_ENFORCEPLL BIT(3)
+#define PHY_DISABLECLK 0
+#define PHY_ENABLECLK BIT(2)
+#define PHY_RSTZ 0
+#define PHY_UNRSTZ BIT(1)
+#define PHY_SHUTDOWNZ 0
+#define PHY_UNSHUTDOWNZ BIT(0)
+
+#define DSI_PHY_IF_CFG 0xa4
+#define PHY_STOP_WAIT_TIME(cycle) (((cycle) & 0xff) << 8)
+#define N_LANES(n) (((n) - 1) & 0x3)
+
+#define DSI_PHY_ULPS_CTRL 0xa8
+#define DSI_PHY_TX_TRIGGERS 0xac
+
+#define DSI_PHY_STATUS 0xb0
+#define PHY_STOP_STATE_CLK_LANE BIT(2)
+#define PHY_LOCK BIT(0)
+
+#define DSI_PHY_TST_CTRL0 0xb4
+#define PHY_TESTCLK BIT(1)
+#define PHY_UNTESTCLK 0
+#define PHY_TESTCLR BIT(0)
+#define PHY_UNTESTCLR 0
+
+#define DSI_PHY_TST_CTRL1 0xb8
+#define PHY_TESTEN BIT(16)
+#define PHY_UNTESTEN 0
+#define PHY_TESTDOUT(n) (((n) & 0xff) << 8)
+#define PHY_TESTDIN(n) ((n) & 0xff)
+
+#define DSI_INT_ST0 0xbc
+#define DSI_INT_ST1 0xc0
+#define DSI_INT_MSK0 0xc4
+#define DSI_INT_MSK1 0xc8
+
+#define DSI_PHY_TMR_RD_CFG 0xf4
+#define MAX_RD_TIME_V131(lbcc) ((lbcc) & 0x7fff)
+
+#define PHY_STATUS_TIMEOUT_US 10000
+#define CMD_PKT_STATUS_TIMEOUT_US 20000
+
+enum vpg_mode {
+ VPG_DISABLED,
+ VPG_BER,
+ VPG_COLORS_V,
+ VPG_COLORS_H,
+};
+
+struct dw_mipi_dsi {
+ struct mipi_dsi_host dsi_host;
+ struct device *dev;
+ void __iomem *base;
+ struct vpl vpl;
+ struct vpl_bridge *panel_bridge;
+
+ struct clk *pclk;
+
+ unsigned int lane_mbps; /* per lane */
+ u32 channel;
+ u32 lanes;
+ u32 format;
+ unsigned long mode_flags;
+ struct fb_videomode *mode;
+
+ int vpg_mode;
+
+ const struct dw_mipi_dsi_plat_data *plat_data;
+};
+
+/*
+ * The controller should generate 2 frames before
+ * preparing the peripheral.
+ */
+static void dw_mipi_dsi_wait_for_two_frames(const struct drm_display_mode *mode)
+{
+ int refresh, two_frames;
+
+ refresh = drm_mode_vrefresh(mode);
+ two_frames = DIV_ROUND_UP(MSEC_PER_SEC, refresh) * 2;
+ mdelay(two_frames);
+}
+
+static inline struct dw_mipi_dsi *host_to_dsi(struct mipi_dsi_host *host)
+{
+ return container_of(host, struct dw_mipi_dsi, dsi_host);
+}
+
+static inline void dsi_write(struct dw_mipi_dsi *dsi, u32 reg, u32 val)
+{
+ writel(val, dsi->base + reg);
+}
+
+static inline u32 dsi_read(struct dw_mipi_dsi *dsi, u32 reg)
+{
+ return readl(dsi->base + reg);
+}
+
+static int dw_mipi_dsi_host_attach(struct mipi_dsi_host *host,
+ struct mipi_dsi_device *device)
+{
+ struct dw_mipi_dsi *dsi = host_to_dsi(host);
+ const struct dw_mipi_dsi_plat_data *pdata = dsi->plat_data;
+ struct vpl_bridge *bridge;
+ int ret;
+
+ if (device->lanes > dsi->plat_data->max_data_lanes) {
+ dev_err(dsi->dev, "the number of data lanes(%u) is too many\n",
+ device->lanes);
+ return -EINVAL;
+ }
+
+ dsi->lanes = device->lanes;
+ dsi->channel = device->channel;
+ dsi->format = device->format;
+ dsi->mode_flags = device->mode_flags;
+
+ bridge = devm_vpl_of_get_bridge(dsi->dev, dsi->dev->of_node, 1, 0);
+ if (IS_ERR(bridge))
+ return PTR_ERR(bridge);
+
+ dsi->panel_bridge = bridge;
+
+ if (pdata->host_ops && pdata->host_ops->attach) {
+ ret = pdata->host_ops->attach(pdata->priv_data, device);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void dw_mipi_message_config(struct dw_mipi_dsi *dsi,
+ const struct mipi_dsi_msg *msg)
+{
+ bool lpm = msg->flags & MIPI_DSI_MSG_USE_LPM;
+ u32 val = 0;
+
+ /*
+ * TODO dw drv improvements
+ * largest packet sizes during hfp or during vsa/vpb/vfp
+ * should be computed according to byte lane, lane number and only
+ * if sending lp cmds in high speed is enable (PHY_TXREQUESTCLKHS)
+ */
+ dsi_write(dsi, DSI_DPI_LP_CMD_TIM, OUTVACT_LPCMD_TIME(16)
+ | INVACT_LPCMD_TIME(4));
+
+ if (msg->flags & MIPI_DSI_MSG_REQ_ACK)
+ val |= ACK_RQST_EN;
+ if (lpm)
+ val |= CMD_MODE_ALL_LP;
+
+ dsi_write(dsi, DSI_CMD_MODE_CFG, val);
+
+ val = dsi_read(dsi, DSI_VID_MODE_CFG);
+ if (lpm)
+ val |= ENABLE_LOW_POWER_CMD;
+ else
+ val &= ~ENABLE_LOW_POWER_CMD;
+ dsi_write(dsi, DSI_VID_MODE_CFG, val);
+}
+
+static int dw_mipi_dsi_gen_pkt_hdr_write(struct dw_mipi_dsi *dsi, u32 hdr_val)
+{
+ int ret;
+ u32 val, mask;
+
+ ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
+ val, !(val & GEN_CMD_FULL),
+ CMD_PKT_STATUS_TIMEOUT_US);
+ if (ret) {
+ dev_err(dsi->dev, "failed to get available command FIFO\n");
+ return ret;
+ }
+
+ dsi_write(dsi, DSI_GEN_HDR, hdr_val);
+
+ mask = GEN_CMD_EMPTY | GEN_PLD_W_EMPTY;
+ ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
+ val, (val & mask) == mask,
+ CMD_PKT_STATUS_TIMEOUT_US);
+ if (ret) {
+ dev_err(dsi->dev, "failed to write command FIFO\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int dw_mipi_dsi_write(struct dw_mipi_dsi *dsi,
+ const struct mipi_dsi_packet *packet)
+{
+ const u8 *tx_buf = packet->payload;
+ int len = packet->payload_length, pld_data_bytes = sizeof(u32), ret;
+ __le32 word;
+ u32 val;
+
+ while (len) {
+ if (len < pld_data_bytes) {
+ word = 0;
+ memcpy(&word, tx_buf, len);
+ dsi_write(dsi, DSI_GEN_PLD_DATA, le32_to_cpu(word));
+ len = 0;
+ } else {
+ memcpy(&word, tx_buf, pld_data_bytes);
+ dsi_write(dsi, DSI_GEN_PLD_DATA, le32_to_cpu(word));
+ tx_buf += pld_data_bytes;
+ len -= pld_data_bytes;
+ }
+
+ ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
+ val, !(val & GEN_PLD_W_FULL),
+ CMD_PKT_STATUS_TIMEOUT_US);
+ if (ret) {
+ dev_err(dsi->dev,
+ "failed to get available write payload FIFO\n");
+ return ret;
+ }
+ }
+
+ word = 0;
+ memcpy(&word, packet->header, sizeof(packet->header));
+ return dw_mipi_dsi_gen_pkt_hdr_write(dsi, le32_to_cpu(word));
+}
+
+static int dw_mipi_dsi_read(struct dw_mipi_dsi *dsi,
+ const struct mipi_dsi_msg *msg)
+{
+ int i, j, ret, len = msg->rx_len;
+ u8 *buf = msg->rx_buf;
+ u32 val;
+
+ /* Wait end of the read operation */
+ ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
+ val, !(val & GEN_RD_CMD_BUSY),
+ CMD_PKT_STATUS_TIMEOUT_US);
+ if (ret) {
+ dev_err(dsi->dev, "Timeout during read operation\n");
+ return ret;
+ }
+
+ for (i = 0; i < len; i += 4) {
+ /* Read fifo must not be empty before all bytes are read */
+ ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
+ val, !(val & GEN_PLD_R_EMPTY),
+ CMD_PKT_STATUS_TIMEOUT_US);
+ if (ret) {
+ dev_err(dsi->dev, "Read payload FIFO is empty\n");
+ return ret;
+ }
+
+ val = dsi_read(dsi, DSI_GEN_PLD_DATA);
+ for (j = 0; j < 4 && j + i < len; j++)
+ buf[i + j] = val >> (8 * j);
+ }
+
+ return ret;
+}
+
+static ssize_t dw_mipi_dsi_host_transfer(struct mipi_dsi_host *host,
+ const struct mipi_dsi_msg *msg)
+{
+ struct dw_mipi_dsi *dsi = host_to_dsi(host);
+ struct mipi_dsi_packet packet;
+ int ret, nb_bytes;
+
+ ret = mipi_dsi_create_packet(&packet, msg);
+ if (ret) {
+ dev_err(dsi->dev, "failed to create packet: %d\n", ret);
+ return ret;
+ }
+
+ dw_mipi_message_config(dsi, msg);
+
+ ret = dw_mipi_dsi_write(dsi, &packet);
+ if (ret)
+ return ret;
+
+ if (msg->rx_buf && msg->rx_len) {
+ ret = dw_mipi_dsi_read(dsi, msg);
+ if (ret)
+ return ret;
+ nb_bytes = msg->rx_len;
+ } else {
+ nb_bytes = packet.size;
+ }
+
+ return nb_bytes;
+}
+
+static const struct mipi_dsi_host_ops dw_mipi_dsi_host_ops = {
+ .attach = dw_mipi_dsi_host_attach,
+ .transfer = dw_mipi_dsi_host_transfer,
+};
+
+static void vpg_update(struct dw_mipi_dsi *dsi, u32 *val)
+{
+ *val &= ~(VID_MODE_VPG_ENABLE | VID_MODE_VPG_HORIZONTAL |
+ VID_MODE_VPG_MODE);
+
+ if (dsi->vpg_mode != VPG_DISABLED) {
+ *val |= VID_MODE_VPG_ENABLE;
+ *val |= dsi->vpg_mode == VPG_COLORS_H ?
+ VID_MODE_VPG_HORIZONTAL : 0;
+ *val |= dsi->vpg_mode == VPG_BER ? VID_MODE_VPG_MODE : 0;
+ }
+}
+
+static void dw_mipi_dsi_video_mode_config(struct dw_mipi_dsi *dsi)
+{
+ u32 val;
+
+ /*
+ * TODO dw drv improvements
+ * enabling low power is panel-dependent, we should use the
+ * panel configuration here...
+ */
+ val = ENABLE_LOW_POWER;
+
+ if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
+ val |= VID_MODE_TYPE_BURST;
+ else if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
+ val |= VID_MODE_TYPE_NON_BURST_SYNC_PULSES;
+ else
+ val |= VID_MODE_TYPE_NON_BURST_SYNC_EVENTS;
+
+ vpg_update(dsi, &val);
+
+ dsi_write(dsi, DSI_VID_MODE_CFG, val);
+}
+
+static void dw_mipi_dsi_set_mode(struct dw_mipi_dsi *dsi,
+ unsigned long mode_flags)
+{
+ u32 val;
+
+ dsi_write(dsi, DSI_PWR_UP, RESET);
+
+ if (mode_flags & MIPI_DSI_MODE_VIDEO) {
+ dsi_write(dsi, DSI_MODE_CFG, ENABLE_VIDEO_MODE);
+ dw_mipi_dsi_video_mode_config(dsi);
+ } else {
+ dsi_write(dsi, DSI_MODE_CFG, ENABLE_CMD_MODE);
+ }
+
+ val = PHY_TXREQUESTCLKHS;
+ if (dsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS)
+ val |= AUTO_CLKLANE_CTRL;
+ dsi_write(dsi, DSI_LPCLK_CTRL, val);
+
+ dsi_write(dsi, DSI_PWR_UP, POWERUP);
+}
+
+static void dw_mipi_dsi_disable(struct dw_mipi_dsi *dsi)
+{
+ dsi_write(dsi, DSI_PWR_UP, RESET);
+ dsi_write(dsi, DSI_PHY_RSTZ, PHY_RSTZ);
+}
+
+static void dw_mipi_dsi_init(struct dw_mipi_dsi *dsi)
+{
+ const struct dw_mipi_dsi_phy_ops *phy_ops = dsi->plat_data->phy_ops;
+ unsigned int esc_rate; /* in MHz */
+ u32 esc_clk_division;
+ int ret;
+
+ /*
+ * The maximum permitted escape clock is 20MHz and it is derived from
+ * lanebyteclk, which is running at "lane_mbps / 8".
+ */
+ if (phy_ops->get_esc_clk_rate) {
+ ret = phy_ops->get_esc_clk_rate(dsi->plat_data->priv_data,
+ &esc_rate);
+ if (ret)
+ DRM_DEBUG_DRIVER("Phy get_esc_clk_rate() failed\n");
+ } else
+ esc_rate = 20; /* Default to 20MHz */
+
+ /*
+ * We want :
+ * (lane_mbps >> 3) / esc_clk_division < X
+ * which is:
+ * (lane_mbps >> 3) / X > esc_clk_division
+ */
+ esc_clk_division = (dsi->lane_mbps >> 3) / esc_rate + 1;
+
+ dsi_write(dsi, DSI_PWR_UP, RESET);
+
+ /*
+ * TODO dw drv improvements
+ * timeout clock division should be computed with the
+ * high speed transmission counter timeout and byte lane...
+ */
+ dsi_write(dsi, DSI_CLKMGR_CFG, TO_CLK_DIVISION(0) |
+ TX_ESC_CLK_DIVISION(esc_clk_division));
+}
+
+static void dw_mipi_dsi_dpi_config(struct dw_mipi_dsi *dsi,
+ const struct drm_display_mode *mode)
+{
+ u32 val = 0, color = 0;
+
+ switch (dsi->format) {
+ case MIPI_DSI_FMT_RGB888:
+ color = DPI_COLOR_CODING_24BIT;
+ break;
+ case MIPI_DSI_FMT_RGB666:
+ color = DPI_COLOR_CODING_18BIT_2 | LOOSELY18_EN;
+ break;
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ color = DPI_COLOR_CODING_18BIT_1;
+ break;
+ case MIPI_DSI_FMT_RGB565:
+ color = DPI_COLOR_CODING_16BIT_1;
+ break;
+ }
+
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+ val |= VSYNC_ACTIVE_LOW;
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+ val |= HSYNC_ACTIVE_LOW;
+
+ dsi_write(dsi, DSI_DPI_VCID, DPI_VCID(dsi->channel));
+ dsi_write(dsi, DSI_DPI_COLOR_CODING, color);
+ dsi_write(dsi, DSI_DPI_CFG_POL, val);
+}
+
+static void dw_mipi_dsi_packet_handler_config(struct dw_mipi_dsi *dsi)
+{
+ u32 val = CRC_RX_EN | ECC_RX_EN | BTA_EN | EOTP_TX_EN;
+
+ if (dsi->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET)
+ val &= ~EOTP_TX_EN;
+
+ dsi_write(dsi, DSI_PCKHDL_CFG, val);
+}
+
+static void dw_mipi_dsi_video_packet_config(struct dw_mipi_dsi *dsi,
+ const struct drm_display_mode *mode)
+{
+ /*
+ * TODO dw drv improvements
+ * only burst mode is supported here. For non-burst video modes,
+ * we should compute DSI_VID_PKT_SIZE, DSI_VCCR.NUMC &
+ * DSI_VNPCR.NPSIZE... especially because this driver supports
+ * non-burst video modes, see dw_mipi_dsi_video_mode_config()...
+ */
+ dsi_write(dsi, DSI_VID_PKT_SIZE, VID_PKT_SIZE(mode->hdisplay));
+}
+
+static void dw_mipi_dsi_command_mode_config(struct dw_mipi_dsi *dsi)
+{
+ /*
+ * TODO dw drv improvements
+ * compute high speed transmission counter timeout according
+ * to the timeout clock division (TO_CLK_DIVISION) and byte lane...
+ */
+ dsi_write(dsi, DSI_TO_CNT_CFG, HSTX_TO_CNT(0) | LPRX_TO_CNT(0));
+ /*
+ * TODO dw drv improvements
+ * the Bus-Turn-Around Timeout Counter should be computed
+ * according to byte lane...
+ */
+ dsi_write(dsi, DSI_BTA_TO_CNT, 0xd00);
+ dsi_write(dsi, DSI_MODE_CFG, ENABLE_CMD_MODE);
+}
+
+static const u32 minimum_lbccs[] = {10, 5, 4, 3};
+
+static inline u32 dw_mipi_dsi_get_minimum_lbcc(struct dw_mipi_dsi *dsi)
+{
+ return minimum_lbccs[dsi->lanes - 1];
+}
+
+/* Get lane byte clock cycles. */
+static u32 dw_mipi_dsi_get_hcomponent_lbcc(struct dw_mipi_dsi *dsi,
+ const struct drm_display_mode *mode,
+ u32 hcomponent)
+{
+ u32 frac, lbcc, minimum_lbcc;
+ int bpp;
+
+ if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) {
+ /* lbcc based on lane_mbps */
+ lbcc = hcomponent * dsi->lane_mbps * MSEC_PER_SEC / 8;
+ } else {
+ /* lbcc based on pixel clock rate */
+ bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+ if (bpp < 0) {
+ dev_err(dsi->dev, "failed to get bpp\n");
+ return 0;
+ }
+
+ lbcc = div_u64((u64)hcomponent * mode->clock * bpp, dsi->lanes * 8);
+ }
+
+ frac = lbcc % mode->clock;
+ lbcc = lbcc / mode->clock;
+ if (frac)
+ lbcc++;
+
+ minimum_lbcc = dw_mipi_dsi_get_minimum_lbcc(dsi);
+
+ if (lbcc < minimum_lbcc)
+ lbcc = minimum_lbcc;
+
+ return lbcc;
+}
+
+static void dw_mipi_dsi_line_timer_config(struct dw_mipi_dsi *dsi,
+ const struct drm_display_mode *mode)
+{
+ u32 htotal, hsa, hbp, lbcc;
+
+ htotal = mode->htotal;
+ hsa = mode->hsync_end - mode->hsync_start;
+ hbp = mode->htotal - mode->hsync_end;
+
+ /*
+ * TODO dw drv improvements
+ * computations below may be improved...
+ */
+ lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, mode, htotal);
+ dsi_write(dsi, DSI_VID_HLINE_TIME, lbcc);
+
+ lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, mode, hsa);
+ dsi_write(dsi, DSI_VID_HSA_TIME, lbcc);
+
+ lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, mode, hbp);
+ dsi_write(dsi, DSI_VID_HBP_TIME, lbcc);
+}
+
+static void dw_mipi_dsi_vertical_timing_config(struct dw_mipi_dsi *dsi,
+ const struct drm_display_mode *mode)
+{
+ u32 vactive, vsa, vfp, vbp;
+
+ vactive = mode->vdisplay;
+ vsa = mode->vsync_end - mode->vsync_start;
+ vfp = mode->vsync_start - mode->vdisplay;
+ vbp = mode->vtotal - mode->vsync_end;
+
+ dsi_write(dsi, DSI_VID_VACTIVE_LINES, vactive);
+ dsi_write(dsi, DSI_VID_VSA_LINES, vsa);
+ dsi_write(dsi, DSI_VID_VFP_LINES, vfp);
+ dsi_write(dsi, DSI_VID_VBP_LINES, vbp);
+}
+
+static void dw_mipi_dsi_dphy_timing_config(struct dw_mipi_dsi *dsi)
+{
+ const struct dw_mipi_dsi_phy_ops *phy_ops = dsi->plat_data->phy_ops;
+ struct dw_mipi_dsi_dphy_timing timing;
+ u32 hw_version;
+ int ret;
+
+ ret = phy_ops->get_timing(dsi->plat_data->priv_data,
+ dsi->lane_mbps, &timing);
+ if (ret)
+ DRM_DEV_ERROR(dsi->dev, "Retrieving phy timings failed\n");
+
+ /*
+ * TODO dw drv improvements
+ * data & clock lane timers should be computed according to panel
+ * blankings and to the automatic clock lane control mode...
+ * note: DSI_PHY_TMR_CFG.MAX_RD_TIME should be in line with
+ * DSI_CMD_MODE_CFG.MAX_RD_PKT_SIZE_LP (see CMD_MODE_ALL_LP)
+ */
+
+ hw_version = dsi_read(dsi, DSI_VERSION) & VERSION;
+
+ if (hw_version >= HWVER_131) {
+ dsi_write(dsi, DSI_PHY_TMR_CFG,
+ PHY_HS2LP_TIME_V131(timing.data_hs2lp) |
+ PHY_LP2HS_TIME_V131(timing.data_lp2hs));
+ dsi_write(dsi, DSI_PHY_TMR_RD_CFG, MAX_RD_TIME_V131(10000));
+ } else {
+ dsi_write(dsi, DSI_PHY_TMR_CFG,
+ PHY_HS2LP_TIME(timing.data_hs2lp) |
+ PHY_LP2HS_TIME(timing.data_lp2hs) |
+ MAX_RD_TIME(10000));
+ }
+
+ dsi_write(dsi, DSI_PHY_TMR_LPCLK_CFG,
+ PHY_CLKHS2LP_TIME(timing.clk_hs2lp) |
+ PHY_CLKLP2HS_TIME(timing.clk_lp2hs));
+}
+
+static void dw_mipi_dsi_dphy_interface_config(struct dw_mipi_dsi *dsi)
+{
+ /*
+ * TODO dw drv improvements
+ * stop wait time should be the maximum between host dsi
+ * and panel stop wait times
+ */
+ dsi_write(dsi, DSI_PHY_IF_CFG, PHY_STOP_WAIT_TIME(0x20) |
+ N_LANES(dsi->lanes));
+}
+
+static void dw_mipi_dsi_dphy_init(struct dw_mipi_dsi *dsi)
+{
+ /* Clear PHY state */
+ dsi_write(dsi, DSI_PHY_RSTZ, PHY_DISFORCEPLL | PHY_DISABLECLK
+ | PHY_RSTZ | PHY_SHUTDOWNZ);
+ dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLR);
+ dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLR);
+ dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLR);
+}
+
+static void dw_mipi_dsi_dphy_enable(struct dw_mipi_dsi *dsi)
+{
+ u32 val;
+ int ret;
+
+ dsi_write(dsi, DSI_PHY_RSTZ, PHY_ENFORCEPLL | PHY_ENABLECLK |
+ PHY_UNRSTZ | PHY_UNSHUTDOWNZ);
+
+ ret = readl_poll_timeout(dsi->base + DSI_PHY_STATUS, val,
+ val & PHY_LOCK, PHY_STATUS_TIMEOUT_US);
+ if (ret)
+ DRM_DEBUG_DRIVER("failed to wait phy lock state\n");
+
+ ret = readl_poll_timeout(dsi->base + DSI_PHY_STATUS,
+ val, val & PHY_STOP_STATE_CLK_LANE,
+ PHY_STATUS_TIMEOUT_US);
+ if (ret)
+ DRM_DEBUG_DRIVER("failed to wait phy clk lane stop state\n");
+}
+
+static void dw_mipi_dsi_clear_err(struct dw_mipi_dsi *dsi)
+{
+ dsi_read(dsi, DSI_INT_ST0);
+ dsi_read(dsi, DSI_INT_ST1);
+ dsi_write(dsi, DSI_INT_MSK0, 0);
+ dsi_write(dsi, DSI_INT_MSK1, 0);
+}
+
+static void dw_mipi_dsi_bridge_post_atomic_disable(struct dw_mipi_dsi *dsi)
+{
+ const struct dw_mipi_dsi_phy_ops *phy_ops = dsi->plat_data->phy_ops;
+
+ /*
+ * Switch to command mode before panel-bridge post_disable &
+ * panel unprepare.
+ * Note: panel-bridge disable & panel disable has been called
+ * before by the drm framework.
+ */
+ dw_mipi_dsi_set_mode(dsi, 0);
+
+ if (phy_ops->power_off)
+ phy_ops->power_off(dsi->plat_data->priv_data);
+
+ dw_mipi_dsi_disable(dsi);
+ clk_disable_unprepare(dsi->pclk);
+}
+
+static unsigned int dw_mipi_dsi_get_lanes(struct dw_mipi_dsi *dsi)
+{
+ /* only single-dsi supported, so no other instance to consider */
+ return dsi->lanes;
+}
+
+static void dw_mipi_dsi_mode_set(struct dw_mipi_dsi *dsi,
+ const struct drm_display_mode *adjusted_mode)
+{
+ const struct dw_mipi_dsi_phy_ops *phy_ops = dsi->plat_data->phy_ops;
+ void *priv_data = dsi->plat_data->priv_data;
+ int ret;
+ u32 lanes = dw_mipi_dsi_get_lanes(dsi);
+
+ clk_prepare_enable(dsi->pclk);
+
+ ret = phy_ops->get_lane_mbps(priv_data, adjusted_mode, dsi->mode_flags,
+ lanes, dsi->format, &dsi->lane_mbps);
+ if (ret)
+ DRM_DEBUG_DRIVER("Phy get_lane_mbps() failed\n");
+
+ dw_mipi_dsi_init(dsi);
+ dw_mipi_dsi_dpi_config(dsi, adjusted_mode);
+ dw_mipi_dsi_packet_handler_config(dsi);
+ dw_mipi_dsi_video_mode_config(dsi);
+ dw_mipi_dsi_video_packet_config(dsi, adjusted_mode);
+ dw_mipi_dsi_command_mode_config(dsi);
+ dw_mipi_dsi_line_timer_config(dsi, adjusted_mode);
+ dw_mipi_dsi_vertical_timing_config(dsi, adjusted_mode);
+
+ dw_mipi_dsi_dphy_init(dsi);
+ dw_mipi_dsi_dphy_timing_config(dsi);
+ dw_mipi_dsi_dphy_interface_config(dsi);
+
+ dw_mipi_dsi_clear_err(dsi);
+
+ ret = phy_ops->init(priv_data);
+ if (ret)
+ DRM_DEBUG_DRIVER("Phy init() failed\n");
+
+ dw_mipi_dsi_dphy_enable(dsi);
+
+ dw_mipi_dsi_wait_for_two_frames(adjusted_mode);
+
+ /* Switch to cmd mode for panel-bridge pre_enable & panel prepare */
+ dw_mipi_dsi_set_mode(dsi, 0);
+
+ if (phy_ops->power_on)
+ phy_ops->power_on(dsi->plat_data->priv_data);
+}
+
+static int vpg_param_set(struct param_d *param, void *data)
+{
+ struct dw_mipi_dsi *dsi = data;
+ u32 mode_cfg;
+
+ mode_cfg = dsi_read(dsi, DSI_VID_MODE_CFG);
+
+ vpg_update(dsi, &mode_cfg);
+
+ dsi_write(dsi, DSI_VID_MODE_CFG, mode_cfg);
+
+ return 0;
+}
+
+static int dw_mipi_dsi_ioctl(struct vpl *vpl, unsigned int port,
+ unsigned int cmd, void *data)
+{
+ struct dw_mipi_dsi *dsi = container_of(vpl, struct dw_mipi_dsi, vpl);
+ struct drm_display_mode mode = {};
+
+ switch (cmd) {
+ case VPL_ENABLE:
+ /* Switch to video mode for panel-bridge enable & panel enable */
+ dw_mipi_dsi_set_mode(dsi, MIPI_DSI_MODE_VIDEO);
+ break;
+ case VPL_DISABLE:
+ dw_mipi_dsi_bridge_post_atomic_disable(dsi);
+ break;
+ case VPL_PREPARE:
+ /*
+ * We store this to simplify debugging by allowing:
+ * vpl->ioctl(vpl, output_port, VPL_DISABLE, NULL);
+ * vpl->ioctl(vpl, output_port, VPL_PREPARE, NULL);
+ * to possibly get an already enabled display back into command mode.
+ */
+ if (data)
+ dsi->mode = data;
+ if (!dsi->mode)
+ return -EINVAL;
+ fb_videomode_to_drm_display_mode(dsi->mode, &mode);
+ dw_mipi_dsi_mode_set(dsi, &mode);
+ break;
+ }
+
+ /* Probes should have occured by now */
+ WARN_ON(!dsi->panel_bridge);
+
+ return vpl_bridge_ioctl(dsi->panel_bridge, cmd, data);
+}
+
+static const char *vpg_modes[] = {
+ [VPG_DISABLED] = "disabled",
+ [VPG_BER] = "ber",
+ [VPG_COLORS_V] = "colors",
+ [VPG_COLORS_H] = "colors-horizontal",
+};
+
+static struct dw_mipi_dsi *
+dw_mipi_dsi_probe(struct device *dev,
+ const struct dw_mipi_dsi_plat_data *plat_data)
+{
+ struct dw_mipi_dsi *dsi;
+ struct reset_control *apb_rst;
+ struct resource *iores;
+ int ret;
+
+ dsi = xzalloc(sizeof(*dsi));
+
+ dsi->dev = dev;
+ dsi->plat_data = plat_data;
+
+ if (!plat_data->phy_ops->init || !plat_data->phy_ops->get_lane_mbps ||
+ !plat_data->phy_ops->get_timing) {
+ dev_err(dev, "Phy not properly configured\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ if (!plat_data->base) {
+ iores = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(iores))
+ return ERR_CAST(iores);
+ dsi->base = IOMEM(iores->start);
+ } else {
+ dsi->base = plat_data->base;
+ }
+
+ dsi->pclk = clk_get(dev, "pclk");
+ if (IS_ERR(dsi->pclk))
+ return dev_err_cast_probe(dev, dsi->pclk,
+ "Unable to get pclk\n");
+
+ /*
+ * Note that the reset was not defined in the initial device tree, so
+ * we have to be prepared for it not being found.
+ */
+ apb_rst = reset_control_get_optional(dev, "apb");
+ if (IS_ERR(apb_rst))
+ return dev_err_cast_probe(dev, apb_rst,
+ "Unable to get reset control\n");
+
+ if (apb_rst) {
+ ret = clk_prepare_enable(dsi->pclk);
+ if (ret) {
+ dev_err(dev, "%s: Failed to enable pclk\n", __func__);
+ return ERR_PTR(ret);
+ }
+
+ reset_control_assert(apb_rst);
+ udelay(10);
+ reset_control_deassert(apb_rst);
+
+ clk_disable_unprepare(dsi->pclk);
+ }
+
+ dsi->vpl.node = dev->device_node;
+ dsi->vpl.ioctl = dw_mipi_dsi_ioctl;
+ ret = vpl_register(&dsi->vpl);
+ if (ret)
+ return ERR_PTR(ret);
+
+ dsi->dsi_host.ops = &dw_mipi_dsi_host_ops;
+ dsi->dsi_host.dev = dev;
+ ret = mipi_dsi_host_register(&dsi->dsi_host);
+ if (ret)
+ return dev_err_ptr_probe(dev, ret,
+ "Failed to register MIPI host\n");
+
+ /* Expose Video Pattern Generator for debug purposes */
+ dev_add_param_enum(dev, "vpg", vpg_param_set, NULL,
+ &dsi->vpg_mode, vpg_modes,
+ ARRAY_SIZE(vpg_modes), dsi);
+
+ return dsi;
+}
+
+struct dw_mipi_dsi *dw_mipi_dsi_bind(struct device *dev,
+ const struct dw_mipi_dsi_plat_data *plat_data)
+{
+ return dw_mipi_dsi_probe(dev, plat_data);
+}
+EXPORT_SYMBOL_GPL(dw_mipi_dsi_bind);
+
+struct device_node *dw_mipi_dsi_crtc_node(struct dw_mipi_dsi *dsi)
+{
+ return dsi->dsi_host.dev->of_node;
+}
+EXPORT_SYMBOL_GPL(dw_mipi_dsi_crtc_node);
+
+MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
+MODULE_AUTHOR("Philippe Cornu <philippe.cornu@st.com>");
+MODULE_AUTHOR("Yannick Fertré <yannick.fertre@st.com>");
+MODULE_DESCRIPTION("DW MIPI DSI host controller driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:dw-mipi-dsi");
diff --git a/include/video/dw_mipi_dsi.h b/include/video/dw_mipi_dsi.h
new file mode 100644
index 000000000000..9e8b279bb911
--- /dev/null
+++ b/include/video/dw_mipi_dsi.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) STMicroelectronics SA 2017
+ *
+ * Authors: Philippe Cornu <philippe.cornu@st.com>
+ * Yannick Fertre <yannick.fertre@st.com>
+ */
+
+#ifndef __DW_MIPI_DSI__
+#define __DW_MIPI_DSI__
+
+#include <linux/types.h>
+#include <linux/compiler.h>
+
+#include <video/drm/drm_modes.h>
+
+struct drm_display_mode;
+struct dw_mipi_dsi;
+struct mipi_dsi_device;
+struct device;
+
+/**
+ * struct dw_mipi_dsi_dphy_timing - DSI host phy timings
+ * @data_hs2lp: High Speed to Low Speed Data Transition Time
+ * @data_lp2hs: Low Speed to High Speed Data Transition Time
+ * @clk_hs2lp: High Speed to Low Speed Clock Transition Time
+ * @clk_lp2hs: Low Speed to High Speed Clock Transition Time
+ */
+struct dw_mipi_dsi_dphy_timing {
+ u16 data_hs2lp;
+ u16 data_lp2hs;
+ u16 clk_hs2lp;
+ u16 clk_lp2hs;
+};
+
+struct dw_mipi_dsi_phy_ops {
+ int (*init)(void *priv_data);
+ void (*power_on)(void *priv_data);
+ void (*power_off)(void *priv_data);
+ int (*get_lane_mbps)(void *priv_data,
+ const struct drm_display_mode *mode,
+ unsigned long mode_flags, u32 lanes, u32 format,
+ unsigned int *lane_mbps);
+ int (*get_timing)(void *priv_data, unsigned int lane_mbps,
+ struct dw_mipi_dsi_dphy_timing *timing);
+ int (*get_esc_clk_rate)(void *priv_data, unsigned int *esc_clk_rate);
+};
+
+struct dw_mipi_dsi_host_ops {
+ int (*attach)(void *priv_data,
+ struct mipi_dsi_device *dsi);
+ int (*detach)(void *priv_data,
+ struct mipi_dsi_device *dsi);
+};
+
+struct dw_mipi_dsi_plat_data {
+ void __iomem *base;
+ unsigned int max_data_lanes;
+
+ enum drm_mode_status (*mode_valid)(void *priv_data,
+ const struct drm_display_mode *mode,
+ unsigned long mode_flags,
+ u32 lanes, u32 format);
+
+ bool (*mode_fixup)(void *priv_data, const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode);
+
+ const struct dw_mipi_dsi_phy_ops *phy_ops;
+ const struct dw_mipi_dsi_host_ops *host_ops;
+
+ void *priv_data;
+};
+
+struct dw_mipi_dsi *
+dw_mipi_dsi_bind(struct device *dev,
+ const struct dw_mipi_dsi_plat_data *plat_data);
+static inline void dw_mipi_dsi_unbind(struct dw_mipi_dsi *dsi) { }
+
+struct device_node *dw_mipi_dsi_crtc_node(struct dw_mipi_dsi *dsi);
+
+#endif /* __DW_MIPI_DSI__ */
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 14/16] video: add STM32 MIPI DSI video driver
2025-06-05 21:07 [PATCH 00/16] ARM: stm32mp: add MIPI DSI support Ahmad Fatoum
` (12 preceding siblings ...)
2025-06-05 21:07 ` [PATCH 13/16] video: add Designware MIPI-DSI support Ahmad Fatoum
@ 2025-06-05 21:07 ` Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 15/16] video: add support for Orise Technology otm8009a panel Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 16/16] ARM: stm32mp: dk2: enable MIPI-DSI display by default Ahmad Fatoum
15 siblings, 0 replies; 17+ messages in thread
From: Ahmad Fatoum @ 2025-06-05 21:07 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
Now with the common Designware MIPI DSI support added, let's add STM32
glue driver to make use of it.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
drivers/video/Kconfig | 9 +
drivers/video/Makefile | 1 +
drivers/video/stm32_dsi.c | 457 ++++++++++++++++++++++++++++++++++++++
3 files changed, 467 insertions(+)
create mode 100644 drivers/video/stm32_dsi.c
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 818ff4a0f1c8..c4b244fa5a98 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -68,6 +68,15 @@ config DRIVER_VIDEO_STM32_LTDC
Say 'Y' here to enable framebuffer and splash screen support for
STM32 and STM32MP1.
+config DRIVER_VIDEO_STM32_DSI
+ bool "Enable STM32 DSI video support"
+ select VIDEO_VPL
+ select DRIVER_VIDEO_DW_MIPI_DSI
+ depends on ARCH_STM32 || COMPILE_TEST
+ help
+ This option enables support DSI internal bridge which can be used on
+ devices which have DSI devices connected.
+
config DRIVER_VIDEO_SDL
bool "SDL framebuffer driver"
depends on HAVE_LIBSDL2
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 73d135776102..f6c309ead23b 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_DRIVER_VIDEO_ATMEL) += atmel_lcdfb.o atmel_lcdfb_core.o
obj-$(CONFIG_DRIVER_VIDEO_ATMEL_HLCD) += atmel_hlcdfb.o atmel_lcdfb_core.o
obj-$(CONFIG_DRIVER_VIDEO_STM) += stm.o
obj-$(CONFIG_DRIVER_VIDEO_STM32_LTDC) += stm32_ltdc.o
+obj-$(CONFIG_DRIVER_VIDEO_STM32_DSI) += stm32_dsi.o
obj-$(CONFIG_DRIVER_VIDEO_IMX) += imx.o
obj-$(CONFIG_DRIVER_VIDEO_IMX_IPU) += imx-ipu-fb.o
obj-$(CONFIG_DRIVER_VIDEO_PXA) += pxa.o
diff --git a/drivers/video/stm32_dsi.c b/drivers/video/stm32_dsi.c
new file mode 100644
index 000000000000..757e0a7081ed
--- /dev/null
+++ b/drivers/video/stm32_dsi.c
@@ -0,0 +1,457 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019 STMicroelectronics - All Rights Reserved
+ * Author(s): Philippe Cornu <philippe.cornu@st.com> for STMicroelectronics.
+ * Yannick Fertre <yannick.fertre@st.com> for STMicroelectronics.
+ *
+ * This MIPI DSI controller driver is based on the Linux Kernel driver from
+ * drivers/gpu/drm/stm/dw_mipi_dsi-stm.c.
+ */
+
+#define pr_fmt(fmt) "stm32_dsi: " fmt
+
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <of.h>
+#include <of_graph.h>
+#include <linux/bitops.h>
+#include <linux/iopoll.h>
+#include <linux/printk.h>
+#include <regulator.h>
+
+#include <video/mipi_display.h>
+#include <video/mipi_dsi.h>
+#include <video/dw_mipi_dsi.h>
+#include <video/drm/drm_modes.h>
+
+#define HWVER_130 0x31333000 /* IP version 1.30 */
+#define HWVER_131 0x31333100 /* IP version 1.31 */
+
+/* DSI digital registers & bit definitions */
+#define DSI_VERSION 0x00
+#define VERSION GENMASK(31, 8)
+
+/*
+ * DSI wrapper registers & bit definitions
+ * Note: registers are named as in the Reference Manual
+ */
+#define DSI_WCFGR 0x0400 /* Wrapper ConFiGuration Reg */
+#define WCFGR_DSIM BIT(0) /* DSI Mode */
+#define WCFGR_COLMUX GENMASK(3, 1) /* COLor MUltipleXing */
+
+#define DSI_WCR 0x0404 /* Wrapper Control Reg */
+#define WCR_DSIEN BIT(3) /* DSI ENable */
+
+#define DSI_WISR 0x040C /* Wrapper Interrupt and Status Reg */
+#define WISR_PLLLS BIT(8) /* PLL Lock Status */
+#define WISR_RRS BIT(12) /* Regulator Ready Status */
+
+#define DSI_WPCR0 0x0418 /* Wrapper Phy Conf Reg 0 */
+#define WPCR0_UIX4 GENMASK(5, 0) /* Unit Interval X 4 */
+#define WPCR0_TDDL BIT(16) /* Turn Disable Data Lanes */
+
+#define DSI_WRPCR 0x0430 /* Wrapper Regulator & Pll Ctrl Reg */
+#define WRPCR_PLLEN BIT(0) /* PLL ENable */
+#define WRPCR_NDIV GENMASK(8, 2) /* pll loop DIVision Factor */
+#define WRPCR_IDF GENMASK(14, 11) /* pll Input Division Factor */
+#define WRPCR_ODF GENMASK(17, 16) /* pll Output Division Factor */
+#define WRPCR_REGEN BIT(24) /* REGulator ENable */
+#define WRPCR_BGREN BIT(28) /* BandGap Reference ENable */
+#define IDF_MIN 1
+#define IDF_MAX 7
+#define NDIV_MIN 10
+#define NDIV_MAX 125
+#define ODF_MIN 1
+#define ODF_MAX 8
+
+/* dsi color format coding according to the datasheet */
+enum dsi_color {
+ DSI_RGB565_CONF1,
+ DSI_RGB565_CONF2,
+ DSI_RGB565_CONF3,
+ DSI_RGB666_CONF1,
+ DSI_RGB666_CONF2,
+ DSI_RGB888,
+};
+
+#define LANE_MIN_KBPS 31250
+#define LANE_MAX_KBPS 500000
+
+/* Timeout for regulator on/off, pll lock/unlock & fifo empty */
+#define TIMEOUT_US 200000
+
+struct stm32_dsi_priv {
+ struct device *dev;
+ struct dw_mipi_dsi_plat_data pdata;
+ u32 pllref_clk;
+ u32 hw_version;
+ int lane_min_kbps;
+ int lane_max_kbps;
+ struct regulator *vdd_reg;
+};
+
+static inline void dsi_write(struct stm32_dsi_priv *dsi, u32 reg, u32 val)
+{
+ writel(val, dsi->pdata.base + reg);
+}
+
+static inline u32 dsi_read(struct stm32_dsi_priv *dsi, u32 reg)
+{
+ return readl(dsi->pdata.base + reg);
+}
+
+static inline void dsi_set(struct stm32_dsi_priv *dsi, u32 reg, u32 mask)
+{
+ dsi_write(dsi, reg, dsi_read(dsi, reg) | mask);
+}
+
+static inline void dsi_clear(struct stm32_dsi_priv *dsi, u32 reg, u32 mask)
+{
+ dsi_write(dsi, reg, dsi_read(dsi, reg) & ~mask);
+}
+
+static inline void dsi_update_bits(struct stm32_dsi_priv *dsi, u32 reg,
+ u32 mask, u32 val)
+{
+ dsi_write(dsi, reg, (dsi_read(dsi, reg) & ~mask) | val);
+}
+
+static enum dsi_color dsi_color_from_mipi(u32 fmt)
+{
+ switch (fmt) {
+ case MIPI_DSI_FMT_RGB888:
+ return DSI_RGB888;
+ case MIPI_DSI_FMT_RGB666:
+ return DSI_RGB666_CONF2;
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ return DSI_RGB666_CONF1;
+ case MIPI_DSI_FMT_RGB565:
+ return DSI_RGB565_CONF1;
+ default:
+ pr_err("MIPI color invalid, so we use rgb888\n");
+ }
+ return DSI_RGB888;
+}
+
+static int dsi_pll_get_clkout_khz(int clkin_khz, int idf, int ndiv, int odf)
+{
+ int divisor = idf * odf;
+
+ /* prevent from division by 0 */
+ if (!divisor)
+ return 0;
+
+ return DIV_ROUND_CLOSEST(clkin_khz * ndiv, divisor);
+}
+
+static int dsi_pll_get_params(struct stm32_dsi_priv *dsi,
+ int clkin_khz, int clkout_khz,
+ int *idf, int *ndiv, int *odf)
+{
+ int i, o, n, n_min, n_max;
+ int fvco_min, fvco_max, delta, best_delta; /* all in khz */
+
+ /* Early checks preventing division by 0 & odd results */
+ if (clkin_khz <= 0 || clkout_khz <= 0)
+ return -EINVAL;
+
+ fvco_min = dsi->lane_min_kbps * 2 * ODF_MAX;
+ fvco_max = dsi->lane_max_kbps * 2 * ODF_MIN;
+
+ best_delta = 1000000; /* big started value (1000000khz) */
+
+ for (i = IDF_MIN; i <= IDF_MAX; i++) {
+ /* Compute ndiv range according to Fvco */
+ n_min = ((fvco_min * i) / (2 * clkin_khz)) + 1;
+ n_max = (fvco_max * i) / (2 * clkin_khz);
+
+ /* No need to continue idf loop if we reach ndiv max */
+ if (n_min >= NDIV_MAX)
+ break;
+
+ /* Clamp ndiv to valid values */
+ if (n_min < NDIV_MIN)
+ n_min = NDIV_MIN;
+ if (n_max > NDIV_MAX)
+ n_max = NDIV_MAX;
+
+ for (o = ODF_MIN; o <= ODF_MAX; o *= 2) {
+ n = DIV_ROUND_CLOSEST(i * o * clkout_khz, clkin_khz);
+ /* Check ndiv according to vco range */
+ if (n < n_min || n > n_max)
+ continue;
+ /* Check if new delta is better & saves parameters */
+ delta = dsi_pll_get_clkout_khz(clkin_khz, i, n, o) -
+ clkout_khz;
+ if (delta < 0)
+ delta = -delta;
+ if (delta < best_delta) {
+ *idf = i;
+ *ndiv = n;
+ *odf = o;
+ best_delta = delta;
+ }
+ /* fast return in case of "perfect result" */
+ if (!delta)
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+static int dsi_phy_init(void *priv_data)
+{
+ struct stm32_dsi_priv *dsi = priv_data;
+ struct device *dev = dsi->dev;
+ u32 val;
+ int ret;
+
+ dev_dbg(dev, "Initialize DSI physical layer\n");
+
+ /* Enable the regulator */
+ dsi_set(dsi, DSI_WRPCR, WRPCR_REGEN | WRPCR_BGREN);
+ ret = readl_poll_timeout(dsi->pdata.base + DSI_WISR, val, val & WISR_RRS,
+ TIMEOUT_US);
+ if (ret) {
+ dev_dbg(dev, "!TIMEOUT! waiting REGU\n");
+ return ret;
+ }
+
+ /* Enable the DSI PLL & wait for its lock */
+ dsi_set(dsi, DSI_WRPCR, WRPCR_PLLEN);
+ ret = readl_poll_timeout(dsi->pdata.base + DSI_WISR, val, val & WISR_PLLLS,
+ TIMEOUT_US);
+ if (ret) {
+ dev_dbg(dev, "!TIMEOUT! waiting PLL\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void dsi_phy_post_set_mode(void *priv_data, unsigned long mode_flags)
+{
+ struct stm32_dsi_priv *dsi = priv_data;
+ struct device *dev = dsi->dev;
+
+ dev_dbg(dev, "Set mode %p enable %ld\n", dsi,
+ mode_flags & MIPI_DSI_MODE_VIDEO);
+
+ if (!dsi)
+ return;
+
+ /*
+ * DSI wrapper must be enabled in video mode & disabled in command mode.
+ * If wrapper is enabled in command mode, the display controller
+ * register access will hang.
+ */
+
+ if (mode_flags & MIPI_DSI_MODE_VIDEO)
+ dsi_set(dsi, DSI_WCR, WCR_DSIEN);
+ else
+ dsi_clear(dsi, DSI_WCR, WCR_DSIEN);
+}
+
+static void dsi_phy_post_power_on(void *priv_data)
+{
+ dsi_phy_post_set_mode(priv_data, MIPI_DSI_MODE_VIDEO);
+}
+
+static void dsi_phy_post_power_off(void *priv_data)
+{
+ dsi_phy_post_set_mode(priv_data, 0);
+}
+
+static int dsi_get_lane_mbps(void *priv_data, const struct drm_display_mode *mode,
+ unsigned long mode_flags, u32 lanes, u32 format,
+ unsigned int *lane_mbps)
+{
+ struct stm32_dsi_priv *dsi = priv_data;
+ struct device *dev = dsi->dev;
+ int idf, ndiv, odf, pll_in_khz, pll_out_khz;
+ int ret, bpp;
+ u32 val;
+
+ /* Update lane capabilities according to hw version */
+ dsi->lane_min_kbps = LANE_MIN_KBPS;
+ dsi->lane_max_kbps = LANE_MAX_KBPS;
+ if (dsi->hw_version == HWVER_131) {
+ dsi->lane_min_kbps *= 2;
+ dsi->lane_max_kbps *= 2;
+ }
+
+ pll_in_khz = dsi->pllref_clk / 1000;
+
+ /* Compute requested pll out */
+ bpp = mipi_dsi_pixel_format_to_bpp(format);
+ pll_out_khz = mode->clock * bpp / lanes;
+ /* Add 20% to pll out to be higher than pixel bw (burst mode only) */
+ pll_out_khz = (pll_out_khz * 12) / 10;
+ if (pll_out_khz > dsi->lane_max_kbps) {
+ pll_out_khz = dsi->lane_max_kbps;
+ dev_warn(dev, "Warning max phy mbps is used\n");
+ }
+ if (pll_out_khz < dsi->lane_min_kbps) {
+ pll_out_khz = dsi->lane_min_kbps;
+ dev_warn(dev, "Warning min phy mbps is used\n");
+ }
+
+ /* Compute best pll parameters */
+ idf = 0;
+ ndiv = 0;
+ odf = 0;
+ ret = dsi_pll_get_params(dsi, pll_in_khz, pll_out_khz,
+ &idf, &ndiv, &odf);
+ if (ret) {
+ dev_err(dev, "Warning dsi_pll_get_params(): bad params\n");
+ return ret;
+ }
+
+ /* Get the adjusted pll out value */
+ pll_out_khz = dsi_pll_get_clkout_khz(pll_in_khz, idf, ndiv, odf);
+
+ /* Set the PLL division factors */
+ dsi_update_bits(dsi, DSI_WRPCR, WRPCR_NDIV | WRPCR_IDF | WRPCR_ODF,
+ (ndiv << 2) | (idf << 11) | ((ffs(odf) - 1) << 16));
+
+ /* Compute uix4 & set the bit period in high-speed mode */
+ val = 4000000 / pll_out_khz;
+ dsi_update_bits(dsi, DSI_WPCR0, WPCR0_UIX4, val);
+
+ /* Select video mode by resetting DSIM bit */
+ dsi_clear(dsi, DSI_WCFGR, WCFGR_DSIM);
+
+ /* Select the color coding */
+ dsi_update_bits(dsi, DSI_WCFGR, WCFGR_COLMUX,
+ dsi_color_from_mipi(format) << 1);
+
+ *lane_mbps = pll_out_khz / 1000;
+
+ dev_dbg(dev, "pll_in %ukHz pll_out %ukHz lane_mbps %uMHz\n",
+ pll_in_khz, pll_out_khz, *lane_mbps);
+
+ return 0;
+}
+
+#define DSI_PHY_DELAY(fp, vp, mbps) DIV_ROUND_UP((fp) * (mbps) + 1000 * (vp), 8000)
+
+static int
+dsi_phy_get_timing(void *priv_data, unsigned int lane_mbps,
+ struct dw_mipi_dsi_dphy_timing *timing)
+{
+ /*
+ * From STM32MP157 datasheet, valid for STM32F469, STM32F7x9, STM32H747
+ * phy_clkhs2lp_time = (272+136*UI)/(8*UI)
+ * phy_clklp2hs_time = (512+40*UI)/(8*UI)
+ * phy_hs2lp_time = (192+64*UI)/(8*UI)
+ * phy_lp2hs_time = (256+32*UI)/(8*UI)
+ */
+ timing->clk_hs2lp = DSI_PHY_DELAY(272, 136, lane_mbps);
+ timing->clk_lp2hs = DSI_PHY_DELAY(512, 40, lane_mbps);
+ timing->data_hs2lp = DSI_PHY_DELAY(192, 64, lane_mbps);
+ timing->data_lp2hs = DSI_PHY_DELAY(256, 32, lane_mbps);
+
+ return 0;
+}
+
+static const struct dw_mipi_dsi_phy_ops dsi_stm_phy_ops = {
+ .init = dsi_phy_init,
+ .get_lane_mbps = dsi_get_lane_mbps,
+ .power_on = dsi_phy_post_power_on,
+ .power_off = dsi_phy_post_power_off,
+ .get_timing = dsi_phy_get_timing,
+};
+
+static int stm32_dsi_probe(struct device *dev)
+{
+ struct stm32_dsi_priv *priv;
+ struct dw_mipi_dsi *dmd;
+ struct resource *res;
+ struct reset_control *rst;
+ struct clk *clk;
+ int ret;
+
+ priv = xzalloc(sizeof(*priv));
+
+ res = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(res))
+ return PTR_ERR(res);
+
+ priv->vdd_reg = regulator_get(dev, "phy-dsi");
+ if (IS_ERR(priv->vdd_reg))
+ return dev_errp_probe(dev, priv->vdd_reg,
+ "getting phy dsi supply\n");
+
+ ret = regulator_enable(priv->vdd_reg);
+ if (ret)
+ return ret;
+
+ clk = clk_get_enabled(dev, "pclk");
+ if (IS_ERR(clk)) {
+ ret = dev_errp_probe(dev, clk,
+ "getting peripheral clock get\n");
+ goto err_reg;
+ }
+
+ clk = clk_get(dev, "ref");
+ if (IS_ERR(clk)) {
+ ret = dev_errp_probe(dev, clk,
+ "getting pll reference clock get\n");
+ goto err_reg;
+ }
+
+ priv->pllref_clk = clk_get_rate(clk);
+
+ rst = reset_control_get(dev, NULL);
+ if (IS_ERR(rst)) {
+ dev_err(dev, "missing dsi hardware reset\n");
+ goto err_clk;
+ }
+
+ /* Reset */
+ reset_control_deassert(rst);
+
+ priv->dev = dev;
+ priv->pdata.base = IOMEM(res->start);
+ priv->pdata.max_data_lanes = 2;
+ priv->pdata.phy_ops = &dsi_stm_phy_ops;
+ priv->pdata.priv_data = priv;
+ dev_set_drvdata(dev, priv);
+
+ dmd = dw_mipi_dsi_bind(dev, &priv->pdata);
+ if (IS_ERR(dmd)) {
+ ret = dev_errp_probe(dev, dmd, "failed to bind\n");
+ goto err_clk;
+ }
+
+ /* check hardware version */
+ priv->hw_version = dsi_read(priv, DSI_VERSION) & VERSION;
+ if (priv->hw_version != HWVER_130 &&
+ priv->hw_version != HWVER_131) {
+ dev_err(dev, "DSI version 0x%x not supported\n", priv->hw_version);
+ ret = -ENODEV;
+ goto err_clk;
+ }
+
+ return 0;
+err_clk:
+ clk_disable(clk);
+err_reg:
+ regulator_disable(priv->vdd_reg);
+
+ return ret;
+}
+
+static const struct of_device_id stm32_dsi_dt_ids[] = {
+ { .compatible = "st,stm32-dsi" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, stm32_dsi_dt_ids);
+
+struct driver stm32_dsi_driver = {
+ .name = "stm32-display-dsi",
+ .probe = stm32_dsi_probe,
+ .of_match_table = stm32_dsi_dt_ids,
+};
+device_platform_driver(stm32_dsi_driver);
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 15/16] video: add support for Orise Technology otm8009a panel
2025-06-05 21:07 [PATCH 00/16] ARM: stm32mp: add MIPI DSI support Ahmad Fatoum
` (13 preceding siblings ...)
2025-06-05 21:07 ` [PATCH 14/16] video: add STM32 MIPI DSI video driver Ahmad Fatoum
@ 2025-06-05 21:07 ` Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 16/16] ARM: stm32mp: dk2: enable MIPI-DSI display by default Ahmad Fatoum
15 siblings, 0 replies; 17+ messages in thread
From: Ahmad Fatoum @ 2025-06-05 21:07 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
This is the panel used on the STM32MP157C-DK2. Unfortunately, I have not
been successful in reading the panel ID via MIPI-DSI, but video mode
works fine.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
drivers/video/Kconfig | 9 +
drivers/video/Makefile | 1 +
drivers/video/panel-orisetech-otm8009a.c | 505 +++++++++++++++++++++++
include/linux/gpio/consumer.h | 3 +
include/module.h | 26 ++
include/video/backlight.h | 14 +
include/video/drm/drm_connector.h | 2 +
7 files changed, 560 insertions(+)
create mode 100644 drivers/video/panel-orisetech-otm8009a.c
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index c4b244fa5a98..2cf6bf6906d5 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -203,6 +203,15 @@ config DRIVER_VIDEO_PANEL_SITRONIX_ST7789V
Say Y here if you want to enable support for the Sitronix
ST7789V controller for 240x320 LCD panels
+config DRIVER_VIDEO_PANEL_ORISETECH_OTM8009A
+ tristate "Orise Technology otm8009a 480x800 dsi 2dl panel"
+ depends on OF
+ depends on DRIVER_VIDEO_MIPI_DSI
+ depends on DRIVER_VIDEO_BACKLIGHT
+ help
+ Say Y here if you want to enable support for Orise Technology
+ otm8009a 480x800 dsi 2dl panel.
+
config DRIVER_VIDEO_PANEL_MIPI_DBI
tristate "MIPI DBI compatible panels"
depends on OFTREE && SPI
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index f6c309ead23b..470a5abaa450 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_DRIVER_VIDEO_MIPI_DSI) += mipi_dsi.o
obj-$(CONFIG_DRIVER_VIDEO_PANEL_ILITEK_ILI9341) += panel-ilitek-ili9341.o
obj-$(CONFIG_DRIVER_VIDEO_PANEL_MIPI_DBI) += panel-mipi-dbi.o
obj-$(CONFIG_DRIVER_VIDEO_PANEL_SITRONIX_ST7789V) += panel-sitronix-st7789v.o
+obj-$(CONFIG_DRIVER_VIDEO_PANEL_ORISETECH_OTM8009A) += panel-orisetech-otm8009a.o
obj-$(CONFIG_DRIVER_VIDEO_ATMEL) += atmel_lcdfb.o atmel_lcdfb_core.o
obj-$(CONFIG_DRIVER_VIDEO_ATMEL_HLCD) += atmel_hlcdfb.o atmel_lcdfb_core.o
diff --git a/drivers/video/panel-orisetech-otm8009a.c b/drivers/video/panel-orisetech-otm8009a.c
new file mode 100644
index 000000000000..e69021c2965f
--- /dev/null
+++ b/drivers/video/panel-orisetech-otm8009a.c
@@ -0,0 +1,505 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics SA 2017
+ *
+ * Authors: Philippe Cornu <philippe.cornu@st.com>
+ * Yannick Fertre <yannick.fertre@st.com>
+ */
+
+#include <video/backlight.h>
+#include <video/vpl.h>
+#include <clock.h>
+#include <linux/gpio/consumer.h>
+#include <video/media-bus-format.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <regulator.h>
+
+#include <video/mipi_display.h>
+
+#include <video/mipi_dsi.h>
+#include <video/mipi_display.h>
+#include <video/drm/drm_modes.h>
+#include <video/videomode.h>
+
+#define OTM8009A_BACKLIGHT_DEFAULT 240
+#define OTM8009A_BACKLIGHT_MAX 255
+
+/* Manufacturer Command Set */
+#define MCS_ADRSFT 0x0000 /* Address Shift Function */
+#define MCS_PANSET 0xB3A6 /* Panel Type Setting */
+#define MCS_SD_CTRL 0xC0A2 /* Source Driver Timing Setting */
+#define MCS_P_DRV_M 0xC0B4 /* Panel Driving Mode */
+#define MCS_OSC_ADJ 0xC181 /* Oscillator Adjustment for Idle/Normal mode */
+#define MCS_RGB_VID_SET 0xC1A1 /* RGB Video Mode Setting */
+#define MCS_SD_PCH_CTRL 0xC480 /* Source Driver Precharge Control */
+#define MCS_NO_DOC1 0xC48A /* Command not documented */
+#define MCS_PWR_CTRL1 0xC580 /* Power Control Setting 1 */
+#define MCS_PWR_CTRL2 0xC590 /* Power Control Setting 2 for Normal Mode */
+#define MCS_PWR_CTRL4 0xC5B0 /* Power Control Setting 4 for DC Voltage */
+#define MCS_PANCTRLSET1 0xCB80 /* Panel Control Setting 1 */
+#define MCS_PANCTRLSET2 0xCB90 /* Panel Control Setting 2 */
+#define MCS_PANCTRLSET3 0xCBA0 /* Panel Control Setting 3 */
+#define MCS_PANCTRLSET4 0xCBB0 /* Panel Control Setting 4 */
+#define MCS_PANCTRLSET5 0xCBC0 /* Panel Control Setting 5 */
+#define MCS_PANCTRLSET6 0xCBD0 /* Panel Control Setting 6 */
+#define MCS_PANCTRLSET7 0xCBE0 /* Panel Control Setting 7 */
+#define MCS_PANCTRLSET8 0xCBF0 /* Panel Control Setting 8 */
+#define MCS_PANU2D1 0xCC80 /* Panel U2D Setting 1 */
+#define MCS_PANU2D2 0xCC90 /* Panel U2D Setting 2 */
+#define MCS_PANU2D3 0xCCA0 /* Panel U2D Setting 3 */
+#define MCS_PAND2U1 0xCCB0 /* Panel D2U Setting 1 */
+#define MCS_PAND2U2 0xCCC0 /* Panel D2U Setting 2 */
+#define MCS_PAND2U3 0xCCD0 /* Panel D2U Setting 3 */
+#define MCS_GOAVST 0xCE80 /* GOA VST Setting */
+#define MCS_GOACLKA1 0xCEA0 /* GOA CLKA1 Setting */
+#define MCS_GOACLKA3 0xCEB0 /* GOA CLKA3 Setting */
+#define MCS_GOAECLK 0xCFC0 /* GOA ECLK Setting */
+#define MCS_NO_DOC2 0xCFD0 /* Command not documented */
+#define MCS_GVDDSET 0xD800 /* GVDD/NGVDD */
+#define MCS_VCOMDC 0xD900 /* VCOM Voltage Setting */
+#define MCS_GMCT2_2P 0xE100 /* Gamma Correction 2.2+ Setting */
+#define MCS_GMCT2_2N 0xE200 /* Gamma Correction 2.2- Setting */
+#define MCS_NO_DOC3 0xF5B6 /* Command not documented */
+#define MCS_CMD2_ENA1 0xFF00 /* Enable Access Command2 "CMD2" */
+#define MCS_CMD2_ENA2 0xFF80 /* Enable Access Orise Command2 */
+
+#define OTM8009A_HDISPLAY 480
+#define OTM8009A_VDISPLAY 800
+
+struct otm8009a {
+ struct device *dev;
+ struct vpl vpl;
+ struct backlight_device bl_dev;
+ struct gpio_desc *reset_gpio;
+ struct regulator *supply;
+ bool prepared;
+};
+
+static const struct drm_display_mode modes[] = {
+ { /* 50 Hz, preferred */
+ .clock = 29700,
+ .hdisplay = 480,
+ .hsync_start = 480 + 98,
+ .hsync_end = 480 + 98 + 32,
+ .htotal = 480 + 98 + 32 + 98,
+ .vdisplay = 800,
+ .vsync_start = 800 + 15,
+ .vsync_end = 800 + 15 + 10,
+ .vtotal = 800 + 15 + 10 + 14,
+ .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+ .width_mm = 52,
+ .height_mm = 86,
+ },
+ { /* 60 Hz */
+ .clock = 33000,
+ .hdisplay = 480,
+ .hsync_start = 480 + 70,
+ .hsync_end = 480 + 70 + 32,
+ .htotal = 480 + 70 + 32 + 72,
+ .vdisplay = 800,
+ .vsync_start = 800 + 15,
+ .vsync_end = 800 + 15 + 10,
+ .vtotal = 800 + 15 + 10 + 16,
+ .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+ .width_mm = 52,
+ .height_mm = 86,
+ },
+};
+
+static inline struct otm8009a *panel_to_otm8009a(struct vpl *vpl)
+{
+ return container_of(vpl, struct otm8009a, vpl);
+}
+
+static void otm8009a_dcs_write_buf(struct otm8009a *ctx, const void *data,
+ size_t len)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+
+ if (mipi_dsi_dcs_write_buffer(dsi, data, len) < 0)
+ dev_warn(ctx->dev, "mipi dsi dcs %swrite buffer [0x%02x..] failed\n",
+ dsi->mode_flags & MIPI_DSI_MODE_LPM ? "" : "hs ",
+ *(u8 *)data);
+}
+
+#define dcs_write_seq(ctx, seq...) \
+({ \
+ static const u8 d[] = { seq }; \
+ otm8009a_dcs_write_buf(ctx, d, ARRAY_SIZE(d)); \
+})
+
+#define dcs_write_cmd_at(ctx, cmd, seq...) \
+({ \
+ dcs_write_seq(ctx, MCS_ADRSFT, (cmd) & 0xFF); \
+ dcs_write_seq(ctx, (cmd) >> 8, seq); \
+})
+
+static int otm8009a_init_sequence(struct otm8009a *ctx)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
+
+ /* Enter CMD2 */
+ dcs_write_cmd_at(ctx, MCS_CMD2_ENA1, 0x80, 0x09, 0x01);
+
+ /* Enter Orise Command2 */
+ dcs_write_cmd_at(ctx, MCS_CMD2_ENA2, 0x80, 0x09);
+
+ dcs_write_cmd_at(ctx, MCS_SD_PCH_CTRL, 0x30);
+ mdelay(10);
+
+ dcs_write_cmd_at(ctx, MCS_NO_DOC1, 0x40);
+ mdelay(10);
+
+ dcs_write_cmd_at(ctx, MCS_PWR_CTRL4 + 1, 0xA9);
+ dcs_write_cmd_at(ctx, MCS_PWR_CTRL2 + 1, 0x34);
+ dcs_write_cmd_at(ctx, MCS_P_DRV_M, 0x50);
+ dcs_write_cmd_at(ctx, MCS_VCOMDC, 0x4E);
+ dcs_write_cmd_at(ctx, MCS_OSC_ADJ, 0x66); /* 65Hz */
+ dcs_write_cmd_at(ctx, MCS_PWR_CTRL2 + 2, 0x01);
+ dcs_write_cmd_at(ctx, MCS_PWR_CTRL2 + 5, 0x34);
+ dcs_write_cmd_at(ctx, MCS_PWR_CTRL2 + 4, 0x33);
+ dcs_write_cmd_at(ctx, MCS_GVDDSET, 0x79, 0x79);
+ dcs_write_cmd_at(ctx, MCS_SD_CTRL + 1, 0x1B);
+ dcs_write_cmd_at(ctx, MCS_PWR_CTRL1 + 2, 0x83);
+ dcs_write_cmd_at(ctx, MCS_SD_PCH_CTRL + 1, 0x83);
+ dcs_write_cmd_at(ctx, MCS_RGB_VID_SET, 0x0E);
+ dcs_write_cmd_at(ctx, MCS_PANSET, 0x00, 0x01);
+
+ dcs_write_cmd_at(ctx, MCS_GOAVST, 0x85, 0x01, 0x00, 0x84, 0x01, 0x00);
+ dcs_write_cmd_at(ctx, MCS_GOACLKA1, 0x18, 0x04, 0x03, 0x39, 0x00, 0x00,
+ 0x00, 0x18, 0x03, 0x03, 0x3A, 0x00, 0x00, 0x00);
+ dcs_write_cmd_at(ctx, MCS_GOACLKA3, 0x18, 0x02, 0x03, 0x3B, 0x00, 0x00,
+ 0x00, 0x18, 0x01, 0x03, 0x3C, 0x00, 0x00, 0x00);
+ dcs_write_cmd_at(ctx, MCS_GOAECLK, 0x01, 0x01, 0x20, 0x20, 0x00, 0x00,
+ 0x01, 0x02, 0x00, 0x00);
+
+ dcs_write_cmd_at(ctx, MCS_NO_DOC2, 0x00);
+
+ dcs_write_cmd_at(ctx, MCS_PANCTRLSET1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ dcs_write_cmd_at(ctx, MCS_PANCTRLSET2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0);
+ dcs_write_cmd_at(ctx, MCS_PANCTRLSET3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0);
+ dcs_write_cmd_at(ctx, MCS_PANCTRLSET4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ dcs_write_cmd_at(ctx, MCS_PANCTRLSET5, 0, 4, 4, 4, 4, 4, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0);
+ dcs_write_cmd_at(ctx, MCS_PANCTRLSET6, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4,
+ 4, 0, 0, 0, 0);
+ dcs_write_cmd_at(ctx, MCS_PANCTRLSET7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ dcs_write_cmd_at(ctx, MCS_PANCTRLSET8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
+
+ dcs_write_cmd_at(ctx, MCS_PANU2D1, 0x00, 0x26, 0x09, 0x0B, 0x01, 0x25,
+ 0x00, 0x00, 0x00, 0x00);
+ dcs_write_cmd_at(ctx, MCS_PANU2D2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x0A, 0x0C, 0x02);
+ dcs_write_cmd_at(ctx, MCS_PANU2D3, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+ dcs_write_cmd_at(ctx, MCS_PAND2U1, 0x00, 0x25, 0x0C, 0x0A, 0x02, 0x26,
+ 0x00, 0x00, 0x00, 0x00);
+ dcs_write_cmd_at(ctx, MCS_PAND2U2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x0B, 0x09, 0x01);
+ dcs_write_cmd_at(ctx, MCS_PAND2U3, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+
+ dcs_write_cmd_at(ctx, MCS_PWR_CTRL1 + 1, 0x66);
+
+ dcs_write_cmd_at(ctx, MCS_NO_DOC3, 0x06);
+
+ dcs_write_cmd_at(ctx, MCS_GMCT2_2P, 0x00, 0x09, 0x0F, 0x0E, 0x07, 0x10,
+ 0x0B, 0x0A, 0x04, 0x07, 0x0B, 0x08, 0x0F, 0x10, 0x0A,
+ 0x01);
+ dcs_write_cmd_at(ctx, MCS_GMCT2_2N, 0x00, 0x09, 0x0F, 0x0E, 0x07, 0x10,
+ 0x0B, 0x0A, 0x04, 0x07, 0x0B, 0x08, 0x0F, 0x10, 0x0A,
+ 0x01);
+
+ /* Exit CMD2 */
+ dcs_write_cmd_at(ctx, MCS_CMD2_ENA1, 0xFF, 0xFF, 0xFF);
+
+ ret = mipi_dsi_dcs_nop(dsi);
+ if (ret)
+ return ret;
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret)
+ return ret;
+
+ /* Wait for sleep out exit */
+ mdelay(120);
+
+ /* Default portrait 480x800 rgb24 */
+ dcs_write_seq(ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x00);
+
+ ret = mipi_dsi_dcs_set_column_address(dsi, 0, OTM8009A_HDISPLAY - 1);
+ if (ret)
+ return ret;
+
+ ret = mipi_dsi_dcs_set_page_address(dsi, 0, OTM8009A_VDISPLAY - 1);
+ if (ret)
+ return ret;
+
+ /* See otm8009a driver documentation for pixel format descriptions */
+ ret = mipi_dsi_dcs_set_pixel_format(dsi, MIPI_DCS_PIXEL_FMT_24BIT |
+ MIPI_DCS_PIXEL_FMT_24BIT << 4);
+ if (ret)
+ return ret;
+
+ /* Disable CABC feature */
+ dcs_write_seq(ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x00);
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret)
+ return ret;
+
+ ret = mipi_dsi_dcs_nop(dsi);
+ if (ret)
+ return ret;
+
+ /* Send Command GRAM memory write (no parameters) */
+ dcs_write_seq(ctx, MIPI_DCS_WRITE_MEMORY_START);
+
+ /* Wait a short while to let the panel be ready before the 1st frame */
+ mdelay(10);
+
+ return 0;
+}
+
+static int otm8009a_disable(struct otm8009a *ctx)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
+
+ backlight_disable(&ctx->bl_dev);
+
+ ret = mipi_dsi_dcs_set_display_off(dsi);
+ if (ret)
+ return ret;
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret)
+ return ret;
+
+ mdelay(120);
+
+ return 0;
+}
+
+static int otm8009a_unprepare(struct otm8009a *ctx)
+{
+ if (ctx->reset_gpio) {
+ gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+ mdelay(20);
+ }
+
+ regulator_disable(ctx->supply);
+
+ ctx->prepared = false;
+
+ return 0;
+}
+
+static int otm8009a_prepare(struct otm8009a *ctx)
+{
+ int ret;
+
+ ret = regulator_enable(ctx->supply);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to enable supply: %pe\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ if (ctx->reset_gpio) {
+ gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+ gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+ mdelay(1); /* >50us */
+ gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+ mdelay(10); /* >5ms */
+ }
+
+ ret = otm8009a_init_sequence(ctx);
+ if (ret)
+ return ret;
+
+ ctx->prepared = true;
+
+ return 0;
+}
+
+static int otm8009a_enable(struct otm8009a *ctx)
+{
+ backlight_enable(&ctx->bl_dev);
+
+ return 0;
+}
+
+static int otm8009a_get_modes(struct otm8009a *ctx,
+ struct display_timings *timings)
+{
+ struct fb_videomode *video_modes;
+ unsigned int num_modes = ARRAY_SIZE(modes);
+ unsigned int i;
+
+ video_modes = calloc(num_modes, sizeof(*video_modes));
+ if (!video_modes)
+ return -ENOMEM;
+
+ for (i = 0; i < num_modes; i++)
+ drm_display_mode_to_fb_videomode(&modes[i], &video_modes[i]);
+
+ timings->modes = video_modes;
+ timings->num_modes = num_modes;
+
+ return 0;
+}
+
+static int otm8009a_ioctl(struct vpl *vpl, unsigned int port,
+ unsigned int cmd, void *ptr)
+{
+ struct otm8009a *ctx = panel_to_otm8009a(vpl);
+
+ switch (cmd) {
+ case VPL_PREPARE:
+ return otm8009a_prepare(ctx);
+ case VPL_ENABLE:
+ return otm8009a_enable(ctx);
+ case VPL_DISABLE:
+ return otm8009a_disable(ctx);
+ case VPL_UNPREPARE:
+ return otm8009a_unprepare(ctx);
+ case VPL_GET_VIDEOMODES:
+ return otm8009a_get_modes(ctx, ptr);
+ case VPL_GET_BUS_FORMAT:
+ *(u32 *)ptr = MEDIA_BUS_FMT_RGB888_1X24;
+ return 0;
+ default:
+ return 0;
+ }
+}
+
+/*
+ * DSI-BASED BACKLIGHT
+ */
+
+static int otm8009a_backlight_update_status(struct backlight_device *bd,
+ int brightness)
+{
+ struct otm8009a *ctx = container_of(bd, struct otm8009a, bl_dev);
+ u8 data[2];
+
+ if (!ctx->prepared) {
+ dev_dbg(&bd->dev, "lcd not ready yet for setting its backlight!\n");
+ return -ENXIO;
+ }
+
+ if (brightness > 0) {
+ /* Power on the backlight with the requested brightness
+ * Note We can not use mipi_dsi_dcs_set_display_brightness()
+ * as otm8009a driver support only 8-bit brightness (1 param).
+ */
+ data[0] = MIPI_DCS_SET_DISPLAY_BRIGHTNESS;
+ data[1] = brightness;
+ otm8009a_dcs_write_buf(ctx, data, ARRAY_SIZE(data));
+
+ /* set Brightness Control & Backlight on */
+ data[1] = 0x24;
+
+ } else {
+ /* Power off the backlight: set Brightness Control & Bl off */
+ data[1] = 0;
+ }
+
+ /* Update Brightness Control & Backlight */
+ data[0] = MIPI_DCS_WRITE_CONTROL_DISPLAY;
+ otm8009a_dcs_write_buf(ctx, data, ARRAY_SIZE(data));
+
+ /* Need to wait a few time before sending the first image */
+ mdelay(10);
+
+ return 0;
+}
+
+static int otm8009a_probe(struct mipi_dsi_device *dsi)
+{
+ struct device *dev = &dsi->dev;
+ struct backlight_device *bl_dev;
+ struct otm8009a *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->reset_gpio = gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->reset_gpio)) {
+ dev_err(dev, "cannot get reset-gpio\n");
+ return PTR_ERR(ctx->reset_gpio);
+ }
+
+ ctx->supply = regulator_get(dev, "power");
+ if (IS_ERR(ctx->supply)) {
+ ret = PTR_ERR(ctx->supply);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to request regulator: %pe\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ mipi_dsi_set_drvdata(dsi, ctx);
+
+ ctx->dev = dev;
+
+ dsi->lanes = 2;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
+ MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS;
+
+ ctx->vpl.node = dev->of_node;
+ ctx->vpl.ioctl = otm8009a_ioctl;
+
+ ret = vpl_register(&ctx->vpl);
+ if (ret)
+ return ret;
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0) {
+ dev_err(dev, "mipi_dsi_attach failed. Is host ready?\n");
+ return ret;
+ }
+
+ bl_dev = &ctx->bl_dev;
+
+ bl_dev->brightness_max = OTM8009A_BACKLIGHT_MAX;
+ bl_dev->brightness_default = OTM8009A_BACKLIGHT_DEFAULT;
+ bl_dev->brightness_set = otm8009a_backlight_update_status;
+ bl_dev->dev.parent = dev;
+ bl_dev->node = dev->of_node;
+
+ ret = backlight_register(bl_dev);
+ if (ret) {
+ dev_err(dev, "failed to register backlight: %pe\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id orisetech_otm8009a_of_match[] = {
+ { .compatible = "orisetech,otm8009a" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, orisetech_otm8009a_of_match);
+
+static struct mipi_dsi_driver orisetech_otm8009a_driver = {
+ .probe = otm8009a_probe,
+ .driver = {
+ .name = "panel-orisetech-otm8009a",
+ .of_match_table = orisetech_otm8009a_of_match,
+ },
+};
+module_mipi_dsi_driver(orisetech_otm8009a_driver);
+
+MODULE_AUTHOR("Philippe Cornu <philippe.cornu@st.com>");
+MODULE_AUTHOR("Yannick Fertre <yannick.fertre@st.com>");
+MODULE_DESCRIPTION("DRM driver for Orise Tech OTM8009A MIPI DSI panel");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h
index 288fd7a030e9..c461f3f3108b 100644
--- a/include/linux/gpio/consumer.h
+++ b/include/linux/gpio/consumer.h
@@ -234,6 +234,9 @@ struct gpio_desc *gpiod_request_one(unsigned gpio,
#endif
+#define gpiod_get_value_cansleep gpiod_get_value
+#define gpiod_set_value_cansleep gpiod_set_value
+
static inline struct gpio_desc *dev_gpiod_get(struct device *dev,
struct device_node *np,
const char *con_id,
diff --git a/include/module.h b/include/module.h
index 60ad6c3556d1..30889a6ddf45 100644
--- a/include/module.h
+++ b/include/module.h
@@ -81,6 +81,32 @@ extern void cleanup_module(void);
#endif
+/**
+ * module_driver() - Helper macro for drivers that don't do anything
+ * special in module init/exit. This eliminates a lot of boilerplate.
+ * Each module may only use this macro once, and calling it replaces
+ * module_init() and module_exit().
+ *
+ * @__driver: driver name
+ * @__register: register function for this driver type
+ * @__unregister: unregister function for this driver type
+ * @...: Additional arguments to be passed to __register and __unregister.
+ *
+ * Use this macro to construct bus specific macros for registering
+ * drivers, and do not use it on its own.
+ */
+#define module_driver(__driver, __register, __unregister, ...) \
+static int __init __driver##_init(void) \
+{ \
+ return __register(&(__driver) , ##__VA_ARGS__); \
+} \
+module_init(__driver##_init); \
+static void __exit __driver##_exit(void) \
+{ \
+ __unregister(&(__driver) , ##__VA_ARGS__); \
+} \
+module_exit(__driver##_exit);
+
#ifdef CONFIG_MODULES
#include <asm/module.h>
diff --git a/include/video/backlight.h b/include/video/backlight.h
index dcb8e8c5e10e..13e852152ea9 100644
--- a/include/video/backlight.h
+++ b/include/video/backlight.h
@@ -3,6 +3,8 @@
#ifndef __VIDEO_BACKLIGHT_H
#define __VIDEO_BACKLIGHT_H
+#include <device.h>
+
#ifdef CONFIG_DRIVER_VIDEO_BACKLIGHT
struct backlight_device {
int brightness;
@@ -36,4 +38,16 @@ static inline struct backlight_device *
of_backlight_find(struct device_node *node) { return NULL; }
#endif
+static inline int
+backlight_enable(struct backlight_device *dev)
+{
+ return dev ? backlight_set_brightness_default(dev) : 0;
+}
+
+static inline int
+backlight_disable(struct backlight_device *dev)
+{
+ return dev ? backlight_set_brightness(dev, 0) : 0;
+}
+
#endif /* __VIDEO_BACKLIGHT_H */
diff --git a/include/video/drm/drm_connector.h b/include/video/drm/drm_connector.h
index 53ec7d3b79cb..40edc9e627a6 100644
--- a/include/video/drm/drm_connector.h
+++ b/include/video/drm/drm_connector.h
@@ -24,6 +24,8 @@
#define __DRM_CONNECTOR_H__
#include <uapi/drm/drm_mode.h>
+#include <linux/types.h>
+#include <linux/bitops.h>
enum drm_connector_force {
DRM_FORCE_UNSPECIFIED,
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 16/16] ARM: stm32mp: dk2: enable MIPI-DSI display by default
2025-06-05 21:07 [PATCH 00/16] ARM: stm32mp: add MIPI DSI support Ahmad Fatoum
` (14 preceding siblings ...)
2025-06-05 21:07 ` [PATCH 15/16] video: add support for Orise Technology otm8009a panel Ahmad Fatoum
@ 2025-06-05 21:07 ` Ahmad Fatoum
15 siblings, 0 replies; 17+ messages in thread
From: Ahmad Fatoum @ 2025-06-05 21:07 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
Now that all drivers are in place, let's enable them in the defconfig
and have barebox show a barebox prompt by default when run on the DK2.
If logo support is enabled, the logo is shown instead, but this is not
enabled here as not to require an imagemagick dependency for the
defconfig.
One way around that would be to have S_shipped logos checked into the
source tree, but that's a task for another day.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
arch/arm/boards/stm32mp15xx-dkx/Makefile | 1 +
arch/arm/boards/stm32mp15xx-dkx/board.c | 3 +++
.../defaultenv-stm32mp15xx-dkx/init/splash | 18 ++++++++++++++++++
arch/arm/configs/stm32mp_defconfig | 18 ++++++++++++------
4 files changed, 34 insertions(+), 6 deletions(-)
create mode 100755 arch/arm/boards/stm32mp15xx-dkx/defaultenv-stm32mp15xx-dkx/init/splash
diff --git a/arch/arm/boards/stm32mp15xx-dkx/Makefile b/arch/arm/boards/stm32mp15xx-dkx/Makefile
index 5678718188b9..88b1faf193bc 100644
--- a/arch/arm/boards/stm32mp15xx-dkx/Makefile
+++ b/arch/arm/boards/stm32mp15xx-dkx/Makefile
@@ -2,3 +2,4 @@
lwl-y += lowlevel.o
obj-y += board.o
+bbenv-y += defaultenv-stm32mp15xx-dkx
diff --git a/arch/arm/boards/stm32mp15xx-dkx/board.c b/arch/arm/boards/stm32mp15xx-dkx/board.c
index d693bf2aaf68..3ef5a6f38d46 100644
--- a/arch/arm/boards/stm32mp15xx-dkx/board.c
+++ b/arch/arm/boards/stm32mp15xx-dkx/board.c
@@ -3,6 +3,7 @@
#include <init.h>
#include <mach/stm32mp/bbu.h>
#include <deep-probe.h>
+#include <envfs.h>
static int dkx_probe(struct device *dev)
{
@@ -17,6 +18,8 @@ static int dkx_probe(struct device *dev)
barebox_set_hostname("stm32mp15xx-dkx");
+ defaultenv_append_directory(defaultenv_stm32mp15xx_dkx);
+
return 0;
}
diff --git a/arch/arm/boards/stm32mp15xx-dkx/defaultenv-stm32mp15xx-dkx/init/splash b/arch/arm/boards/stm32mp15xx-dkx/defaultenv-stm32mp15xx-dkx/init/splash
new file mode 100755
index 000000000000..922431eb916e
--- /dev/null
+++ b/arch/arm/boards/stm32mp15xx-dkx/defaultenv-stm32mp15xx-dkx/init/splash
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+if [ -e /dev/fb0 ]; then
+ fb0.enable=1
+fi
+
+if [ -d /logo ]; then
+ for logo in /logo/*; do
+ splash $logo
+ exit
+ done
+fi
+
+# No logo, so let's show console instead
+if [ -e /dev/fbconsole0-1 ]; then
+ fbconsole0.font=7x14
+ fbconsole0.active=ioe
+fi
diff --git a/arch/arm/configs/stm32mp_defconfig b/arch/arm/configs/stm32mp_defconfig
index 366c9430d551..20955b3f6b80 100644
--- a/arch/arm/configs/stm32mp_defconfig
+++ b/arch/arm/configs/stm32mp_defconfig
@@ -9,16 +9,13 @@ CONFIG_MACH_STM32MP15X_EV1=y
CONFIG_MACH_PROTONIC_STM32MP1=y
CONFIG_MACH_PROTONIC_STM32MP13=y
CONFIG_MACH_PHYTEC_PHYCORE_STM32MP1=y
-CONFIG_BOARD_GENERIC_DT=y
CONFIG_THUMB2_BAREBOX=y
CONFIG_ARM_BOARD_APPEND_ATAG=y
CONFIG_ARM_OPTIMZED_STRING_FUNCTIONS=y
CONFIG_ARM_UNWIND=y
CONFIG_MMU=y
CONFIG_MALLOC_SIZE=0x0
-CONFIG_MALLOC_TLSF=y
CONFIG_KALLSYMS=y
-CONFIG_RELOCATABLE=y
CONFIG_HUSH_FANCY_PROMPT=y
CONFIG_AUTO_COMPLETE=y
CONFIG_MENU=y
@@ -77,6 +74,8 @@ CONFIG_CMD_EDIT=y
CONFIG_CMD_MENU=y
CONFIG_CMD_MENU_MANAGEMENT=y
CONFIG_CMD_MENUTREE=y
+CONFIG_CMD_SPLASH=y
+CONFIG_CMD_FBTEST=y
CONFIG_CMD_READLINE=y
CONFIG_CMD_TIMEOUT=y
CONFIG_CMD_CRC=y
@@ -114,7 +113,6 @@ CONFIG_I2C=y
CONFIG_I2C_STM32=y
CONFIG_MTD=y
CONFIG_MTD_M25P80=y
-CONFIG_MTD_SST25L=y
CONFIG_USB_HOST=y
CONFIG_USB_DWC2_HOST=y
CONFIG_USB_DWC2_GADGET=y
@@ -126,10 +124,13 @@ CONFIG_USB_GADGET_DFU=y
CONFIG_USB_GADGET_SERIAL=y
CONFIG_USB_GADGET_FASTBOOT=y
CONFIG_VIDEO=y
+CONFIG_FRAMEBUFFER_CONSOLE=y
CONFIG_DRIVER_VIDEO_FB_SSD1307=y
CONFIG_DRIVER_VIDEO_STM32_LTDC=y
+CONFIG_DRIVER_VIDEO_STM32_DSI=y
CONFIG_DRIVER_VIDEO_BACKLIGHT=y
CONFIG_DRIVER_VIDEO_SIMPLE_PANEL=y
+CONFIG_DRIVER_VIDEO_PANEL_ORISETECH_OTM8009A=y
CONFIG_MCI=y
CONFIG_MCI_STARTUP=y
CONFIG_MCI_MMC_BOOT_PARTITIONS=y
@@ -173,11 +174,16 @@ CONFIG_FS_TFTP=y
CONFIG_FS_NFS=y
CONFIG_FS_FAT=y
CONFIG_FS_FAT_WRITE=y
-CONFIG_FS_FAT_LFN=y
CONFIG_FS_PSTORE=y
CONFIG_FS_PSTORE_CONSOLE=y
CONFIG_FS_PSTORE_RAMOOPS=y
CONFIG_FS_SQUASHFS=y
CONFIG_FS_RATP=y
-CONFIG_ZLIB=y
CONFIG_CRC8=y
+CONFIG_BMP=y
+CONFIG_PNG=y
+CONFIG_QOI=y
+CONFIG_FONT_8x8=y
+CONFIG_FONT_7x14=y
+CONFIG_FONT_MINI_4x6=y
+CONFIG_FONT_TER16x32=y
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2025-06-05 21:15 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-06-05 21:07 [PATCH 00/16] ARM: stm32mp: add MIPI DSI support Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 01/16] driver: bus: embed bus driver node into bus Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 02/16] driver: switch busses to device class Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 03/16] driver: factor out bus definitions into separate header Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 04/16] driver: bus: add helpers for finding devices in busses Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 05/16] drive: bus: make use of new bus_find_device helper Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 06/16] of: implement of_alias_from_compatible Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 07/16] video: vpl: fix potential read of uninitialized variable Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 08/16] video: vpl: factor out vpl_for_each Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 09/16] video: vpl: handle missing struct vpl::ioctl gracefully Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 10/16] video: vpl: add vpl_bridge abstraction Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 11/16] video: factor out drm_mode_vrefresh Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 12/16] video: add base MIPI DSI support Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 13/16] video: add Designware MIPI-DSI support Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 14/16] video: add STM32 MIPI DSI video driver Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 15/16] video: add support for Orise Technology otm8009a panel Ahmad Fatoum
2025-06-05 21:07 ` [PATCH 16/16] ARM: stm32mp: dk2: enable MIPI-DSI display by default Ahmad Fatoum
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox