diff options
-rw-r--r-- | drivers/gpu/drm/Kconfig | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/drm_mipi_dsi.c | 315 | ||||
-rw-r--r-- | include/drm/drm_mipi_dsi.h | 158 |
4 files changed, 478 insertions, 0 deletions
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index f864275..0b8f8fd 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -20,6 +20,10 @@ menuconfig DRM details. You should also select and configure AGP (/dev/agpgart) support if it is available for your platform. +config DRM_MIPI_DSI + bool + depends on DRM + config DRM_USB tristate depends on DRM diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index cc08b84..b46239a 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o CFLAGS_drm_trace_points.o := -I$(src) obj-$(CONFIG_DRM) += drm.o +obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o obj-$(CONFIG_DRM_USB) += drm_usb.o obj-$(CONFIG_DRM_TTM) += ttm/ obj-$(CONFIG_DRM_TDFX) += tdfx/ diff --git a/drivers/gpu/drm/drm_mipi_dsi.c b/drivers/gpu/drm/drm_mipi_dsi.c new file mode 100644 index 0000000..b155ee2 --- /dev/null +++ b/drivers/gpu/drm/drm_mipi_dsi.c @@ -0,0 +1,315 @@ +/* + * MIPI DSI Bus + * + * Copyright (C) 2012-2013, Samsung Electronics, Co., Ltd. + * 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. + */ + +#include <drm/drm_mipi_dsi.h> + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> + +#include <video/mipi_display.h> + +static int mipi_dsi_device_match(struct device *dev, struct device_driver *drv) +{ + return of_driver_match_device(dev, drv); +} + +static const struct dev_pm_ops mipi_dsi_device_pm_ops = { + .runtime_suspend = pm_generic_runtime_suspend, + .runtime_resume = pm_generic_runtime_resume, + .suspend = pm_generic_suspend, + .resume = pm_generic_resume, + .freeze = pm_generic_freeze, + .thaw = pm_generic_thaw, + .poweroff = pm_generic_poweroff, + .restore = pm_generic_restore, +}; + +static struct bus_type mipi_dsi_bus_type = { + .name = "mipi-dsi", + .match = mipi_dsi_device_match, + .pm = &mipi_dsi_device_pm_ops, +}; + +static void mipi_dsi_dev_release(struct device *dev) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(dev); + + of_node_put(dev->of_node); + kfree(dsi); +} + +static const struct device_type mipi_dsi_device_type = { + .release = mipi_dsi_dev_release, +}; + +static struct mipi_dsi_device *mipi_dsi_device_alloc(struct mipi_dsi_host *host) +{ + struct mipi_dsi_device *dsi; + + dsi = kzalloc(sizeof(*dsi), GFP_KERNEL); + if (!dsi) + return ERR_PTR(-ENOMEM); + + dsi->host = host; + dsi->dev.bus = &mipi_dsi_bus_type; + dsi->dev.parent = host->dev; + dsi->dev.type = &mipi_dsi_device_type; + + device_initialize(&dsi->dev); + + return dsi; +} + +static int mipi_dsi_device_add(struct mipi_dsi_device *dsi) +{ + struct mipi_dsi_host *host = dsi->host; + + dev_set_name(&dsi->dev, "%s.%d", dev_name(host->dev), dsi->channel); + + return device_add(&dsi->dev); +} + +static struct mipi_dsi_device * +of_mipi_dsi_device_add(struct mipi_dsi_host *host, struct device_node *node) +{ + struct mipi_dsi_device *dsi; + struct device *dev = host->dev; + int ret; + u32 reg; + + ret = of_property_read_u32(node, "reg", ®); + if (ret) { + dev_err(dev, "device node %s has no valid reg property: %d\n", + node->full_name, ret); + return ERR_PTR(-EINVAL); + } + + if (reg > 3) { + dev_err(dev, "device node %s has invalid reg property: %u\n", + node->full_name, reg); + return ERR_PTR(-EINVAL); + } + + dsi = mipi_dsi_device_alloc(host); + if (IS_ERR(dsi)) { + dev_err(dev, "failed to allocate DSI device %s: %ld\n", + node->full_name, PTR_ERR(dsi)); + return dsi; + } + + dsi->dev.of_node = of_node_get(node); + dsi->channel = reg; + + ret = mipi_dsi_device_add(dsi); + if (ret) { + dev_err(dev, "failed to add DSI device %s: %d\n", + node->full_name, ret); + kfree(dsi); + return ERR_PTR(ret); + } + + return dsi; +} + +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) + of_mipi_dsi_device_add(host, node); + + return 0; +} +EXPORT_SYMBOL(mipi_dsi_host_register); + +static int mipi_dsi_remove_device_fn(struct device *dev, void *priv) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(dev); + + device_unregister(&dsi->dev); + + return 0; +} + +void mipi_dsi_host_unregister(struct mipi_dsi_host *host) +{ + device_for_each_child(host->dev, NULL, mipi_dsi_remove_device_fn); +} +EXPORT_SYMBOL(mipi_dsi_host_unregister); + +/** + * 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; + + if (!ops || !ops->attach) + return -ENOSYS; + + return ops->attach(dsi->host, dsi); +} +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 (!ops || !ops->detach) + return -ENOSYS; + + return ops->detach(dsi->host, dsi); +} +EXPORT_SYMBOL(mipi_dsi_detach); + +/** + * mipi_dsi_dcs_write - send DCS write command + * @dsi: DSI device + * @channel: virtual channel + * @data: pointer to the command followed by parameters + * @len: length of @data + */ +int mipi_dsi_dcs_write(struct mipi_dsi_device *dsi, unsigned int channel, + const void *data, size_t len) +{ + const struct mipi_dsi_host_ops *ops = dsi->host->ops; + struct mipi_dsi_msg msg = { + .channel = channel, + .tx_buf = data, + .tx_len = len + }; + + if (!ops || !ops->transfer) + return -ENOSYS; + + 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 ops->transfer(dsi->host, &msg); +} +EXPORT_SYMBOL(mipi_dsi_dcs_write); + +/** + * mipi_dsi_dcs_read - send DCS read request command + * @dsi: DSI device + * @channel: virtual channel + * @cmd: DCS read command + * @data: pointer to read buffer + * @len: length of @data + * + * Function returns number of read bytes or error code. + */ +ssize_t mipi_dsi_dcs_read(struct mipi_dsi_device *dsi, unsigned int channel, + u8 cmd, void *data, size_t len) +{ + const struct mipi_dsi_host_ops *ops = dsi->host->ops; + struct mipi_dsi_msg msg = { + .channel = channel, + .type = MIPI_DSI_DCS_READ, + .tx_buf = &cmd, + .tx_len = 1, + .rx_buf = data, + .rx_len = len + }; + + if (!ops || !ops->transfer) + return -ENOSYS; + + return ops->transfer(dsi->host, &msg); +} +EXPORT_SYMBOL(mipi_dsi_dcs_read); + +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 int 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); + + return drv->remove(dsi); +} + +/** + * mipi_dsi_driver_register - register a driver for DSI devices + * @drv: DSI driver structure + */ +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); + +/** + * mipi_dsi_driver_unregister - unregister a driver for DSI devices + * @drv: DSI driver structure + */ +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/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h new file mode 100644 index 0000000..d32628a --- /dev/null +++ b/include/drm/drm_mipi_dsi.h @@ -0,0 +1,158 @@ +/* + * MIPI DSI Bus + * + * Copyright (C) 2012-2013, Samsung Electronics, Co., Ltd. + * Andrzej Hajda <a.hajda@samsung.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 __DRM_MIPI_DSI_H__ +#define __DRM_MIPI_DSI_H__ + +#include <linux/device.h> + +struct mipi_dsi_host; +struct mipi_dsi_device; + +/** + * struct mipi_dsi_msg - read/write DSI buffer + * @channel: virtual channel id + * @type: payload data type + * @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; + + size_t tx_len; + const void *tx_buf; + + size_t rx_len; + void *rx_buf; +}; + +/** + * struct mipi_dsi_host_ops - DSI bus operations + * @attach: attach DSI device to DSI host + * @detach: detach DSI device from DSI host + * @transfer: send and/or receive DSI packet, return number of received bytes, + * or error + */ +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, + 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 + */ +struct mipi_dsi_host { + struct device *dev; + const struct mipi_dsi_host_ops *ops; +}; + +int mipi_dsi_host_register(struct mipi_dsi_host *host); +void mipi_dsi_host_unregister(struct mipi_dsi_host *host); + +/* 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_EOT_PACKET BIT(9) + +enum mipi_dsi_pixel_format { + MIPI_DSI_FMT_RGB888, + MIPI_DSI_FMT_RGB666, + MIPI_DSI_FMT_RGB666_PACKED, + MIPI_DSI_FMT_RGB565, +}; + +/** + * struct mipi_dsi_device - DSI peripheral device + * @host: DSI host for this peripheral + * @dev: driver model device node for this peripheral + * @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; + + unsigned int channel; + unsigned int lanes; + enum mipi_dsi_pixel_format format; + unsigned long mode_flags; +}; + +#define to_mipi_dsi_device(d) container_of(d, struct mipi_dsi_device, dev) + +int mipi_dsi_attach(struct mipi_dsi_device *dsi); +int mipi_dsi_detach(struct mipi_dsi_device *dsi); +int mipi_dsi_dcs_write(struct mipi_dsi_device *dsi, unsigned int channel, + const void *data, size_t len); +ssize_t mipi_dsi_dcs_read(struct mipi_dsi_device *dsi, unsigned int channel, + u8 cmd, void *data, size_t len); + +/** + * 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); + int(*remove)(struct mipi_dsi_device *dsi); +}; + +#define to_mipi_dsi_driver(d) container_of(d, 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) + +#endif /* __DRM_MIPI_DSI__ */ |