Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
config DRM_IMX
tristate "DRM Support for Freescale i.MX"
select DRM_KMS_HELPER
select DRM_KMS_FB_HELPER
select VIDEOMODE_HELPERS
select DRM_GEM_CMA_HELPER
select DRM_KMS_CMA_HELPER
depends on DRM && (ARCH_MXC || ARCH_MULTIPLATFORM)
help
enable i.MX graphics support
config DRM_IMX_FB_HELPER
tristate "provide legacy framebuffer /dev/fb0"
select DRM_KMS_CMA_HELPER
depends on DRM_IMX
help
The DRM framework can provide a legacy /dev/fb0 framebuffer
for your device. This is necessary to get a framebuffer console
and also for appplications using the legacy framebuffer API
config DRM_IMX_PARALLEL_DISPLAY
tristate "Support for parallel displays"
depends on DRM_IMX
select VIDEOMODE_HELPERS
config DRM_IMX_TVE
tristate "Support for TV and VGA displays"
depends on DRM_IMX
select REGMAP_MMIO
help
Choose this to enable the internal Television Encoder (TVe)
found on i.MX53 processors.
config DRM_IMX_IPUV3_CORE
tristate "IPUv3 core support"
depends on DRM_IMX
depends on RESET_CONTROLLER
help
Choose this if you have a i.MX5/6 system and want
to use the IPU. This option only enables IPU base
support.
config DRM_IMX_IPUV3
tristate "DRM Support for i.MX IPUv3"
depends on DRM_IMX
depends on DRM_IMX_IPUV3_CORE
help
Choose this if you have a i.MX5 or i.MX6 processor.

View File

@@ -0,0 +1,10 @@
imxdrm-objs := imx-drm-core.o imx-fb.o
obj-$(CONFIG_DRM_IMX) += imxdrm.o
obj-$(CONFIG_DRM_IMX_PARALLEL_DISPLAY) += parallel-display.o
obj-$(CONFIG_DRM_IMX_TVE) += imx-tve.o
obj-$(CONFIG_DRM_IMX_FB_HELPER) += imx-fbdev.o
obj-$(CONFIG_DRM_IMX_IPUV3_CORE) += ipu-v3/
obj-$(CONFIG_DRM_IMX_IPUV3) += ipuv3-crtc.o

View File

@@ -0,0 +1,25 @@
TODO:
- get DRM Maintainer review for this code
- Wait for common display framework to hit mainline and update the IPU
driver to use it. This will most probably make changes to the devicetree
bindings necessary.
- Factor out more code to common helper functions
- decide where to put the base driver. It is not specific to a subsystem
and would be used by DRM/KMS and media/V4L2
- convert irq driver to irq_domain_add_linear
Missing features (not necessarily for moving out of staging):
- Add KMS plane support for CRTC driver
- Add LDB (LVDS Display Bridge) support
- Add i.MX6 HDMI support
- Add support for IC (Image converter)
- Add support for CSI (CMOS Sensor interface)
- Add support for VDIC (Video Deinterlacer)
Many work-in-progress patches for the above features exist. Contact
Sascha Hauer <kernel@pengutronix.de> if you are interested in working
on a specific feature.
Please send any patches to Greg Kroah-Hartman <gregkh@linuxfoundation.org> and
Sascha Hauer <kernel@pengutronix.de>

View File

@@ -0,0 +1,892 @@
/*
* Freescale i.MX drm driver
*
* Copyright (C) 2011 Sascha Hauer, Pengutronix
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/device.h>
#include <linux/platform_device.h>
#include <drm/drmP.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_crtc_helper.h>
#include <linux/fb.h>
#include <linux/module.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include "imx-drm.h"
#define MAX_CRTC 4
struct crtc_cookie {
void *cookie;
int id;
struct list_head list;
};
struct imx_drm_device {
struct drm_device *drm;
struct device *dev;
struct list_head crtc_list;
struct list_head encoder_list;
struct list_head connector_list;
struct mutex mutex;
int references;
int pipes;
struct drm_fbdev_cma *fbhelper;
};
struct imx_drm_crtc {
struct drm_crtc *crtc;
struct list_head list;
struct imx_drm_device *imxdrm;
int pipe;
struct imx_drm_crtc_helper_funcs imx_drm_helper_funcs;
struct module *owner;
struct crtc_cookie cookie;
};
struct imx_drm_encoder {
struct drm_encoder *encoder;
struct list_head list;
struct module *owner;
struct list_head possible_crtcs;
};
struct imx_drm_connector {
struct drm_connector *connector;
struct list_head list;
struct module *owner;
};
static int imx_drm_driver_firstopen(struct drm_device *drm)
{
if (!imx_drm_device_get())
return -EINVAL;
return 0;
}
static void imx_drm_driver_lastclose(struct drm_device *drm)
{
struct imx_drm_device *imxdrm = drm->dev_private;
if (imxdrm->fbhelper)
drm_fbdev_cma_restore_mode(imxdrm->fbhelper);
imx_drm_device_put();
}
static int imx_drm_driver_unload(struct drm_device *drm)
{
struct imx_drm_device *imxdrm = drm->dev_private;
drm_mode_config_cleanup(imxdrm->drm);
drm_kms_helper_poll_fini(imxdrm->drm);
return 0;
}
/*
* We don't care at all for crtc numbers, but the core expects the
* crtcs to be numbered
*/
static struct imx_drm_crtc *imx_drm_crtc_by_num(struct imx_drm_device *imxdrm,
int num)
{
struct imx_drm_crtc *imx_drm_crtc;
list_for_each_entry(imx_drm_crtc, &imxdrm->crtc_list, list)
if (imx_drm_crtc->pipe == num)
return imx_drm_crtc;
return NULL;
}
int imx_drm_crtc_panel_format_pins(struct drm_crtc *crtc, u32 encoder_type,
u32 interface_pix_fmt, int hsync_pin, int vsync_pin)
{
struct imx_drm_device *imxdrm = crtc->dev->dev_private;
struct imx_drm_crtc *imx_crtc;
struct imx_drm_crtc_helper_funcs *helper;
mutex_lock(&imxdrm->mutex);
list_for_each_entry(imx_crtc, &imxdrm->crtc_list, list)
if (imx_crtc->crtc == crtc)
goto found;
mutex_unlock(&imxdrm->mutex);
return -EINVAL;
found:
mutex_unlock(&imxdrm->mutex);
helper = &imx_crtc->imx_drm_helper_funcs;
if (helper->set_interface_pix_fmt)
return helper->set_interface_pix_fmt(crtc,
encoder_type, interface_pix_fmt,
hsync_pin, vsync_pin);
return 0;
}
EXPORT_SYMBOL_GPL(imx_drm_crtc_panel_format_pins);
int imx_drm_crtc_panel_format(struct drm_crtc *crtc, u32 encoder_type,
u32 interface_pix_fmt)
{
return imx_drm_crtc_panel_format_pins(crtc, encoder_type,
interface_pix_fmt, 0, 0);
}
EXPORT_SYMBOL_GPL(imx_drm_crtc_panel_format);
int imx_drm_crtc_vblank_get(struct imx_drm_crtc *imx_drm_crtc)
{
return drm_vblank_get(imx_drm_crtc->imxdrm->drm, imx_drm_crtc->pipe);
}
EXPORT_SYMBOL_GPL(imx_drm_crtc_vblank_get);
void imx_drm_crtc_vblank_put(struct imx_drm_crtc *imx_drm_crtc)
{
drm_vblank_put(imx_drm_crtc->imxdrm->drm, imx_drm_crtc->pipe);
}
EXPORT_SYMBOL_GPL(imx_drm_crtc_vblank_put);
void imx_drm_handle_vblank(struct imx_drm_crtc *imx_drm_crtc)
{
drm_handle_vblank(imx_drm_crtc->imxdrm->drm, imx_drm_crtc->pipe);
}
EXPORT_SYMBOL_GPL(imx_drm_handle_vblank);
static int imx_drm_enable_vblank(struct drm_device *drm, int crtc)
{
struct imx_drm_device *imxdrm = drm->dev_private;
struct imx_drm_crtc *imx_drm_crtc;
int ret;
imx_drm_crtc = imx_drm_crtc_by_num(imxdrm, crtc);
if (!imx_drm_crtc)
return -EINVAL;
if (!imx_drm_crtc->imx_drm_helper_funcs.enable_vblank)
return -ENOSYS;
ret = imx_drm_crtc->imx_drm_helper_funcs.enable_vblank(
imx_drm_crtc->crtc);
return ret;
}
static void imx_drm_disable_vblank(struct drm_device *drm, int crtc)
{
struct imx_drm_device *imxdrm = drm->dev_private;
struct imx_drm_crtc *imx_drm_crtc;
imx_drm_crtc = imx_drm_crtc_by_num(imxdrm, crtc);
if (!imx_drm_crtc)
return;
if (!imx_drm_crtc->imx_drm_helper_funcs.disable_vblank)
return;
imx_drm_crtc->imx_drm_helper_funcs.disable_vblank(imx_drm_crtc->crtc);
}
static const struct file_operations imx_drm_driver_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
.mmap = drm_gem_cma_mmap,
.poll = drm_poll,
.read = drm_read,
.llseek = noop_llseek,
};
static struct imx_drm_device *imx_drm_device;
static struct imx_drm_device *__imx_drm_device(void)
{
return imx_drm_device;
}
struct drm_device *imx_drm_device_get(void)
{
struct imx_drm_device *imxdrm = __imx_drm_device();
struct imx_drm_encoder *enc;
struct imx_drm_connector *con;
struct imx_drm_crtc *crtc;
mutex_lock(&imxdrm->mutex);
list_for_each_entry(enc, &imxdrm->encoder_list, list) {
if (!try_module_get(enc->owner)) {
dev_err(imxdrm->dev, "could not get module %s\n",
module_name(enc->owner));
goto unwind_enc;
}
}
list_for_each_entry(con, &imxdrm->connector_list, list) {
if (!try_module_get(con->owner)) {
dev_err(imxdrm->dev, "could not get module %s\n",
module_name(con->owner));
goto unwind_con;
}
}
list_for_each_entry(crtc, &imxdrm->crtc_list, list) {
if (!try_module_get(crtc->owner)) {
dev_err(imxdrm->dev, "could not get module %s\n",
module_name(crtc->owner));
goto unwind_crtc;
}
}
imxdrm->references++;
mutex_unlock(&imxdrm->mutex);
return imxdrm->drm;
unwind_crtc:
list_for_each_entry_continue_reverse(crtc, &imxdrm->crtc_list, list)
module_put(crtc->owner);
unwind_con:
list_for_each_entry_continue_reverse(con, &imxdrm->connector_list, list)
module_put(con->owner);
unwind_enc:
list_for_each_entry_continue_reverse(enc, &imxdrm->encoder_list, list)
module_put(enc->owner);
mutex_unlock(&imxdrm->mutex);
return NULL;
}
EXPORT_SYMBOL_GPL(imx_drm_device_get);
void imx_drm_device_put(void)
{
struct imx_drm_device *imxdrm = __imx_drm_device();
struct imx_drm_encoder *enc;
struct imx_drm_connector *con;
struct imx_drm_crtc *crtc;
mutex_lock(&imxdrm->mutex);
list_for_each_entry(crtc, &imxdrm->crtc_list, list)
module_put(crtc->owner);
list_for_each_entry(con, &imxdrm->connector_list, list)
module_put(con->owner);
list_for_each_entry(enc, &imxdrm->encoder_list, list)
module_put(enc->owner);
imxdrm->references--;
mutex_unlock(&imxdrm->mutex);
}
EXPORT_SYMBOL_GPL(imx_drm_device_put);
static int drm_mode_group_reinit(struct drm_device *dev)
{
struct drm_mode_group *group = &dev->primary->mode_group;
uint32_t *id_list = group->id_list;
int ret;
ret = drm_mode_group_init_legacy_group(dev, group);
if (ret < 0)
return ret;
kfree(id_list);
return 0;
}
/*
* register an encoder to the drm core
*/
static int imx_drm_encoder_register(struct imx_drm_encoder *imx_drm_encoder)
{
struct imx_drm_device *imxdrm = __imx_drm_device();
INIT_LIST_HEAD(&imx_drm_encoder->possible_crtcs);
drm_encoder_init(imxdrm->drm, imx_drm_encoder->encoder,
imx_drm_encoder->encoder->funcs,
imx_drm_encoder->encoder->encoder_type);
drm_mode_group_reinit(imxdrm->drm);
return 0;
}
/*
* unregister an encoder from the drm core
*/
static void imx_drm_encoder_unregister(struct imx_drm_encoder
*imx_drm_encoder)
{
struct imx_drm_device *imxdrm = __imx_drm_device();
drm_encoder_cleanup(imx_drm_encoder->encoder);
drm_mode_group_reinit(imxdrm->drm);
}
/*
* register a connector to the drm core
*/
static int imx_drm_connector_register(
struct imx_drm_connector *imx_drm_connector)
{
struct imx_drm_device *imxdrm = __imx_drm_device();
drm_connector_init(imxdrm->drm, imx_drm_connector->connector,
imx_drm_connector->connector->funcs,
imx_drm_connector->connector->connector_type);
drm_mode_group_reinit(imxdrm->drm);
return drm_sysfs_connector_add(imx_drm_connector->connector);
}
/*
* unregister a connector from the drm core
*/
static void imx_drm_connector_unregister(
struct imx_drm_connector *imx_drm_connector)
{
struct imx_drm_device *imxdrm = __imx_drm_device();
drm_sysfs_connector_remove(imx_drm_connector->connector);
drm_connector_cleanup(imx_drm_connector->connector);
drm_mode_group_reinit(imxdrm->drm);
}
/*
* register a crtc to the drm core
*/
static int imx_drm_crtc_register(struct imx_drm_crtc *imx_drm_crtc)
{
struct imx_drm_device *imxdrm = __imx_drm_device();
int ret;
drm_crtc_init(imxdrm->drm, imx_drm_crtc->crtc,
imx_drm_crtc->imx_drm_helper_funcs.crtc_funcs);
ret = drm_mode_crtc_set_gamma_size(imx_drm_crtc->crtc, 256);
if (ret)
return ret;
drm_crtc_helper_add(imx_drm_crtc->crtc,
imx_drm_crtc->imx_drm_helper_funcs.crtc_helper_funcs);
drm_mode_group_reinit(imxdrm->drm);
return 0;
}
/*
* Called by the CRTC driver when all CRTCs are registered. This
* puts all the pieces together and initializes the driver.
* Once this is called no more CRTCs can be registered since
* the drm core has hardcoded the number of crtcs in several
* places.
*/
static int imx_drm_driver_load(struct drm_device *drm, unsigned long flags)
{
struct imx_drm_device *imxdrm = __imx_drm_device();
int ret;
imxdrm->drm = drm;
drm->dev_private = imxdrm;
/*
* enable drm irq mode.
* - with irq_enabled = true, we can use the vblank feature.
*
* P.S. note that we wouldn't use drm irq handler but
* just specific driver own one instead because
* drm framework supports only one irq handler and
* drivers can well take care of their interrupts
*/
drm->irq_enabled = true;
drm_mode_config_init(drm);
imx_drm_mode_config_init(drm);
mutex_lock(&imxdrm->mutex);
drm_kms_helper_poll_init(imxdrm->drm);
/* setup the grouping for the legacy output */
ret = drm_mode_group_init_legacy_group(imxdrm->drm,
&imxdrm->drm->primary->mode_group);
if (ret)
goto err_init;
ret = drm_vblank_init(imxdrm->drm, MAX_CRTC);
if (ret)
goto err_init;
/*
* with vblank_disable_allowed = true, vblank interrupt will be disabled
* by drm timer once a current process gives up ownership of
* vblank event.(after drm_vblank_put function is called)
*/
imxdrm->drm->vblank_disable_allowed = true;
ret = 0;
err_init:
mutex_unlock(&imxdrm->mutex);
return ret;
}
static void imx_drm_update_possible_crtcs(void)
{
struct imx_drm_device *imxdrm = __imx_drm_device();
struct imx_drm_crtc *imx_drm_crtc;
struct imx_drm_encoder *enc;
struct crtc_cookie *cookie;
list_for_each_entry(enc, &imxdrm->encoder_list, list) {
u32 possible_crtcs = 0;
list_for_each_entry(cookie, &enc->possible_crtcs, list) {
list_for_each_entry(imx_drm_crtc, &imxdrm->crtc_list, list) {
if (imx_drm_crtc->cookie.cookie == cookie->cookie &&
imx_drm_crtc->cookie.id == cookie->id) {
possible_crtcs |= 1 << imx_drm_crtc->pipe;
}
}
}
enc->encoder->possible_crtcs = possible_crtcs;
enc->encoder->possible_clones = possible_crtcs;
}
}
/*
* imx_drm_add_crtc - add a new crtc
*
* The return value if !NULL is a cookie for the caller to pass to
* imx_drm_remove_crtc later.
*/
int imx_drm_add_crtc(struct drm_crtc *crtc,
struct imx_drm_crtc **new_crtc,
const struct imx_drm_crtc_helper_funcs *imx_drm_helper_funcs,
struct module *owner, void *cookie, int id)
{
struct imx_drm_device *imxdrm = __imx_drm_device();
struct imx_drm_crtc *imx_drm_crtc;
const struct drm_crtc_funcs *crtc_funcs;
int ret;
mutex_lock(&imxdrm->mutex);
if (imxdrm->references) {
ret = -EBUSY;
goto err_busy;
}
imx_drm_crtc = kzalloc(sizeof(*imx_drm_crtc), GFP_KERNEL);
if (!imx_drm_crtc) {
ret = -ENOMEM;
goto err_alloc;
}
imx_drm_crtc->imx_drm_helper_funcs = *imx_drm_helper_funcs;
imx_drm_crtc->pipe = imxdrm->pipes++;
imx_drm_crtc->cookie.cookie = cookie;
imx_drm_crtc->cookie.id = id;
crtc_funcs = imx_drm_helper_funcs->crtc_funcs;
imx_drm_crtc->crtc = crtc;
imx_drm_crtc->imxdrm = imxdrm;
imx_drm_crtc->owner = owner;
list_add_tail(&imx_drm_crtc->list, &imxdrm->crtc_list);
*new_crtc = imx_drm_crtc;
ret = imx_drm_crtc_register(imx_drm_crtc);
if (ret)
goto err_register;
imx_drm_update_possible_crtcs();
mutex_unlock(&imxdrm->mutex);
return 0;
err_register:
kfree(imx_drm_crtc);
err_alloc:
err_busy:
mutex_unlock(&imxdrm->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(imx_drm_add_crtc);
/*
* imx_drm_remove_crtc - remove a crtc
*/
int imx_drm_remove_crtc(struct imx_drm_crtc *imx_drm_crtc)
{
struct imx_drm_device *imxdrm = imx_drm_crtc->imxdrm;
mutex_lock(&imxdrm->mutex);
drm_crtc_cleanup(imx_drm_crtc->crtc);
list_del(&imx_drm_crtc->list);
drm_mode_group_reinit(imxdrm->drm);
mutex_unlock(&imxdrm->mutex);
kfree(imx_drm_crtc);
return 0;
}
EXPORT_SYMBOL_GPL(imx_drm_remove_crtc);
/*
* imx_drm_add_encoder - add a new encoder
*/
int imx_drm_add_encoder(struct drm_encoder *encoder,
struct imx_drm_encoder **newenc, struct module *owner)
{
struct imx_drm_device *imxdrm = __imx_drm_device();
struct imx_drm_encoder *imx_drm_encoder;
int ret;
mutex_lock(&imxdrm->mutex);
if (imxdrm->references) {
ret = -EBUSY;
goto err_busy;
}
imx_drm_encoder = kzalloc(sizeof(*imx_drm_encoder), GFP_KERNEL);
if (!imx_drm_encoder) {
ret = -ENOMEM;
goto err_alloc;
}
imx_drm_encoder->encoder = encoder;
imx_drm_encoder->owner = owner;
ret = imx_drm_encoder_register(imx_drm_encoder);
if (ret) {
ret = -ENOMEM;
goto err_register;
}
list_add_tail(&imx_drm_encoder->list, &imxdrm->encoder_list);
*newenc = imx_drm_encoder;
mutex_unlock(&imxdrm->mutex);
return 0;
err_register:
kfree(imx_drm_encoder);
err_alloc:
err_busy:
mutex_unlock(&imxdrm->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(imx_drm_add_encoder);
int imx_drm_encoder_add_possible_crtcs(
struct imx_drm_encoder *imx_drm_encoder,
struct device_node *np)
{
struct imx_drm_device *imxdrm = __imx_drm_device();
struct of_phandle_args args;
struct crtc_cookie *c;
int ret = 0;
int i;
if (!list_empty(&imx_drm_encoder->possible_crtcs))
return -EBUSY;
for (i = 0; !ret; i++) {
ret = of_parse_phandle_with_args(np, "crtcs",
"#crtc-cells", i, &args);
if (ret < 0)
break;
c = kzalloc(sizeof(*c), GFP_KERNEL);
if (!c) {
of_node_put(args.np);
return -ENOMEM;
}
c->cookie = args.np;
c->id = args.args_count > 0 ? args.args[0] : 0;
of_node_put(args.np);
mutex_lock(&imxdrm->mutex);
list_add_tail(&c->list, &imx_drm_encoder->possible_crtcs);
mutex_unlock(&imxdrm->mutex);
}
imx_drm_update_possible_crtcs();
return 0;
}
EXPORT_SYMBOL_GPL(imx_drm_encoder_add_possible_crtcs);
int imx_drm_encoder_get_mux_id(struct imx_drm_encoder *imx_drm_encoder,
struct drm_crtc *crtc)
{
struct imx_drm_device *imxdrm = __imx_drm_device();
struct imx_drm_crtc *imx_crtc;
int i = 0;
mutex_lock(&imxdrm->mutex);
list_for_each_entry(imx_crtc, &imxdrm->crtc_list, list) {
if (imx_crtc->crtc == crtc)
goto found;
i++;
}
mutex_unlock(&imxdrm->mutex);
return -EINVAL;
found:
mutex_unlock(&imxdrm->mutex);
return i;
}
EXPORT_SYMBOL_GPL(imx_drm_encoder_get_mux_id);
/*
* imx_drm_remove_encoder - remove an encoder
*/
int imx_drm_remove_encoder(struct imx_drm_encoder *imx_drm_encoder)
{
struct imx_drm_device *imxdrm = __imx_drm_device();
struct crtc_cookie *c, *tmp;
mutex_lock(&imxdrm->mutex);
imx_drm_encoder_unregister(imx_drm_encoder);
list_del(&imx_drm_encoder->list);
list_for_each_entry_safe(c, tmp, &imx_drm_encoder->possible_crtcs,
list)
kfree(c);
mutex_unlock(&imxdrm->mutex);
kfree(imx_drm_encoder);
return 0;
}
EXPORT_SYMBOL_GPL(imx_drm_remove_encoder);
/*
* imx_drm_add_connector - add a connector
*/
int imx_drm_add_connector(struct drm_connector *connector,
struct imx_drm_connector **new_con,
struct module *owner)
{
struct imx_drm_device *imxdrm = __imx_drm_device();
struct imx_drm_connector *imx_drm_connector;
int ret;
mutex_lock(&imxdrm->mutex);
if (imxdrm->references) {
ret = -EBUSY;
goto err_busy;
}
imx_drm_connector = kzalloc(sizeof(*imx_drm_connector), GFP_KERNEL);
if (!imx_drm_connector) {
ret = -ENOMEM;
goto err_alloc;
}
imx_drm_connector->connector = connector;
imx_drm_connector->owner = owner;
ret = imx_drm_connector_register(imx_drm_connector);
if (ret)
goto err_register;
list_add_tail(&imx_drm_connector->list, &imxdrm->connector_list);
*new_con = imx_drm_connector;
mutex_unlock(&imxdrm->mutex);
return 0;
err_register:
kfree(imx_drm_connector);
err_alloc:
err_busy:
mutex_unlock(&imxdrm->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(imx_drm_add_connector);
void imx_drm_fb_helper_set(struct drm_fbdev_cma *fbdev_helper)
{
struct imx_drm_device *imxdrm = __imx_drm_device();
imxdrm->fbhelper = fbdev_helper;
}
EXPORT_SYMBOL_GPL(imx_drm_fb_helper_set);
/*
* imx_drm_remove_connector - remove a connector
*/
int imx_drm_remove_connector(struct imx_drm_connector *imx_drm_connector)
{
struct imx_drm_device *imxdrm = __imx_drm_device();
mutex_lock(&imxdrm->mutex);
imx_drm_connector_unregister(imx_drm_connector);
list_del(&imx_drm_connector->list);
mutex_unlock(&imxdrm->mutex);
kfree(imx_drm_connector);
return 0;
}
EXPORT_SYMBOL_GPL(imx_drm_remove_connector);
static const struct drm_ioctl_desc imx_drm_ioctls[] = {
/* none so far */
};
static struct drm_driver imx_drm_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM,
.load = imx_drm_driver_load,
.unload = imx_drm_driver_unload,
.firstopen = imx_drm_driver_firstopen,
.lastclose = imx_drm_driver_lastclose,
.gem_free_object = drm_gem_cma_free_object,
.gem_vm_ops = &drm_gem_cma_vm_ops,
.dumb_create = drm_gem_cma_dumb_create,
.dumb_map_offset = drm_gem_cma_dumb_map_offset,
.dumb_destroy = drm_gem_dumb_destroy,
.get_vblank_counter = drm_vblank_count,
.enable_vblank = imx_drm_enable_vblank,
.disable_vblank = imx_drm_disable_vblank,
.ioctls = imx_drm_ioctls,
.num_ioctls = ARRAY_SIZE(imx_drm_ioctls),
.fops = &imx_drm_driver_fops,
.name = "imx-drm",
.desc = "i.MX DRM graphics",
.date = "20120507",
.major = 1,
.minor = 0,
.patchlevel = 0,
};
static int imx_drm_platform_probe(struct platform_device *pdev)
{
imx_drm_device->dev = &pdev->dev;
return drm_platform_init(&imx_drm_driver, pdev);
}
static int imx_drm_platform_remove(struct platform_device *pdev)
{
drm_platform_exit(&imx_drm_driver, pdev);
return 0;
}
static struct platform_driver imx_drm_pdrv = {
.probe = imx_drm_platform_probe,
.remove = imx_drm_platform_remove,
.driver = {
.owner = THIS_MODULE,
.name = "imx-drm",
},
};
static struct platform_device *imx_drm_pdev;
static int __init imx_drm_init(void)
{
int ret;
imx_drm_device = kzalloc(sizeof(*imx_drm_device), GFP_KERNEL);
if (!imx_drm_device)
return -ENOMEM;
mutex_init(&imx_drm_device->mutex);
INIT_LIST_HEAD(&imx_drm_device->crtc_list);
INIT_LIST_HEAD(&imx_drm_device->connector_list);
INIT_LIST_HEAD(&imx_drm_device->encoder_list);
imx_drm_pdev = platform_device_register_simple("imx-drm", -1, NULL, 0);
if (!imx_drm_pdev) {
ret = -EINVAL;
goto err_pdev;
}
imx_drm_pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32),
ret = platform_driver_register(&imx_drm_pdrv);
if (ret)
goto err_pdrv;
return 0;
err_pdrv:
platform_device_unregister(imx_drm_pdev);
err_pdev:
kfree(imx_drm_device);
return ret;
}
static void __exit imx_drm_exit(void)
{
platform_device_unregister(imx_drm_pdev);
platform_driver_unregister(&imx_drm_pdrv);
kfree(imx_drm_device);
}
module_init(imx_drm_init);
module_exit(imx_drm_exit);
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
MODULE_DESCRIPTION("i.MX drm driver core");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,70 @@
#ifndef _IMX_DRM_H_
#define _IMX_DRM_H_
#include <linux/videodev2.h>
#define IPU_PIX_FMT_GBR24 v4l2_fourcc('G', 'B', 'R', '3')
struct drm_crtc;
struct drm_connector;
struct drm_device;
struct drm_encoder;
struct imx_drm_crtc;
struct drm_fbdev_cma;
struct drm_framebuffer;
struct platform_device;
struct imx_drm_crtc_helper_funcs {
int (*enable_vblank)(struct drm_crtc *crtc);
void (*disable_vblank)(struct drm_crtc *crtc);
int (*set_interface_pix_fmt)(struct drm_crtc *crtc, u32 encoder_type,
u32 pix_fmt, int hsync_pin, int vsync_pin);
const struct drm_crtc_helper_funcs *crtc_helper_funcs;
const struct drm_crtc_funcs *crtc_funcs;
};
int imx_drm_add_crtc(struct drm_crtc *crtc,
struct imx_drm_crtc **new_crtc,
const struct imx_drm_crtc_helper_funcs *imx_helper_funcs,
struct module *owner, void *cookie, int id);
int imx_drm_remove_crtc(struct imx_drm_crtc *);
int imx_drm_init_drm(struct platform_device *pdev,
int preferred_bpp);
int imx_drm_exit_drm(void);
int imx_drm_crtc_vblank_get(struct imx_drm_crtc *imx_drm_crtc);
void imx_drm_crtc_vblank_put(struct imx_drm_crtc *imx_drm_crtc);
void imx_drm_handle_vblank(struct imx_drm_crtc *imx_drm_crtc);
struct imx_drm_encoder;
int imx_drm_add_encoder(struct drm_encoder *encoder,
struct imx_drm_encoder **new_enc,
struct module *owner);
int imx_drm_remove_encoder(struct imx_drm_encoder *);
struct imx_drm_connector;
int imx_drm_add_connector(struct drm_connector *connector,
struct imx_drm_connector **new_con,
struct module *owner);
int imx_drm_remove_connector(struct imx_drm_connector *);
void imx_drm_mode_config_init(struct drm_device *drm);
struct drm_gem_cma_object *imx_drm_fb_get_obj(struct drm_framebuffer *fb);
struct drm_device *imx_drm_device_get(void);
void imx_drm_device_put(void);
int imx_drm_crtc_panel_format_pins(struct drm_crtc *crtc, u32 encoder_type,
u32 interface_pix_fmt, int hsync_pin, int vsync_pin);
int imx_drm_crtc_panel_format(struct drm_crtc *crtc, u32 encoder_type,
u32 interface_pix_fmt);
void imx_drm_fb_helper_set(struct drm_fbdev_cma *fbdev_helper);
struct device_node;
int imx_drm_encoder_get_mux_id(struct imx_drm_encoder *imx_drm_encoder,
struct drm_crtc *crtc);
int imx_drm_encoder_add_possible_crtcs(struct imx_drm_encoder *imx_drm_encoder,
struct device_node *np);
#endif /* _IMX_DRM_H_ */

View File

@@ -0,0 +1,47 @@
/*
* i.MX drm driver
*
* Copyright (C) 2012 Sascha Hauer, Pengutronix
*
* Based on Samsung Exynos code
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/module.h>
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include "imx-drm.h"
static struct drm_mode_config_funcs imx_drm_mode_config_funcs = {
.fb_create = drm_fb_cma_create,
};
void imx_drm_mode_config_init(struct drm_device *dev)
{
dev->mode_config.min_width = 64;
dev->mode_config.min_height = 64;
/*
* set max width and height as default value(4096x4096).
* this value would be used to check framebuffer size limitation
* at drm_mode_addfb().
*/
dev->mode_config.max_width = 4096;
dev->mode_config.max_height = 4096;
dev->mode_config.funcs = &imx_drm_mode_config_funcs;
}

View File

@@ -0,0 +1,74 @@
/*
* i.MX drm driver
*
* Copyright (C) 2012 Sascha Hauer, Pengutronix
*
* Based on Samsung Exynos code
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/module.h>
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include "imx-drm.h"
#define MAX_CONNECTOR 4
#define PREFERRED_BPP 16
static struct drm_fbdev_cma *fbdev_cma;
static int legacyfb_depth = 16;
module_param(legacyfb_depth, int, 0444);
static int __init imx_fb_helper_init(void)
{
struct drm_device *drm = imx_drm_device_get();
if (!drm)
return -EINVAL;
if (legacyfb_depth != 16 && legacyfb_depth != 32) {
pr_warn("i.MX legacyfb: invalid legacyfb_depth setting. defaulting to 16bpp\n");
legacyfb_depth = 16;
}
fbdev_cma = drm_fbdev_cma_init(drm, legacyfb_depth,
drm->mode_config.num_crtc, MAX_CONNECTOR);
if (IS_ERR(fbdev_cma)) {
imx_drm_device_put();
return PTR_ERR(fbdev_cma);
}
imx_drm_fb_helper_set(fbdev_cma);
return 0;
}
static void __exit imx_fb_helper_exit(void)
{
imx_drm_fb_helper_set(NULL);
drm_fbdev_cma_fini(fbdev_cma);
imx_drm_device_put();
}
late_initcall(imx_fb_helper_init);
module_exit(imx_fb_helper_exit);
MODULE_DESCRIPTION("Freescale i.MX legacy fb driver");
MODULE_AUTHOR("Sascha Hauer, Pengutronix");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,757 @@
/*
* i.MX drm driver - Television Encoder (TVEv2)
*
* Copyright (C) 2013 Philipp Zabel, Pengutronix
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/module.h>
#include <linux/of_i2c.h>
#include <linux/pinctrl/consumer.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/spinlock.h>
#include <linux/videodev2.h>
#include <drm/drmP.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_crtc_helper.h>
#include "imx-drm.h"
#define TVE_COM_CONF_REG 0x00
#define TVE_TVDAC0_CONT_REG 0x28
#define TVE_TVDAC1_CONT_REG 0x2c
#define TVE_TVDAC2_CONT_REG 0x30
#define TVE_CD_CONT_REG 0x34
#define TVE_INT_CONT_REG 0x64
#define TVE_STAT_REG 0x68
#define TVE_TST_MODE_REG 0x6c
#define TVE_MV_CONT_REG 0xdc
/* TVE_COM_CONF_REG */
#define TVE_SYNC_CH_2_EN BIT(22)
#define TVE_SYNC_CH_1_EN BIT(21)
#define TVE_SYNC_CH_0_EN BIT(20)
#define TVE_TV_OUT_MODE_MASK (0x7 << 12)
#define TVE_TV_OUT_DISABLE (0x0 << 12)
#define TVE_TV_OUT_CVBS_0 (0x1 << 12)
#define TVE_TV_OUT_CVBS_2 (0x2 << 12)
#define TVE_TV_OUT_CVBS_0_2 (0x3 << 12)
#define TVE_TV_OUT_SVIDEO_0_1 (0x4 << 12)
#define TVE_TV_OUT_SVIDEO_0_1_CVBS2_2 (0x5 << 12)
#define TVE_TV_OUT_YPBPR (0x6 << 12)
#define TVE_TV_OUT_RGB (0x7 << 12)
#define TVE_TV_STAND_MASK (0xf << 8)
#define TVE_TV_STAND_HD_1080P30 (0xc << 8)
#define TVE_P2I_CONV_EN BIT(7)
#define TVE_INP_VIDEO_FORM BIT(6)
#define TVE_INP_YCBCR_422 (0x0 << 6)
#define TVE_INP_YCBCR_444 (0x1 << 6)
#define TVE_DATA_SOURCE_MASK (0x3 << 4)
#define TVE_DATA_SOURCE_BUS1 (0x0 << 4)
#define TVE_DATA_SOURCE_BUS2 (0x1 << 4)
#define TVE_DATA_SOURCE_EXT (0x2 << 4)
#define TVE_DATA_SOURCE_TESTGEN (0x3 << 4)
#define TVE_IPU_CLK_EN_OFS 3
#define TVE_IPU_CLK_EN BIT(3)
#define TVE_DAC_SAMP_RATE_OFS 1
#define TVE_DAC_SAMP_RATE_WIDTH 2
#define TVE_DAC_SAMP_RATE_MASK (0x3 << 1)
#define TVE_DAC_FULL_RATE (0x0 << 1)
#define TVE_DAC_DIV2_RATE (0x1 << 1)
#define TVE_DAC_DIV4_RATE (0x2 << 1)
#define TVE_EN BIT(0)
/* TVE_TVDACx_CONT_REG */
#define TVE_TVDAC_GAIN_MASK (0x3f << 0)
/* TVE_CD_CONT_REG */
#define TVE_CD_CH_2_SM_EN BIT(22)
#define TVE_CD_CH_1_SM_EN BIT(21)
#define TVE_CD_CH_0_SM_EN BIT(20)
#define TVE_CD_CH_2_LM_EN BIT(18)
#define TVE_CD_CH_1_LM_EN BIT(17)
#define TVE_CD_CH_0_LM_EN BIT(16)
#define TVE_CD_CH_2_REF_LVL BIT(10)
#define TVE_CD_CH_1_REF_LVL BIT(9)
#define TVE_CD_CH_0_REF_LVL BIT(8)
#define TVE_CD_EN BIT(0)
/* TVE_INT_CONT_REG */
#define TVE_FRAME_END_IEN BIT(13)
#define TVE_CD_MON_END_IEN BIT(2)
#define TVE_CD_SM_IEN BIT(1)
#define TVE_CD_LM_IEN BIT(0)
/* TVE_TST_MODE_REG */
#define TVE_TVDAC_TEST_MODE_MASK (0x7 << 0)
#define con_to_tve(x) container_of(x, struct imx_tve, connector)
#define enc_to_tve(x) container_of(x, struct imx_tve, encoder)
enum {
TVE_MODE_TVOUT,
TVE_MODE_VGA,
};
struct imx_tve {
struct drm_connector connector;
struct imx_drm_connector *imx_drm_connector;
struct drm_encoder encoder;
struct imx_drm_encoder *imx_drm_encoder;
struct device *dev;
spinlock_t enable_lock; /* serializes tve_enable/disable */
spinlock_t lock; /* register lock */
bool enabled;
int mode;
struct regmap *regmap;
struct regulator *dac_reg;
struct i2c_adapter *ddc;
struct clk *clk;
struct clk *di_sel_clk;
struct clk_hw clk_hw_di;
struct clk *di_clk;
int vsync_pin;
int hsync_pin;
};
static void tve_lock(void *__tve)
{
struct imx_tve *tve = __tve;
spin_lock(&tve->lock);
}
static void tve_unlock(void *__tve)
{
struct imx_tve *tve = __tve;
spin_unlock(&tve->lock);
}
static void tve_enable(struct imx_tve *tve)
{
unsigned long flags;
int ret;
spin_lock_irqsave(&tve->enable_lock, flags);
if (!tve->enabled) {
tve->enabled = 1;
clk_prepare_enable(tve->clk);
ret = regmap_update_bits(tve->regmap, TVE_COM_CONF_REG,
TVE_IPU_CLK_EN | TVE_EN,
TVE_IPU_CLK_EN | TVE_EN);
}
/* clear interrupt status register */
regmap_write(tve->regmap, TVE_STAT_REG, 0xffffffff);
/* cable detection irq disabled in VGA mode, enabled in TVOUT mode */
if (tve->mode == TVE_MODE_VGA)
regmap_write(tve->regmap, TVE_INT_CONT_REG, 0);
else
regmap_write(tve->regmap, TVE_INT_CONT_REG,
TVE_CD_SM_IEN | TVE_CD_LM_IEN | TVE_CD_MON_END_IEN);
spin_unlock_irqrestore(&tve->enable_lock, flags);
}
static void tve_disable(struct imx_tve *tve)
{
unsigned long flags;
int ret;
spin_lock_irqsave(&tve->enable_lock, flags);
if (tve->enabled) {
tve->enabled = 0;
ret = regmap_update_bits(tve->regmap, TVE_COM_CONF_REG,
TVE_IPU_CLK_EN | TVE_EN, 0);
clk_disable_unprepare(tve->clk);
}
spin_unlock_irqrestore(&tve->enable_lock, flags);
}
static int tve_setup_tvout(struct imx_tve *tve)
{
return -ENOTSUPP;
}
static int tve_setup_vga(struct imx_tve *tve)
{
unsigned int mask;
unsigned int val;
int ret;
/* set gain to (1 + 10/128) to provide 0.7V peak-to-peak amplitude */
ret = regmap_update_bits(tve->regmap, TVE_TVDAC0_CONT_REG,
TVE_TVDAC_GAIN_MASK, 0x0a);
ret = regmap_update_bits(tve->regmap, TVE_TVDAC1_CONT_REG,
TVE_TVDAC_GAIN_MASK, 0x0a);
ret = regmap_update_bits(tve->regmap, TVE_TVDAC2_CONT_REG,
TVE_TVDAC_GAIN_MASK, 0x0a);
/* set configuration register */
mask = TVE_DATA_SOURCE_MASK | TVE_INP_VIDEO_FORM;
val = TVE_DATA_SOURCE_BUS2 | TVE_INP_YCBCR_444;
mask |= TVE_TV_STAND_MASK | TVE_P2I_CONV_EN;
val |= TVE_TV_STAND_HD_1080P30 | 0;
mask |= TVE_TV_OUT_MODE_MASK | TVE_SYNC_CH_0_EN;
val |= TVE_TV_OUT_RGB | TVE_SYNC_CH_0_EN;
ret = regmap_update_bits(tve->regmap, TVE_COM_CONF_REG, mask, val);
if (ret < 0) {
dev_err(tve->dev, "failed to set configuration: %d\n", ret);
return ret;
}
/* set test mode (as documented) */
ret = regmap_update_bits(tve->regmap, TVE_TST_MODE_REG,
TVE_TVDAC_TEST_MODE_MASK, 1);
return 0;
}
static enum drm_connector_status imx_tve_connector_detect(
struct drm_connector *connector, bool force)
{
return connector_status_connected;
}
static void imx_tve_connector_destroy(struct drm_connector *connector)
{
/* do not free here */
}
static int imx_tve_connector_get_modes(struct drm_connector *connector)
{
struct imx_tve *tve = con_to_tve(connector);
struct edid *edid;
int ret = 0;
if (!tve->ddc)
return 0;
edid = drm_get_edid(connector, tve->ddc);
if (edid) {
drm_mode_connector_update_edid_property(connector, edid);
ret = drm_add_edid_modes(connector, edid);
kfree(edid);
}
return ret;
}
static int imx_tve_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
struct imx_tve *tve = con_to_tve(connector);
unsigned long rate;
/* pixel clock with 2x oversampling */
rate = clk_round_rate(tve->clk, 2000UL * mode->clock) / 2000;
if (rate == mode->clock)
return MODE_OK;
/* pixel clock without oversampling */
rate = clk_round_rate(tve->clk, 1000UL * mode->clock) / 1000;
if (rate == mode->clock)
return MODE_OK;
dev_warn(tve->dev, "ignoring mode %dx%d\n",
mode->hdisplay, mode->vdisplay);
return MODE_BAD;
}
static struct drm_encoder *imx_tve_connector_best_encoder(
struct drm_connector *connector)
{
struct imx_tve *tve = con_to_tve(connector);
return &tve->encoder;
}
static void imx_tve_encoder_dpms(struct drm_encoder *encoder, int mode)
{
struct imx_tve *tve = enc_to_tve(encoder);
int ret;
ret = regmap_update_bits(tve->regmap, TVE_COM_CONF_REG,
TVE_TV_OUT_MODE_MASK, TVE_TV_OUT_DISABLE);
if (ret < 0)
dev_err(tve->dev, "failed to disable TVOUT: %d\n", ret);
}
static bool imx_tve_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
return true;
}
static void imx_tve_encoder_prepare(struct drm_encoder *encoder)
{
struct imx_tve *tve = enc_to_tve(encoder);
tve_disable(tve);
switch (tve->mode) {
case TVE_MODE_VGA:
imx_drm_crtc_panel_format_pins(encoder->crtc,
DRM_MODE_ENCODER_DAC, IPU_PIX_FMT_GBR24,
tve->hsync_pin, tve->vsync_pin);
break;
case TVE_MODE_TVOUT:
imx_drm_crtc_panel_format(encoder->crtc, DRM_MODE_ENCODER_TVDAC,
V4L2_PIX_FMT_YUV444);
break;
}
}
static void imx_tve_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct imx_tve *tve = enc_to_tve(encoder);
unsigned long rounded_rate;
unsigned long rate;
int div = 1;
int ret;
/*
* FIXME
* we should try 4k * mode->clock first,
* and enable 4x oversampling for lower resolutions
*/
rate = 2000UL * mode->clock;
clk_set_rate(tve->clk, rate);
rounded_rate = clk_get_rate(tve->clk);
if (rounded_rate >= rate)
div = 2;
clk_set_rate(tve->di_clk, rounded_rate / div);
ret = clk_set_parent(tve->di_sel_clk, tve->di_clk);
if (ret < 0) {
dev_err(tve->dev, "failed to set di_sel parent to tve_di: %d\n",
ret);
}
if (tve->mode == TVE_MODE_VGA)
tve_setup_vga(tve);
else
tve_setup_tvout(tve);
}
static void imx_tve_encoder_commit(struct drm_encoder *encoder)
{
struct imx_tve *tve = enc_to_tve(encoder);
tve_enable(tve);
}
static void imx_tve_encoder_disable(struct drm_encoder *encoder)
{
struct imx_tve *tve = enc_to_tve(encoder);
tve_disable(tve);
}
static void imx_tve_encoder_destroy(struct drm_encoder *encoder)
{
/* do not free here */
}
static struct drm_connector_funcs imx_tve_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = imx_tve_connector_detect,
.destroy = imx_tve_connector_destroy,
};
static struct drm_connector_helper_funcs imx_tve_connector_helper_funcs = {
.get_modes = imx_tve_connector_get_modes,
.best_encoder = imx_tve_connector_best_encoder,
.mode_valid = imx_tve_connector_mode_valid,
};
static struct drm_encoder_funcs imx_tve_encoder_funcs = {
.destroy = imx_tve_encoder_destroy,
};
static struct drm_encoder_helper_funcs imx_tve_encoder_helper_funcs = {
.dpms = imx_tve_encoder_dpms,
.mode_fixup = imx_tve_encoder_mode_fixup,
.prepare = imx_tve_encoder_prepare,
.mode_set = imx_tve_encoder_mode_set,
.commit = imx_tve_encoder_commit,
.disable = imx_tve_encoder_disable,
};
static irqreturn_t imx_tve_irq_handler(int irq, void *data)
{
struct imx_tve *tve = data;
unsigned int val;
regmap_read(tve->regmap, TVE_STAT_REG, &val);
/* clear interrupt status register */
regmap_write(tve->regmap, TVE_STAT_REG, 0xffffffff);
return IRQ_HANDLED;
}
static unsigned long clk_tve_di_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct imx_tve *tve = container_of(hw, struct imx_tve, clk_hw_di);
unsigned int val;
int ret;
ret = regmap_read(tve->regmap, TVE_COM_CONF_REG, &val);
if (ret < 0)
return 0;
switch (val & TVE_DAC_SAMP_RATE_MASK) {
case TVE_DAC_DIV4_RATE:
return parent_rate / 4;
case TVE_DAC_DIV2_RATE:
return parent_rate / 2;
case TVE_DAC_FULL_RATE:
default:
return parent_rate;
}
return 0;
}
static long clk_tve_di_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
unsigned long div;
div = *prate / rate;
if (div >= 4)
return *prate / 4;
else if (div >= 2)
return *prate / 2;
else
return *prate;
}
static int clk_tve_di_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct imx_tve *tve = container_of(hw, struct imx_tve, clk_hw_di);
unsigned long div;
u32 val;
int ret;
div = parent_rate / rate;
if (div >= 4)
val = TVE_DAC_DIV4_RATE;
else if (div >= 2)
val = TVE_DAC_DIV2_RATE;
else
val = TVE_DAC_FULL_RATE;
ret = regmap_update_bits(tve->regmap, TVE_COM_CONF_REG, TVE_DAC_SAMP_RATE_MASK, val);
if (ret < 0) {
dev_err(tve->dev, "failed to set divider: %d\n", ret);
return ret;
}
return 0;
}
static struct clk_ops clk_tve_di_ops = {
.round_rate = clk_tve_di_round_rate,
.set_rate = clk_tve_di_set_rate,
.recalc_rate = clk_tve_di_recalc_rate,
};
static int tve_clk_init(struct imx_tve *tve, void __iomem *base)
{
const char *tve_di_parent[1];
struct clk_init_data init = {
.name = "tve_di",
.ops = &clk_tve_di_ops,
.num_parents = 1,
.flags = 0,
};
tve_di_parent[0] = __clk_get_name(tve->clk);
init.parent_names = (const char **)&tve_di_parent;
tve->clk_hw_di.init = &init;
tve->di_clk = clk_register(tve->dev, &tve->clk_hw_di);
if (IS_ERR(tve->di_clk)) {
dev_err(tve->dev, "failed to register TVE output clock: %ld\n",
PTR_ERR(tve->di_clk));
return PTR_ERR(tve->di_clk);
}
return 0;
}
static int imx_tve_register(struct imx_tve *tve)
{
int ret;
tve->connector.funcs = &imx_tve_connector_funcs;
tve->encoder.funcs = &imx_tve_encoder_funcs;
tve->encoder.encoder_type = DRM_MODE_ENCODER_NONE;
tve->connector.connector_type = DRM_MODE_CONNECTOR_VGA;
drm_encoder_helper_add(&tve->encoder, &imx_tve_encoder_helper_funcs);
ret = imx_drm_add_encoder(&tve->encoder, &tve->imx_drm_encoder,
THIS_MODULE);
if (ret) {
dev_err(tve->dev, "adding encoder failed with %d\n", ret);
return ret;
}
drm_connector_helper_add(&tve->connector,
&imx_tve_connector_helper_funcs);
ret = imx_drm_add_connector(&tve->connector,
&tve->imx_drm_connector, THIS_MODULE);
if (ret) {
imx_drm_remove_encoder(tve->imx_drm_encoder);
dev_err(tve->dev, "adding connector failed with %d\n", ret);
return ret;
}
drm_mode_connector_attach_encoder(&tve->connector, &tve->encoder);
return 0;
}
static bool imx_tve_readable_reg(struct device *dev, unsigned int reg)
{
return (reg % 4 == 0) && (reg <= 0xdc);
}
static struct regmap_config tve_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.readable_reg = imx_tve_readable_reg,
.lock = tve_lock,
.unlock = tve_unlock,
.max_register = 0xdc,
};
static const char *imx_tve_modes[] = {
[TVE_MODE_TVOUT] = "tvout",
[TVE_MODE_VGA] = "vga",
};
const int of_get_tve_mode(struct device_node *np)
{
const char *bm;
int ret, i;
ret = of_property_read_string(np, "fsl,tve-mode", &bm);
if (ret < 0)
return ret;
for (i = 0; i < ARRAY_SIZE(imx_tve_modes); i++)
if (!strcasecmp(bm, imx_tve_modes[i]))
return i;
return -EINVAL;
}
static int imx_tve_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *ddc_node;
struct imx_tve *tve;
struct resource *res;
void __iomem *base;
unsigned int val;
int irq;
int ret;
tve = devm_kzalloc(&pdev->dev, sizeof(*tve), GFP_KERNEL);
if (!tve)
return -ENOMEM;
tve->dev = &pdev->dev;
spin_lock_init(&tve->lock);
spin_lock_init(&tve->enable_lock);
ddc_node = of_parse_phandle(np, "ddc", 0);
if (ddc_node) {
tve->ddc = of_find_i2c_adapter_by_node(ddc_node);
of_node_put(ddc_node);
}
tve->mode = of_get_tve_mode(np);
if (tve->mode != TVE_MODE_VGA) {
dev_err(&pdev->dev, "only VGA mode supported, currently\n");
return -EINVAL;
}
if (tve->mode == TVE_MODE_VGA) {
struct pinctrl *pinctrl;
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
if (IS_ERR(pinctrl)) {
ret = PTR_ERR(pinctrl);
dev_warn(&pdev->dev, "failed to setup pinctrl: %d", ret);
return ret;
}
ret = of_property_read_u32(np, "fsl,hsync-pin", &tve->hsync_pin);
if (ret < 0) {
dev_err(&pdev->dev, "failed to get vsync pin\n");
return ret;
}
ret |= of_property_read_u32(np, "fsl,vsync-pin", &tve->vsync_pin);
if (ret < 0) {
dev_err(&pdev->dev, "failed to get vsync pin\n");
return ret;
}
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "failed to get memory region\n");
return -ENOENT;
}
base = devm_request_and_ioremap(&pdev->dev, res);
if (!base) {
dev_err(&pdev->dev, "failed to remap memory region\n");
return -ENOENT;
}
tve_regmap_config.lock_arg = tve;
tve->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "tve", base,
&tve_regmap_config);
if (IS_ERR(tve->regmap)) {
dev_err(&pdev->dev, "failed to init regmap: %ld\n",
PTR_ERR(tve->regmap));
return PTR_ERR(tve->regmap);
}
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "failed to get irq\n");
return irq;
}
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
imx_tve_irq_handler, IRQF_ONESHOT,
"imx-tve", tve);
if (ret < 0) {
dev_err(&pdev->dev, "failed to request irq: %d\n", ret);
return ret;
}
tve->dac_reg = devm_regulator_get(&pdev->dev, "dac");
if (!IS_ERR(tve->dac_reg)) {
regulator_set_voltage(tve->dac_reg, 2750000, 2750000);
ret = regulator_enable(tve->dac_reg);
if (ret)
return ret;
}
tve->clk = devm_clk_get(&pdev->dev, "tve");
if (IS_ERR(tve->clk)) {
dev_err(&pdev->dev, "failed to get high speed tve clock: %ld\n",
PTR_ERR(tve->clk));
return PTR_ERR(tve->clk);
}
/* this is the IPU DI clock input selector, can be parented to tve_di */
tve->di_sel_clk = devm_clk_get(&pdev->dev, "di_sel");
if (IS_ERR(tve->di_sel_clk)) {
dev_err(&pdev->dev, "failed to get ipu di mux clock: %ld\n",
PTR_ERR(tve->di_sel_clk));
return PTR_ERR(tve->di_sel_clk);
}
ret = tve_clk_init(tve, base);
if (ret < 0)
return ret;
ret = regmap_read(tve->regmap, TVE_COM_CONF_REG, &val);
if (ret < 0) {
dev_err(&pdev->dev, "failed to read configuration register: %d\n", ret);
return ret;
}
if (val != 0x00100000) {
dev_err(&pdev->dev, "configuration register default value indicates this is not a TVEv2\n");
return -ENODEV;
};
/* disable cable detection for VGA mode */
ret = regmap_write(tve->regmap, TVE_CD_CONT_REG, 0);
ret = imx_tve_register(tve);
if (ret)
return ret;
ret = imx_drm_encoder_add_possible_crtcs(tve->imx_drm_encoder, np);
platform_set_drvdata(pdev, tve);
return 0;
}
static int imx_tve_remove(struct platform_device *pdev)
{
struct imx_tve *tve = platform_get_drvdata(pdev);
struct drm_connector *connector = &tve->connector;
struct drm_encoder *encoder = &tve->encoder;
drm_mode_connector_detach_encoder(connector, encoder);
imx_drm_remove_connector(tve->imx_drm_connector);
imx_drm_remove_encoder(tve->imx_drm_encoder);
if (!IS_ERR(tve->dac_reg))
regulator_disable(tve->dac_reg);
return 0;
}
static const struct of_device_id imx_tve_dt_ids[] = {
{ .compatible = "fsl,imx53-tve", },
{ /* sentinel */ }
};
static struct platform_driver imx_tve_driver = {
.probe = imx_tve_probe,
.remove = imx_tve_remove,
.driver = {
.of_match_table = imx_tve_dt_ids,
.name = "imx-tve",
.owner = THIS_MODULE,
},
};
module_platform_driver(imx_tve_driver);
MODULE_DESCRIPTION("i.MX Television Encoder driver");
MODULE_AUTHOR("Philipp Zabel, Pengutronix");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,3 @@
obj-$(CONFIG_DRM_IMX_IPUV3_CORE) += imx-ipu-v3.o
imx-ipu-v3-objs := ipu-common.o ipu-dc.o ipu-di.o ipu-dp.o ipu-dmfc.o

View File

@@ -0,0 +1,322 @@
/*
* Copyright 2005-2009 Freescale Semiconductor, Inc.
*
* The code contained herein is licensed under the GNU Lesser General
* Public License. You may obtain a copy of the GNU Lesser General
* Public License Version 2.1 or later at the following locations:
*
* http://www.opensource.org/licenses/lgpl-license.html
* http://www.gnu.org/copyleft/lgpl.html
*/
#ifndef __DRM_IPU_H__
#define __DRM_IPU_H__
#include <linux/types.h>
#include <linux/videodev2.h>
#include <linux/bitmap.h>
#include <linux/fb.h>
struct ipu_soc;
enum ipuv3_type {
IPUV3EX,
IPUV3M,
IPUV3H,
};
/*
* Bitfield of Display Interface signal polarities.
*/
struct ipu_di_signal_cfg {
unsigned datamask_en:1;
unsigned interlaced:1;
unsigned odd_field_first:1;
unsigned clksel_en:1;
unsigned clkidle_en:1;
unsigned data_pol:1; /* true = inverted */
unsigned clk_pol:1; /* true = rising edge */
unsigned enable_pol:1;
unsigned Hsync_pol:1; /* true = active high */
unsigned Vsync_pol:1;
u16 width;
u16 height;
u32 pixel_fmt;
u16 h_start_width;
u16 h_sync_width;
u16 h_end_width;
u16 v_start_width;
u16 v_sync_width;
u16 v_end_width;
u32 v_to_h_sync;
unsigned long pixelclock;
#define IPU_DI_CLKMODE_SYNC (1 << 0)
#define IPU_DI_CLKMODE_EXT (1 << 1)
unsigned long clkflags;
u8 hsync_pin;
u8 vsync_pin;
};
enum ipu_color_space {
IPUV3_COLORSPACE_RGB,
IPUV3_COLORSPACE_YUV,
IPUV3_COLORSPACE_UNKNOWN,
};
struct ipuv3_channel;
enum ipu_channel_irq {
IPU_IRQ_EOF = 0,
IPU_IRQ_NFACK = 64,
IPU_IRQ_NFB4EOF = 128,
IPU_IRQ_EOS = 192,
};
int ipu_idmac_channel_irq(struct ipu_soc *ipu, struct ipuv3_channel *channel,
enum ipu_channel_irq irq);
#define IPU_IRQ_DP_SF_START (448 + 2)
#define IPU_IRQ_DP_SF_END (448 + 3)
#define IPU_IRQ_BG_SF_END IPU_IRQ_DP_SF_END,
#define IPU_IRQ_DC_FC_0 (448 + 8)
#define IPU_IRQ_DC_FC_1 (448 + 9)
#define IPU_IRQ_DC_FC_2 (448 + 10)
#define IPU_IRQ_DC_FC_3 (448 + 11)
#define IPU_IRQ_DC_FC_4 (448 + 12)
#define IPU_IRQ_DC_FC_6 (448 + 13)
#define IPU_IRQ_VSYNC_PRE_0 (448 + 14)
#define IPU_IRQ_VSYNC_PRE_1 (448 + 15)
/*
* IPU Image DMA Controller (idmac) functions
*/
struct ipuv3_channel *ipu_idmac_get(struct ipu_soc *ipu, unsigned channel);
void ipu_idmac_put(struct ipuv3_channel *);
int ipu_idmac_enable_channel(struct ipuv3_channel *channel);
int ipu_idmac_disable_channel(struct ipuv3_channel *channel);
void ipu_idmac_set_double_buffer(struct ipuv3_channel *channel,
bool doublebuffer);
void ipu_idmac_select_buffer(struct ipuv3_channel *channel, u32 buf_num);
/*
* IPU Display Controller (dc) functions
*/
struct ipu_dc;
struct ipu_di;
struct ipu_dc *ipu_dc_get(struct ipu_soc *ipu, int channel);
void ipu_dc_put(struct ipu_dc *dc);
int ipu_dc_init_sync(struct ipu_dc *dc, struct ipu_di *di, bool interlaced,
u32 pixel_fmt, u32 width);
void ipu_dc_enable_channel(struct ipu_dc *dc);
void ipu_dc_disable_channel(struct ipu_dc *dc);
/*
* IPU Display Interface (di) functions
*/
struct ipu_di *ipu_di_get(struct ipu_soc *ipu, int disp);
void ipu_di_put(struct ipu_di *);
int ipu_di_disable(struct ipu_di *);
int ipu_di_enable(struct ipu_di *);
int ipu_di_get_num(struct ipu_di *);
int ipu_di_init_sync_panel(struct ipu_di *, struct ipu_di_signal_cfg *sig);
/*
* IPU Display Multi FIFO Controller (dmfc) functions
*/
struct dmfc_channel;
int ipu_dmfc_enable_channel(struct dmfc_channel *dmfc);
void ipu_dmfc_disable_channel(struct dmfc_channel *dmfc);
int ipu_dmfc_alloc_bandwidth(struct dmfc_channel *dmfc,
unsigned long bandwidth_mbs, int burstsize);
void ipu_dmfc_free_bandwidth(struct dmfc_channel *dmfc);
int ipu_dmfc_init_channel(struct dmfc_channel *dmfc, int width);
struct dmfc_channel *ipu_dmfc_get(struct ipu_soc *ipu, int ipuv3_channel);
void ipu_dmfc_put(struct dmfc_channel *dmfc);
/*
* IPU Display Processor (dp) functions
*/
#define IPU_DP_FLOW_SYNC_BG 0
#define IPU_DP_FLOW_SYNC_FG 1
#define IPU_DP_FLOW_ASYNC0_BG 2
#define IPU_DP_FLOW_ASYNC0_FG 3
#define IPU_DP_FLOW_ASYNC1_BG 4
#define IPU_DP_FLOW_ASYNC1_FG 5
struct ipu_dp *ipu_dp_get(struct ipu_soc *ipu, unsigned int flow);
void ipu_dp_put(struct ipu_dp *);
int ipu_dp_enable_channel(struct ipu_dp *dp);
void ipu_dp_disable_channel(struct ipu_dp *dp);
int ipu_dp_setup_channel(struct ipu_dp *dp,
enum ipu_color_space in, enum ipu_color_space out);
int ipu_dp_set_window_pos(struct ipu_dp *, u16 x_pos, u16 y_pos);
int ipu_dp_set_global_alpha(struct ipu_dp *dp, bool enable, u8 alpha,
bool bg_chan);
#define IPU_CPMEM_WORD(word, ofs, size) ((((word) * 160 + (ofs)) << 8) | (size))
#define IPU_FIELD_UBO IPU_CPMEM_WORD(0, 46, 22)
#define IPU_FIELD_VBO IPU_CPMEM_WORD(0, 68, 22)
#define IPU_FIELD_IOX IPU_CPMEM_WORD(0, 90, 4)
#define IPU_FIELD_RDRW IPU_CPMEM_WORD(0, 94, 1)
#define IPU_FIELD_SO IPU_CPMEM_WORD(0, 113, 1)
#define IPU_FIELD_SLY IPU_CPMEM_WORD(1, 102, 14)
#define IPU_FIELD_SLUV IPU_CPMEM_WORD(1, 128, 14)
#define IPU_FIELD_XV IPU_CPMEM_WORD(0, 0, 10)
#define IPU_FIELD_YV IPU_CPMEM_WORD(0, 10, 9)
#define IPU_FIELD_XB IPU_CPMEM_WORD(0, 19, 13)
#define IPU_FIELD_YB IPU_CPMEM_WORD(0, 32, 12)
#define IPU_FIELD_NSB_B IPU_CPMEM_WORD(0, 44, 1)
#define IPU_FIELD_CF IPU_CPMEM_WORD(0, 45, 1)
#define IPU_FIELD_SX IPU_CPMEM_WORD(0, 46, 12)
#define IPU_FIELD_SY IPU_CPMEM_WORD(0, 58, 11)
#define IPU_FIELD_NS IPU_CPMEM_WORD(0, 69, 10)
#define IPU_FIELD_SDX IPU_CPMEM_WORD(0, 79, 7)
#define IPU_FIELD_SM IPU_CPMEM_WORD(0, 86, 10)
#define IPU_FIELD_SCC IPU_CPMEM_WORD(0, 96, 1)
#define IPU_FIELD_SCE IPU_CPMEM_WORD(0, 97, 1)
#define IPU_FIELD_SDY IPU_CPMEM_WORD(0, 98, 7)
#define IPU_FIELD_SDRX IPU_CPMEM_WORD(0, 105, 1)
#define IPU_FIELD_SDRY IPU_CPMEM_WORD(0, 106, 1)
#define IPU_FIELD_BPP IPU_CPMEM_WORD(0, 107, 3)
#define IPU_FIELD_DEC_SEL IPU_CPMEM_WORD(0, 110, 2)
#define IPU_FIELD_DIM IPU_CPMEM_WORD(0, 112, 1)
#define IPU_FIELD_BNDM IPU_CPMEM_WORD(0, 114, 3)
#define IPU_FIELD_BM IPU_CPMEM_WORD(0, 117, 2)
#define IPU_FIELD_ROT IPU_CPMEM_WORD(0, 119, 1)
#define IPU_FIELD_HF IPU_CPMEM_WORD(0, 120, 1)
#define IPU_FIELD_VF IPU_CPMEM_WORD(0, 121, 1)
#define IPU_FIELD_THE IPU_CPMEM_WORD(0, 122, 1)
#define IPU_FIELD_CAP IPU_CPMEM_WORD(0, 123, 1)
#define IPU_FIELD_CAE IPU_CPMEM_WORD(0, 124, 1)
#define IPU_FIELD_FW IPU_CPMEM_WORD(0, 125, 13)
#define IPU_FIELD_FH IPU_CPMEM_WORD(0, 138, 12)
#define IPU_FIELD_EBA0 IPU_CPMEM_WORD(1, 0, 29)
#define IPU_FIELD_EBA1 IPU_CPMEM_WORD(1, 29, 29)
#define IPU_FIELD_ILO IPU_CPMEM_WORD(1, 58, 20)
#define IPU_FIELD_NPB IPU_CPMEM_WORD(1, 78, 7)
#define IPU_FIELD_PFS IPU_CPMEM_WORD(1, 85, 4)
#define IPU_FIELD_ALU IPU_CPMEM_WORD(1, 89, 1)
#define IPU_FIELD_ALBM IPU_CPMEM_WORD(1, 90, 3)
#define IPU_FIELD_ID IPU_CPMEM_WORD(1, 93, 2)
#define IPU_FIELD_TH IPU_CPMEM_WORD(1, 95, 7)
#define IPU_FIELD_SL IPU_CPMEM_WORD(1, 102, 14)
#define IPU_FIELD_WID0 IPU_CPMEM_WORD(1, 116, 3)
#define IPU_FIELD_WID1 IPU_CPMEM_WORD(1, 119, 3)
#define IPU_FIELD_WID2 IPU_CPMEM_WORD(1, 122, 3)
#define IPU_FIELD_WID3 IPU_CPMEM_WORD(1, 125, 3)
#define IPU_FIELD_OFS0 IPU_CPMEM_WORD(1, 128, 5)
#define IPU_FIELD_OFS1 IPU_CPMEM_WORD(1, 133, 5)
#define IPU_FIELD_OFS2 IPU_CPMEM_WORD(1, 138, 5)
#define IPU_FIELD_OFS3 IPU_CPMEM_WORD(1, 143, 5)
#define IPU_FIELD_SXYS IPU_CPMEM_WORD(1, 148, 1)
#define IPU_FIELD_CRE IPU_CPMEM_WORD(1, 149, 1)
#define IPU_FIELD_DEC_SEL2 IPU_CPMEM_WORD(1, 150, 1)
struct ipu_cpmem_word {
u32 data[5];
u32 res[3];
};
struct ipu_ch_param {
struct ipu_cpmem_word word[2];
};
void ipu_ch_param_write_field(struct ipu_ch_param __iomem *base, u32 wbs, u32 v);
u32 ipu_ch_param_read_field(struct ipu_ch_param __iomem *base, u32 wbs);
struct ipu_ch_param __iomem *ipu_get_cpmem(struct ipuv3_channel *channel);
void ipu_ch_param_dump(struct ipu_ch_param __iomem *p);
static inline void ipu_ch_param_zero(struct ipu_ch_param __iomem *p)
{
int i;
void __iomem *base = p;
for (i = 0; i < sizeof(*p) / sizeof(u32); i++)
writel(0, base + i * sizeof(u32));
}
static inline void ipu_cpmem_set_buffer(struct ipu_ch_param __iomem *p,
int bufnum, dma_addr_t buf)
{
if (bufnum)
ipu_ch_param_write_field(p, IPU_FIELD_EBA1, buf >> 3);
else
ipu_ch_param_write_field(p, IPU_FIELD_EBA0, buf >> 3);
}
static inline void ipu_cpmem_set_resolution(struct ipu_ch_param __iomem *p,
int xres, int yres)
{
ipu_ch_param_write_field(p, IPU_FIELD_FW, xres - 1);
ipu_ch_param_write_field(p, IPU_FIELD_FH, yres - 1);
}
static inline void ipu_cpmem_set_stride(struct ipu_ch_param __iomem *p,
int stride)
{
ipu_ch_param_write_field(p, IPU_FIELD_SLY, stride - 1);
}
void ipu_cpmem_set_high_priority(struct ipuv3_channel *channel);
struct ipu_rgb {
struct fb_bitfield red;
struct fb_bitfield green;
struct fb_bitfield blue;
struct fb_bitfield transp;
int bits_per_pixel;
};
struct ipu_image {
struct v4l2_pix_format pix;
struct v4l2_rect rect;
dma_addr_t phys;
};
int ipu_cpmem_set_format_passthrough(struct ipu_ch_param __iomem *p,
int width);
int ipu_cpmem_set_format_rgb(struct ipu_ch_param __iomem *,
struct ipu_rgb *rgb);
static inline void ipu_cpmem_interlaced_scan(struct ipu_ch_param *p,
int stride)
{
ipu_ch_param_write_field(p, IPU_FIELD_SO, 1);
ipu_ch_param_write_field(p, IPU_FIELD_ILO, stride / 8);
ipu_ch_param_write_field(p, IPU_FIELD_SLY, (stride * 2) - 1);
};
void ipu_cpmem_set_yuv_planar(struct ipu_ch_param __iomem *p, u32 pixel_format,
int stride, int height);
void ipu_cpmem_set_yuv_interleaved(struct ipu_ch_param __iomem *p,
u32 pixel_format);
void ipu_cpmem_set_yuv_planar_full(struct ipu_ch_param __iomem *p,
u32 pixel_format, int stride, int u_offset, int v_offset);
int ipu_cpmem_set_fmt(struct ipu_ch_param __iomem *cpmem, u32 pixelformat);
int ipu_cpmem_set_image(struct ipu_ch_param __iomem *cpmem,
struct ipu_image *image);
enum ipu_color_space ipu_pixelformat_to_colorspace(u32 pixelformat);
static inline void ipu_cpmem_set_burstsize(struct ipu_ch_param __iomem *p,
int burstsize)
{
ipu_ch_param_write_field(p, IPU_FIELD_NPB, burstsize - 1);
};
struct ipu_client_platformdata {
int di;
int dc;
int dp;
int dmfc;
int dma[2];
};
#endif /* __DRM_IPU_H__ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,402 @@
/*
* Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2005-2009 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <linux/export.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/io.h>
#include "../imx-drm.h"
#include "imx-ipu-v3.h"
#include "ipu-prv.h"
#define DC_MAP_CONF_PTR(n) (0x108 + ((n) & ~0x1) * 2)
#define DC_MAP_CONF_VAL(n) (0x144 + ((n) & ~0x1) * 2)
#define DC_EVT_NF 0
#define DC_EVT_NL 1
#define DC_EVT_EOF 2
#define DC_EVT_NFIELD 3
#define DC_EVT_EOL 4
#define DC_EVT_EOFIELD 5
#define DC_EVT_NEW_ADDR 6
#define DC_EVT_NEW_CHAN 7
#define DC_EVT_NEW_DATA 8
#define DC_EVT_NEW_ADDR_W_0 0
#define DC_EVT_NEW_ADDR_W_1 1
#define DC_EVT_NEW_CHAN_W_0 2
#define DC_EVT_NEW_CHAN_W_1 3
#define DC_EVT_NEW_DATA_W_0 4
#define DC_EVT_NEW_DATA_W_1 5
#define DC_EVT_NEW_ADDR_R_0 6
#define DC_EVT_NEW_ADDR_R_1 7
#define DC_EVT_NEW_CHAN_R_0 8
#define DC_EVT_NEW_CHAN_R_1 9
#define DC_EVT_NEW_DATA_R_0 10
#define DC_EVT_NEW_DATA_R_1 11
#define DC_WR_CH_CONF 0x0
#define DC_WR_CH_ADDR 0x4
#define DC_RL_CH(evt) (8 + ((evt) & ~0x1) * 2)
#define DC_GEN 0xd4
#define DC_DISP_CONF1(disp) (0xd8 + (disp) * 4)
#define DC_DISP_CONF2(disp) (0xe8 + (disp) * 4)
#define DC_STAT 0x1c8
#define WROD(lf) (0x18 | ((lf) << 1))
#define WRG 0x01
#define WCLK 0xc9
#define SYNC_WAVE 0
#define NULL_WAVE (-1)
#define DC_GEN_SYNC_1_6_SYNC (2 << 1)
#define DC_GEN_SYNC_PRIORITY_1 (1 << 7)
#define DC_WR_CH_CONF_WORD_SIZE_8 (0 << 0)
#define DC_WR_CH_CONF_WORD_SIZE_16 (1 << 0)
#define DC_WR_CH_CONF_WORD_SIZE_24 (2 << 0)
#define DC_WR_CH_CONF_WORD_SIZE_32 (3 << 0)
#define DC_WR_CH_CONF_DISP_ID_PARALLEL(i) (((i) & 0x1) << 3)
#define DC_WR_CH_CONF_DISP_ID_SERIAL (2 << 3)
#define DC_WR_CH_CONF_DISP_ID_ASYNC (3 << 4)
#define DC_WR_CH_CONF_FIELD_MODE (1 << 9)
#define DC_WR_CH_CONF_PROG_TYPE_NORMAL (4 << 5)
#define DC_WR_CH_CONF_PROG_TYPE_MASK (7 << 5)
#define DC_WR_CH_CONF_PROG_DI_ID (1 << 2)
#define DC_WR_CH_CONF_PROG_DISP_ID(i) (((i) & 0x1) << 3)
#define IPU_DC_NUM_CHANNELS 10
struct ipu_dc_priv;
enum ipu_dc_map {
IPU_DC_MAP_RGB24,
IPU_DC_MAP_RGB565,
IPU_DC_MAP_GBR24, /* TVEv2 */
IPU_DC_MAP_BGR666,
};
struct ipu_dc {
/* The display interface number assigned to this dc channel */
unsigned int di;
void __iomem *base;
struct ipu_dc_priv *priv;
int chno;
bool in_use;
};
struct ipu_dc_priv {
void __iomem *dc_reg;
void __iomem *dc_tmpl_reg;
struct ipu_soc *ipu;
struct device *dev;
struct ipu_dc channels[IPU_DC_NUM_CHANNELS];
struct mutex mutex;
};
static void dc_link_event(struct ipu_dc *dc, int event, int addr, int priority)
{
u32 reg;
reg = readl(dc->base + DC_RL_CH(event));
reg &= ~(0xffff << (16 * (event & 0x1)));
reg |= ((addr << 8) | priority) << (16 * (event & 0x1));
writel(reg, dc->base + DC_RL_CH(event));
}
static void dc_write_tmpl(struct ipu_dc *dc, int word, u32 opcode, u32 operand,
int map, int wave, int glue, int sync, int stop)
{
struct ipu_dc_priv *priv = dc->priv;
u32 reg1, reg2;
if (opcode == WCLK) {
reg1 = (operand << 20) & 0xfff00000;
reg2 = operand >> 12 | opcode << 1 | stop << 9;
} else if (opcode == WRG) {
reg1 = sync | glue << 4 | ++wave << 11 | ((operand << 15) & 0xffff8000);
reg2 = operand >> 17 | opcode << 7 | stop << 9;
} else {
reg1 = sync | glue << 4 | ++wave << 11 | ++map << 15 | ((operand << 20) & 0xfff00000);
reg2 = operand >> 12 | opcode << 4 | stop << 9;
}
writel(reg1, priv->dc_tmpl_reg + word * 8);
writel(reg2, priv->dc_tmpl_reg + word * 8 + 4);
}
static int ipu_pixfmt_to_map(u32 fmt)
{
switch (fmt) {
case V4L2_PIX_FMT_RGB24:
return IPU_DC_MAP_RGB24;
case V4L2_PIX_FMT_RGB565:
return IPU_DC_MAP_RGB565;
case IPU_PIX_FMT_GBR24:
return IPU_DC_MAP_GBR24;
case V4L2_PIX_FMT_BGR666:
return IPU_DC_MAP_BGR666;
default:
return -EINVAL;
}
}
int ipu_dc_init_sync(struct ipu_dc *dc, struct ipu_di *di, bool interlaced,
u32 pixel_fmt, u32 width)
{
struct ipu_dc_priv *priv = dc->priv;
u32 reg = 0, map;
dc->di = ipu_di_get_num(di);
map = ipu_pixfmt_to_map(pixel_fmt);
if (map < 0) {
dev_dbg(priv->dev, "IPU_DISP: No MAP\n");
return -EINVAL;
}
if (interlaced) {
dc_link_event(dc, DC_EVT_NL, 0, 3);
dc_link_event(dc, DC_EVT_EOL, 0, 2);
dc_link_event(dc, DC_EVT_NEW_DATA, 0, 1);
/* Init template microcode */
dc_write_tmpl(dc, 0, WROD(0), 0, map, SYNC_WAVE, 0, 8, 1);
} else {
if (dc->di) {
dc_link_event(dc, DC_EVT_NL, 2, 3);
dc_link_event(dc, DC_EVT_EOL, 3, 2);
dc_link_event(dc, DC_EVT_NEW_DATA, 1, 1);
/* Init template microcode */
dc_write_tmpl(dc, 2, WROD(0), 0, map, SYNC_WAVE, 8, 5, 1);
dc_write_tmpl(dc, 3, WROD(0), 0, map, SYNC_WAVE, 4, 5, 0);
dc_write_tmpl(dc, 4, WRG, 0, map, NULL_WAVE, 0, 0, 1);
dc_write_tmpl(dc, 1, WROD(0), 0, map, SYNC_WAVE, 0, 5, 1);
} else {
dc_link_event(dc, DC_EVT_NL, 5, 3);
dc_link_event(dc, DC_EVT_EOL, 6, 2);
dc_link_event(dc, DC_EVT_NEW_DATA, 8, 1);
/* Init template microcode */
dc_write_tmpl(dc, 5, WROD(0), 0, map, SYNC_WAVE, 8, 5, 1);
dc_write_tmpl(dc, 6, WROD(0), 0, map, SYNC_WAVE, 4, 5, 0);
dc_write_tmpl(dc, 7, WRG, 0, map, NULL_WAVE, 0, 0, 1);
dc_write_tmpl(dc, 8, WROD(0), 0, map, SYNC_WAVE, 0, 5, 1);
}
}
dc_link_event(dc, DC_EVT_NF, 0, 0);
dc_link_event(dc, DC_EVT_NFIELD, 0, 0);
dc_link_event(dc, DC_EVT_EOF, 0, 0);
dc_link_event(dc, DC_EVT_EOFIELD, 0, 0);
dc_link_event(dc, DC_EVT_NEW_CHAN, 0, 0);
dc_link_event(dc, DC_EVT_NEW_ADDR, 0, 0);
reg = readl(dc->base + DC_WR_CH_CONF);
if (interlaced)
reg |= DC_WR_CH_CONF_FIELD_MODE;
else
reg &= ~DC_WR_CH_CONF_FIELD_MODE;
writel(reg, dc->base + DC_WR_CH_CONF);
writel(0x0, dc->base + DC_WR_CH_ADDR);
writel(width, priv->dc_reg + DC_DISP_CONF2(dc->di));
ipu_module_enable(priv->ipu, IPU_CONF_DC_EN);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dc_init_sync);
void ipu_dc_enable_channel(struct ipu_dc *dc)
{
int di;
u32 reg;
di = dc->di;
reg = readl(dc->base + DC_WR_CH_CONF);
reg |= DC_WR_CH_CONF_PROG_TYPE_NORMAL;
writel(reg, dc->base + DC_WR_CH_CONF);
}
EXPORT_SYMBOL_GPL(ipu_dc_enable_channel);
void ipu_dc_disable_channel(struct ipu_dc *dc)
{
struct ipu_dc_priv *priv = dc->priv;
u32 val;
int irq = 0, timeout = 50;
if (dc->chno == 1)
irq = IPU_IRQ_DC_FC_1;
else if (dc->chno == 5)
irq = IPU_IRQ_DP_SF_END;
else
return;
/* should wait for the interrupt here */
mdelay(50);
if (dc->di == 0)
val = 0x00000002;
else
val = 0x00000020;
/* Wait for DC triple buffer to empty */
while ((readl(priv->dc_reg + DC_STAT) & val) != val) {
msleep(2);
timeout -= 2;
if (timeout <= 0)
break;
}
val = readl(dc->base + DC_WR_CH_CONF);
val &= ~DC_WR_CH_CONF_PROG_TYPE_MASK;
writel(val, dc->base + DC_WR_CH_CONF);
}
EXPORT_SYMBOL_GPL(ipu_dc_disable_channel);
static void ipu_dc_map_config(struct ipu_dc_priv *priv, enum ipu_dc_map map,
int byte_num, int offset, int mask)
{
int ptr = map * 3 + byte_num;
u32 reg;
reg = readl(priv->dc_reg + DC_MAP_CONF_VAL(ptr));
reg &= ~(0xffff << (16 * (ptr & 0x1)));
reg |= ((offset << 8) | mask) << (16 * (ptr & 0x1));
writel(reg, priv->dc_reg + DC_MAP_CONF_VAL(ptr));
reg = readl(priv->dc_reg + DC_MAP_CONF_PTR(map));
reg &= ~(0x1f << ((16 * (map & 0x1)) + (5 * byte_num)));
reg |= ptr << ((16 * (map & 0x1)) + (5 * byte_num));
writel(reg, priv->dc_reg + DC_MAP_CONF_PTR(map));
}
static void ipu_dc_map_clear(struct ipu_dc_priv *priv, int map)
{
u32 reg = readl(priv->dc_reg + DC_MAP_CONF_PTR(map));
writel(reg & ~(0xffff << (16 * (map & 0x1))),
priv->dc_reg + DC_MAP_CONF_PTR(map));
}
struct ipu_dc *ipu_dc_get(struct ipu_soc *ipu, int channel)
{
struct ipu_dc_priv *priv = ipu->dc_priv;
struct ipu_dc *dc;
if (channel >= IPU_DC_NUM_CHANNELS)
return ERR_PTR(-ENODEV);
dc = &priv->channels[channel];
mutex_lock(&priv->mutex);
if (dc->in_use) {
mutex_unlock(&priv->mutex);
return ERR_PTR(-EBUSY);
}
dc->in_use = 1;
mutex_unlock(&priv->mutex);
return dc;
}
EXPORT_SYMBOL_GPL(ipu_dc_get);
void ipu_dc_put(struct ipu_dc *dc)
{
struct ipu_dc_priv *priv = dc->priv;
mutex_lock(&priv->mutex);
dc->in_use = 0;
mutex_unlock(&priv->mutex);
}
EXPORT_SYMBOL_GPL(ipu_dc_put);
int ipu_dc_init(struct ipu_soc *ipu, struct device *dev,
unsigned long base, unsigned long template_base)
{
struct ipu_dc_priv *priv;
static int channel_offsets[] = { 0, 0x1c, 0x38, 0x54, 0x58, 0x5c,
0x78, 0, 0x94, 0xb4};
int i;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
mutex_init(&priv->mutex);
priv->dev = dev;
priv->ipu = ipu;
priv->dc_reg = devm_ioremap(dev, base, PAGE_SIZE);
priv->dc_tmpl_reg = devm_ioremap(dev, template_base, PAGE_SIZE);
if (!priv->dc_reg || !priv->dc_tmpl_reg)
return -ENOMEM;
for (i = 0; i < IPU_DC_NUM_CHANNELS; i++) {
priv->channels[i].chno = i;
priv->channels[i].priv = priv;
priv->channels[i].base = priv->dc_reg + channel_offsets[i];
}
writel(DC_WR_CH_CONF_WORD_SIZE_24 | DC_WR_CH_CONF_DISP_ID_PARALLEL(1) |
DC_WR_CH_CONF_PROG_DI_ID,
priv->channels[1].base + DC_WR_CH_CONF);
writel(DC_WR_CH_CONF_WORD_SIZE_24 | DC_WR_CH_CONF_DISP_ID_PARALLEL(0),
priv->channels[5].base + DC_WR_CH_CONF);
writel(DC_GEN_SYNC_1_6_SYNC | DC_GEN_SYNC_PRIORITY_1, priv->dc_reg + DC_GEN);
ipu->dc_priv = priv;
dev_dbg(dev, "DC base: 0x%08lx template base: 0x%08lx\n",
base, template_base);
/* rgb24 */
ipu_dc_map_clear(priv, IPU_DC_MAP_RGB24);
ipu_dc_map_config(priv, IPU_DC_MAP_RGB24, 0, 7, 0xff); /* blue */
ipu_dc_map_config(priv, IPU_DC_MAP_RGB24, 1, 15, 0xff); /* green */
ipu_dc_map_config(priv, IPU_DC_MAP_RGB24, 2, 23, 0xff); /* red */
/* rgb565 */
ipu_dc_map_clear(priv, IPU_DC_MAP_RGB565);
ipu_dc_map_config(priv, IPU_DC_MAP_RGB565, 0, 4, 0xf8); /* blue */
ipu_dc_map_config(priv, IPU_DC_MAP_RGB565, 1, 10, 0xfc); /* green */
ipu_dc_map_config(priv, IPU_DC_MAP_RGB565, 2, 15, 0xf8); /* red */
/* gbr24 */
ipu_dc_map_clear(priv, IPU_DC_MAP_GBR24);
ipu_dc_map_config(priv, IPU_DC_MAP_GBR24, 2, 15, 0xff); /* green */
ipu_dc_map_config(priv, IPU_DC_MAP_GBR24, 1, 7, 0xff); /* blue */
ipu_dc_map_config(priv, IPU_DC_MAP_GBR24, 0, 23, 0xff); /* red */
/* bgr666 */
ipu_dc_map_clear(priv, IPU_DC_MAP_BGR666);
ipu_dc_map_config(priv, IPU_DC_MAP_BGR666, 0, 5, 0xfc); /* blue */
ipu_dc_map_config(priv, IPU_DC_MAP_BGR666, 1, 11, 0xfc); /* green */
ipu_dc_map_config(priv, IPU_DC_MAP_BGR666, 2, 17, 0xfc); /* red */
return 0;
}
void ipu_dc_exit(struct ipu_soc *ipu)
{
}

View File

@@ -0,0 +1,786 @@
/*
* Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2005-2009 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <linux/export.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include "imx-ipu-v3.h"
#include "ipu-prv.h"
struct ipu_di {
void __iomem *base;
int id;
u32 module;
struct clk *clk_di; /* display input clock */
struct clk *clk_ipu; /* IPU bus clock */
struct clk *clk_di_pixel; /* resulting pixel clock */
struct clk_hw clk_hw_out;
char *clk_name;
bool inuse;
unsigned long clkflags;
struct ipu_soc *ipu;
};
static DEFINE_MUTEX(di_mutex);
struct di_sync_config {
int run_count;
int run_src;
int offset_count;
int offset_src;
int repeat_count;
int cnt_clr_src;
int cnt_polarity_gen_en;
int cnt_polarity_clr_src;
int cnt_polarity_trigger_src;
int cnt_up;
int cnt_down;
};
enum di_pins {
DI_PIN11 = 0,
DI_PIN12 = 1,
DI_PIN13 = 2,
DI_PIN14 = 3,
DI_PIN15 = 4,
DI_PIN16 = 5,
DI_PIN17 = 6,
DI_PIN_CS = 7,
DI_PIN_SER_CLK = 0,
DI_PIN_SER_RS = 1,
};
enum di_sync_wave {
DI_SYNC_NONE = 0,
DI_SYNC_CLK = 1,
DI_SYNC_INT_HSYNC = 2,
DI_SYNC_HSYNC = 3,
DI_SYNC_VSYNC = 4,
DI_SYNC_DE = 6,
};
#define SYNC_WAVE 0
#define DI_GENERAL 0x0000
#define DI_BS_CLKGEN0 0x0004
#define DI_BS_CLKGEN1 0x0008
#define DI_SW_GEN0(gen) (0x000c + 4 * ((gen) - 1))
#define DI_SW_GEN1(gen) (0x0030 + 4 * ((gen) - 1))
#define DI_STP_REP(gen) (0x0148 + 4 * (((gen) - 1)/2))
#define DI_SYNC_AS_GEN 0x0054
#define DI_DW_GEN(gen) (0x0058 + 4 * (gen))
#define DI_DW_SET(gen, set) (0x0088 + 4 * ((gen) + 0xc * (set)))
#define DI_SER_CONF 0x015c
#define DI_SSC 0x0160
#define DI_POL 0x0164
#define DI_AW0 0x0168
#define DI_AW1 0x016c
#define DI_SCR_CONF 0x0170
#define DI_STAT 0x0174
#define DI_SW_GEN0_RUN_COUNT(x) ((x) << 19)
#define DI_SW_GEN0_RUN_SRC(x) ((x) << 16)
#define DI_SW_GEN0_OFFSET_COUNT(x) ((x) << 3)
#define DI_SW_GEN0_OFFSET_SRC(x) ((x) << 0)
#define DI_SW_GEN1_CNT_POL_GEN_EN(x) ((x) << 29)
#define DI_SW_GEN1_CNT_CLR_SRC(x) ((x) << 25)
#define DI_SW_GEN1_CNT_POL_TRIGGER_SRC(x) ((x) << 12)
#define DI_SW_GEN1_CNT_POL_CLR_SRC(x) ((x) << 9)
#define DI_SW_GEN1_CNT_DOWN(x) ((x) << 16)
#define DI_SW_GEN1_CNT_UP(x) (x)
#define DI_SW_GEN1_AUTO_RELOAD (0x10000000)
#define DI_DW_GEN_ACCESS_SIZE_OFFSET 24
#define DI_DW_GEN_COMPONENT_SIZE_OFFSET 16
#define DI_GEN_POLARITY_1 (1 << 0)
#define DI_GEN_POLARITY_2 (1 << 1)
#define DI_GEN_POLARITY_3 (1 << 2)
#define DI_GEN_POLARITY_4 (1 << 3)
#define DI_GEN_POLARITY_5 (1 << 4)
#define DI_GEN_POLARITY_6 (1 << 5)
#define DI_GEN_POLARITY_7 (1 << 6)
#define DI_GEN_POLARITY_8 (1 << 7)
#define DI_GEN_POLARITY_DISP_CLK (1 << 17)
#define DI_GEN_DI_CLK_EXT (1 << 20)
#define DI_GEN_DI_VSYNC_EXT (1 << 21)
#define DI_POL_DRDY_DATA_POLARITY (1 << 7)
#define DI_POL_DRDY_POLARITY_15 (1 << 4)
#define DI_VSYNC_SEL_OFFSET 13
static inline u32 ipu_di_read(struct ipu_di *di, unsigned offset)
{
return readl(di->base + offset);
}
static inline void ipu_di_write(struct ipu_di *di, u32 value, unsigned offset)
{
writel(value, di->base + offset);
}
static int ipu_di_clk_calc_div(unsigned long inrate, unsigned long outrate)
{
u64 tmp = inrate;
int div;
tmp *= 16;
do_div(tmp, outrate);
div = tmp;
if (div < 0x10)
div = 0x10;
#ifdef WTF_IS_THIS
/*
* Freescale has this in their Kernel. It is neither clear what
* it does nor why it does it
*/
if (div & 0x10)
div &= ~0x7;
else {
/* Round up divider if it gets us closer to desired pix clk */
if ((div & 0xC) == 0xC) {
div += 0x10;
div &= ~0xF;
}
}
#endif
return div;
}
static unsigned long clk_di_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct ipu_di *di = container_of(hw, struct ipu_di, clk_hw_out);
unsigned long outrate;
u32 div = ipu_di_read(di, DI_BS_CLKGEN0);
if (div < 0x10)
div = 0x10;
outrate = (parent_rate / div) * 16;
return outrate;
}
static long clk_di_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
struct ipu_di *di = container_of(hw, struct ipu_di, clk_hw_out);
unsigned long outrate;
int div;
u32 val;
div = ipu_di_clk_calc_div(*prate, rate);
outrate = (*prate / div) * 16;
val = ipu_di_read(di, DI_GENERAL);
if (!(val & DI_GEN_DI_CLK_EXT) && outrate > *prate / 2)
outrate = *prate / 2;
dev_dbg(di->ipu->dev,
"%s: inrate: %ld div: 0x%08x outrate: %ld wanted: %ld\n",
__func__, *prate, div, outrate, rate);
return outrate;
}
static int clk_di_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct ipu_di *di = container_of(hw, struct ipu_di, clk_hw_out);
int div;
u32 clkgen0;
clkgen0 = ipu_di_read(di, DI_BS_CLKGEN0) & ~0xfff;
div = ipu_di_clk_calc_div(parent_rate, rate);
ipu_di_write(di, clkgen0 | div, DI_BS_CLKGEN0);
dev_dbg(di->ipu->dev, "%s: inrate: %ld desired: %ld div: 0x%08x\n",
__func__, parent_rate, rate, div);
return 0;
}
static u8 clk_di_get_parent(struct clk_hw *hw)
{
struct ipu_di *di = container_of(hw, struct ipu_di, clk_hw_out);
u32 val;
val = ipu_di_read(di, DI_GENERAL);
return val & DI_GEN_DI_CLK_EXT ? 1 : 0;
}
static int clk_di_set_parent(struct clk_hw *hw, u8 index)
{
struct ipu_di *di = container_of(hw, struct ipu_di, clk_hw_out);
u32 val;
val = ipu_di_read(di, DI_GENERAL);
if (index)
val |= DI_GEN_DI_CLK_EXT;
else
val &= ~DI_GEN_DI_CLK_EXT;
ipu_di_write(di, val, DI_GENERAL);
return 0;
}
static struct clk_ops clk_di_ops = {
.round_rate = clk_di_round_rate,
.set_rate = clk_di_set_rate,
.recalc_rate = clk_di_recalc_rate,
.set_parent = clk_di_set_parent,
.get_parent = clk_di_get_parent,
};
static void ipu_di_data_wave_config(struct ipu_di *di,
int wave_gen,
int access_size, int component_size)
{
u32 reg;
reg = (access_size << DI_DW_GEN_ACCESS_SIZE_OFFSET) |
(component_size << DI_DW_GEN_COMPONENT_SIZE_OFFSET);
ipu_di_write(di, reg, DI_DW_GEN(wave_gen));
}
static void ipu_di_data_pin_config(struct ipu_di *di, int wave_gen, int di_pin,
int set, int up, int down)
{
u32 reg;
reg = ipu_di_read(di, DI_DW_GEN(wave_gen));
reg &= ~(0x3 << (di_pin * 2));
reg |= set << (di_pin * 2);
ipu_di_write(di, reg, DI_DW_GEN(wave_gen));
ipu_di_write(di, (down << 16) | up, DI_DW_SET(wave_gen, set));
}
static void ipu_di_sync_config(struct ipu_di *di, struct di_sync_config *config,
int start, int count)
{
u32 reg;
int i;
for (i = 0; i < count; i++) {
struct di_sync_config *c = &config[i];
int wave_gen = start + i + 1;
if ((c->run_count >= 0x1000) || (c->offset_count >= 0x1000) ||
(c->repeat_count >= 0x1000) ||
(c->cnt_up >= 0x400) ||
(c->cnt_down >= 0x400)) {
dev_err(di->ipu->dev, "DI%d counters out of range.\n",
di->id);
return;
}
reg = DI_SW_GEN0_RUN_COUNT(c->run_count) |
DI_SW_GEN0_RUN_SRC(c->run_src) |
DI_SW_GEN0_OFFSET_COUNT(c->offset_count) |
DI_SW_GEN0_OFFSET_SRC(c->offset_src);
ipu_di_write(di, reg, DI_SW_GEN0(wave_gen));
reg = DI_SW_GEN1_CNT_POL_GEN_EN(c->cnt_polarity_gen_en) |
DI_SW_GEN1_CNT_CLR_SRC(c->cnt_clr_src) |
DI_SW_GEN1_CNT_POL_TRIGGER_SRC(
c->cnt_polarity_trigger_src) |
DI_SW_GEN1_CNT_POL_CLR_SRC(c->cnt_polarity_clr_src) |
DI_SW_GEN1_CNT_DOWN(c->cnt_down) |
DI_SW_GEN1_CNT_UP(c->cnt_up);
/* Enable auto reload */
if (c->repeat_count == 0)
reg |= DI_SW_GEN1_AUTO_RELOAD;
ipu_di_write(di, reg, DI_SW_GEN1(wave_gen));
reg = ipu_di_read(di, DI_STP_REP(wave_gen));
reg &= ~(0xffff << (16 * ((wave_gen - 1) & 0x1)));
reg |= c->repeat_count << (16 * ((wave_gen - 1) & 0x1));
ipu_di_write(di, reg, DI_STP_REP(wave_gen));
}
}
static void ipu_di_sync_config_interlaced(struct ipu_di *di,
struct ipu_di_signal_cfg *sig)
{
u32 h_total = sig->width + sig->h_sync_width +
sig->h_start_width + sig->h_end_width;
u32 v_total = sig->height + sig->v_sync_width +
sig->v_start_width + sig->v_end_width;
u32 reg;
struct di_sync_config cfg[] = {
{
.run_count = h_total / 2 - 1,
.run_src = DI_SYNC_CLK,
}, {
.run_count = h_total - 11,
.run_src = DI_SYNC_CLK,
.cnt_down = 4,
}, {
.run_count = v_total * 2 - 1,
.run_src = DI_SYNC_INT_HSYNC,
.offset_count = 1,
.offset_src = DI_SYNC_INT_HSYNC,
.cnt_down = 4,
}, {
.run_count = v_total / 2 - 1,
.run_src = DI_SYNC_HSYNC,
.offset_count = sig->v_start_width,
.offset_src = DI_SYNC_HSYNC,
.repeat_count = 2,
.cnt_clr_src = DI_SYNC_VSYNC,
}, {
.run_src = DI_SYNC_HSYNC,
.repeat_count = sig->height / 2,
.cnt_clr_src = 4,
}, {
.run_count = v_total - 1,
.run_src = DI_SYNC_HSYNC,
}, {
.run_count = v_total / 2 - 1,
.run_src = DI_SYNC_HSYNC,
.offset_count = 9,
.offset_src = DI_SYNC_HSYNC,
.repeat_count = 2,
.cnt_clr_src = DI_SYNC_VSYNC,
}, {
.run_src = DI_SYNC_CLK,
.offset_count = sig->h_start_width,
.offset_src = DI_SYNC_CLK,
.repeat_count = sig->width,
.cnt_clr_src = 5,
}, {
.run_count = v_total - 1,
.run_src = DI_SYNC_INT_HSYNC,
.offset_count = v_total / 2,
.offset_src = DI_SYNC_INT_HSYNC,
.cnt_clr_src = DI_SYNC_HSYNC,
.cnt_down = 4,
}
};
ipu_di_sync_config(di, cfg, 0, ARRAY_SIZE(cfg));
/* set gentime select and tag sel */
reg = ipu_di_read(di, DI_SW_GEN1(9));
reg &= 0x1FFFFFFF;
reg |= (3 - 1) << 29 | 0x00008000;
ipu_di_write(di, reg, DI_SW_GEN1(9));
ipu_di_write(di, v_total / 2 - 1, DI_SCR_CONF);
}
static void ipu_di_sync_config_noninterlaced(struct ipu_di *di,
struct ipu_di_signal_cfg *sig, int div)
{
u32 h_total = sig->width + sig->h_sync_width + sig->h_start_width +
sig->h_end_width;
u32 v_total = sig->height + sig->v_sync_width + sig->v_start_width +
sig->v_end_width;
struct di_sync_config cfg[] = {
{
/* 1: INT_HSYNC */
.run_count = h_total - 1,
.run_src = DI_SYNC_CLK,
} , {
/* PIN2: HSYNC */
.run_count = h_total - 1,
.run_src = DI_SYNC_CLK,
.offset_count = div * sig->v_to_h_sync,
.offset_src = DI_SYNC_CLK,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_CLK,
.cnt_down = sig->h_sync_width * 2,
} , {
/* PIN3: VSYNC */
.run_count = v_total - 1,
.run_src = DI_SYNC_INT_HSYNC,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_INT_HSYNC,
.cnt_down = sig->v_sync_width * 2,
} , {
/* 4: Line Active */
.run_src = DI_SYNC_HSYNC,
.offset_count = sig->v_sync_width + sig->v_start_width,
.offset_src = DI_SYNC_HSYNC,
.repeat_count = sig->height,
.cnt_clr_src = DI_SYNC_VSYNC,
} , {
/* 5: Pixel Active, referenced by DC */
.run_src = DI_SYNC_CLK,
.offset_count = sig->h_sync_width + sig->h_start_width,
.offset_src = DI_SYNC_CLK,
.repeat_count = sig->width,
.cnt_clr_src = 5, /* Line Active */
} , {
/* unused */
} , {
/* unused */
} , {
/* unused */
} , {
/* unused */
},
};
/* can't use #7 and #8 for line active and pixel active counters */
struct di_sync_config cfg_vga[] = {
{
/* 1: INT_HSYNC */
.run_count = h_total - 1,
.run_src = DI_SYNC_CLK,
} , {
/* 2: VSYNC */
.run_count = v_total - 1,
.run_src = DI_SYNC_INT_HSYNC,
} , {
/* 3: Line Active */
.run_src = DI_SYNC_INT_HSYNC,
.offset_count = sig->v_sync_width + sig->v_start_width,
.offset_src = DI_SYNC_INT_HSYNC,
.repeat_count = sig->height,
.cnt_clr_src = 3 /* VSYNC */,
} , {
/* PIN4: HSYNC for VGA via TVEv2 on TQ MBa53 */
.run_count = h_total - 1,
.run_src = DI_SYNC_CLK,
.offset_count = div * sig->v_to_h_sync + 18, /* magic value from Freescale TVE driver */
.offset_src = DI_SYNC_CLK,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_CLK,
.cnt_down = sig->h_sync_width * 2,
} , {
/* 5: Pixel Active signal to DC */
.run_src = DI_SYNC_CLK,
.offset_count = sig->h_sync_width + sig->h_start_width,
.offset_src = DI_SYNC_CLK,
.repeat_count = sig->width,
.cnt_clr_src = 4, /* Line Active */
} , {
/* PIN6: VSYNC for VGA via TVEv2 on TQ MBa53 */
.run_count = v_total - 1,
.run_src = DI_SYNC_INT_HSYNC,
.offset_count = 1, /* magic value from Freescale TVE driver */
.offset_src = DI_SYNC_INT_HSYNC,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_INT_HSYNC,
.cnt_down = sig->v_sync_width * 2,
} , {
/* PIN4: HSYNC for VGA via TVEv2 on i.MX53-QSB */
.run_count = h_total - 1,
.run_src = DI_SYNC_CLK,
.offset_count = div * sig->v_to_h_sync + 18, /* magic value from Freescale TVE driver */
.offset_src = DI_SYNC_CLK,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_CLK,
.cnt_down = sig->h_sync_width * 2,
} , {
/* PIN6: VSYNC for VGA via TVEv2 on i.MX53-QSB */
.run_count = v_total - 1,
.run_src = DI_SYNC_INT_HSYNC,
.offset_count = 1, /* magic value from Freescale TVE driver */
.offset_src = DI_SYNC_INT_HSYNC,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_INT_HSYNC,
.cnt_down = sig->v_sync_width * 2,
} , {
/* unused */
},
};
ipu_di_write(di, v_total - 1, DI_SCR_CONF);
if (sig->hsync_pin == 2 && sig->vsync_pin == 3)
ipu_di_sync_config(di, cfg, 0, ARRAY_SIZE(cfg));
else
ipu_di_sync_config(di, cfg_vga, 0, ARRAY_SIZE(cfg_vga));
}
int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig)
{
u32 reg;
u32 di_gen, vsync_cnt;
u32 div;
u32 h_total, v_total;
int ret;
unsigned long round;
struct clk *parent;
dev_dbg(di->ipu->dev, "disp %d: panel size = %d x %d\n",
di->id, sig->width, sig->height);
if ((sig->v_sync_width == 0) || (sig->h_sync_width == 0))
return -EINVAL;
if (sig->clkflags & IPU_DI_CLKMODE_EXT)
parent = di->clk_di;
else
parent = di->clk_ipu;
ret = clk_set_parent(di->clk_di_pixel, parent);
if (ret) {
dev_err(di->ipu->dev,
"setting pixel clock to parent %s failed with %d\n",
__clk_get_name(parent), ret);
return ret;
}
if (sig->clkflags & IPU_DI_CLKMODE_SYNC)
round = clk_get_rate(parent);
else
round = clk_round_rate(di->clk_di_pixel, sig->pixelclock);
ret = clk_set_rate(di->clk_di_pixel, round);
h_total = sig->width + sig->h_sync_width + sig->h_start_width +
sig->h_end_width;
v_total = sig->height + sig->v_sync_width + sig->v_start_width +
sig->v_end_width;
mutex_lock(&di_mutex);
div = ipu_di_read(di, DI_BS_CLKGEN0) & 0xfff;
div = div / 16; /* Now divider is integer portion */
/* Setup pixel clock timing */
/* Down time is half of period */
ipu_di_write(di, (div << 16), DI_BS_CLKGEN1);
ipu_di_data_wave_config(di, SYNC_WAVE, div - 1, div - 1);
ipu_di_data_pin_config(di, SYNC_WAVE, DI_PIN15, 3, 0, div * 2);
di_gen = ipu_di_read(di, DI_GENERAL) & DI_GEN_DI_CLK_EXT;
di_gen |= DI_GEN_DI_VSYNC_EXT;
if (sig->interlaced) {
ipu_di_sync_config_interlaced(di, sig);
/* set y_sel = 1 */
di_gen |= 0x10000000;
di_gen |= DI_GEN_POLARITY_5;
di_gen |= DI_GEN_POLARITY_8;
vsync_cnt = 7;
if (sig->Hsync_pol)
di_gen |= DI_GEN_POLARITY_3;
if (sig->Vsync_pol)
di_gen |= DI_GEN_POLARITY_2;
} else {
ipu_di_sync_config_noninterlaced(di, sig, div);
vsync_cnt = 3;
if (di->id == 1)
vsync_cnt = 6;
if (sig->Hsync_pol) {
if (sig->hsync_pin == 2)
di_gen |= DI_GEN_POLARITY_2;
else if (sig->hsync_pin == 4)
di_gen |= DI_GEN_POLARITY_4;
else if (sig->hsync_pin == 7)
di_gen |= DI_GEN_POLARITY_7;
}
if (sig->Vsync_pol) {
if (sig->hsync_pin == 3)
di_gen |= DI_GEN_POLARITY_3;
else if (sig->hsync_pin == 6)
di_gen |= DI_GEN_POLARITY_6;
else if (sig->hsync_pin == 8)
di_gen |= DI_GEN_POLARITY_8;
}
}
if (!sig->clk_pol)
di_gen |= DI_GEN_POLARITY_DISP_CLK;
ipu_di_write(di, di_gen, DI_GENERAL);
ipu_di_write(di, (--vsync_cnt << DI_VSYNC_SEL_OFFSET) | 0x00000002,
DI_SYNC_AS_GEN);
reg = ipu_di_read(di, DI_POL);
reg &= ~(DI_POL_DRDY_DATA_POLARITY | DI_POL_DRDY_POLARITY_15);
if (sig->enable_pol)
reg |= DI_POL_DRDY_POLARITY_15;
if (sig->data_pol)
reg |= DI_POL_DRDY_DATA_POLARITY;
ipu_di_write(di, reg, DI_POL);
mutex_unlock(&di_mutex);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_di_init_sync_panel);
int ipu_di_enable(struct ipu_di *di)
{
clk_prepare_enable(di->clk_di_pixel);
ipu_module_enable(di->ipu, di->module);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_di_enable);
int ipu_di_disable(struct ipu_di *di)
{
ipu_module_disable(di->ipu, di->module);
clk_disable_unprepare(di->clk_di_pixel);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_di_disable);
int ipu_di_get_num(struct ipu_di *di)
{
return di->id;
}
EXPORT_SYMBOL_GPL(ipu_di_get_num);
static DEFINE_MUTEX(ipu_di_lock);
struct ipu_di *ipu_di_get(struct ipu_soc *ipu, int disp)
{
struct ipu_di *di;
if (disp > 1)
return ERR_PTR(-EINVAL);
di = ipu->di_priv[disp];
mutex_lock(&ipu_di_lock);
if (di->inuse) {
di = ERR_PTR(-EBUSY);
goto out;
}
di->inuse = true;
out:
mutex_unlock(&ipu_di_lock);
return di;
}
EXPORT_SYMBOL_GPL(ipu_di_get);
void ipu_di_put(struct ipu_di *di)
{
mutex_lock(&ipu_di_lock);
di->inuse = false;
mutex_unlock(&ipu_di_lock);
}
EXPORT_SYMBOL_GPL(ipu_di_put);
int ipu_di_init(struct ipu_soc *ipu, struct device *dev, int id,
unsigned long base,
u32 module, struct clk *clk_ipu)
{
struct ipu_di *di;
int ret;
const char *di_parent[2];
struct clk_init_data init = {
.ops = &clk_di_ops,
.num_parents = 2,
.flags = 0,
};
if (id > 1)
return -ENODEV;
di = devm_kzalloc(dev, sizeof(*di), GFP_KERNEL);
if (!di)
return -ENOMEM;
ipu->di_priv[id] = di;
di->clk_di = devm_clk_get(dev, id ? "di1" : "di0");
if (IS_ERR(di->clk_di))
return PTR_ERR(di->clk_di);
di->module = module;
di->id = id;
di->clk_ipu = clk_ipu;
di->base = devm_ioremap(dev, base, PAGE_SIZE);
if (!di->base)
return -ENOMEM;
di_parent[0] = __clk_get_name(di->clk_ipu);
di_parent[1] = __clk_get_name(di->clk_di);
ipu_di_write(di, 0x10, DI_BS_CLKGEN0);
init.parent_names = (const char **)&di_parent;
di->clk_name = kasprintf(GFP_KERNEL, "%s_di%d_pixel",
dev_name(dev), id);
if (!di->clk_name)
return -ENOMEM;
init.name = di->clk_name;
di->clk_hw_out.init = &init;
di->clk_di_pixel = clk_register(dev, &di->clk_hw_out);
if (IS_ERR(di->clk_di_pixel)) {
ret = PTR_ERR(di->clk_di_pixel);
goto failed_clk_register;
}
dev_dbg(dev, "DI%d base: 0x%08lx remapped to %p\n",
id, base, di->base);
di->inuse = false;
di->ipu = ipu;
return 0;
failed_clk_register:
kfree(di->clk_name);
return ret;
}
void ipu_di_exit(struct ipu_soc *ipu, int id)
{
struct ipu_di *di = ipu->di_priv[id];
clk_unregister(di->clk_di_pixel);
kfree(di->clk_name);
}

View File

@@ -0,0 +1,408 @@
/*
* Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2005-2009 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <linux/export.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/io.h>
#include "imx-ipu-v3.h"
#include "ipu-prv.h"
#define DMFC_RD_CHAN 0x0000
#define DMFC_WR_CHAN 0x0004
#define DMFC_WR_CHAN_DEF 0x0008
#define DMFC_DP_CHAN 0x000c
#define DMFC_DP_CHAN_DEF 0x0010
#define DMFC_GENERAL1 0x0014
#define DMFC_GENERAL2 0x0018
#define DMFC_IC_CTRL 0x001c
#define DMFC_STAT 0x0020
#define DMFC_WR_CHAN_1_28 0
#define DMFC_WR_CHAN_2_41 8
#define DMFC_WR_CHAN_1C_42 16
#define DMFC_WR_CHAN_2C_43 24
#define DMFC_DP_CHAN_5B_23 0
#define DMFC_DP_CHAN_5F_27 8
#define DMFC_DP_CHAN_6B_24 16
#define DMFC_DP_CHAN_6F_29 24
#define DMFC_FIFO_SIZE_64 (3 << 3)
#define DMFC_FIFO_SIZE_128 (2 << 3)
#define DMFC_FIFO_SIZE_256 (1 << 3)
#define DMFC_FIFO_SIZE_512 (0 << 3)
#define DMFC_SEGMENT(x) ((x & 0x7) << 0)
#define DMFC_BURSTSIZE_128 (0 << 6)
#define DMFC_BURSTSIZE_64 (1 << 6)
#define DMFC_BURSTSIZE_32 (2 << 6)
#define DMFC_BURSTSIZE_16 (3 << 6)
struct dmfc_channel_data {
int ipu_channel;
unsigned long channel_reg;
unsigned long shift;
unsigned eot_shift;
unsigned max_fifo_lines;
};
static const struct dmfc_channel_data dmfcdata[] = {
{
.ipu_channel = 23,
.channel_reg = DMFC_DP_CHAN,
.shift = DMFC_DP_CHAN_5B_23,
.eot_shift = 20,
.max_fifo_lines = 3,
}, {
.ipu_channel = 24,
.channel_reg = DMFC_DP_CHAN,
.shift = DMFC_DP_CHAN_6B_24,
.eot_shift = 22,
.max_fifo_lines = 1,
}, {
.ipu_channel = 27,
.channel_reg = DMFC_DP_CHAN,
.shift = DMFC_DP_CHAN_5F_27,
.eot_shift = 21,
.max_fifo_lines = 2,
}, {
.ipu_channel = 28,
.channel_reg = DMFC_WR_CHAN,
.shift = DMFC_WR_CHAN_1_28,
.eot_shift = 16,
.max_fifo_lines = 2,
}, {
.ipu_channel = 29,
.channel_reg = DMFC_DP_CHAN,
.shift = DMFC_DP_CHAN_6F_29,
.eot_shift = 23,
.max_fifo_lines = 1,
},
};
#define DMFC_NUM_CHANNELS ARRAY_SIZE(dmfcdata)
struct ipu_dmfc_priv;
struct dmfc_channel {
unsigned slots;
unsigned slotmask;
unsigned segment;
int burstsize;
struct ipu_soc *ipu;
struct ipu_dmfc_priv *priv;
const struct dmfc_channel_data *data;
};
struct ipu_dmfc_priv {
struct ipu_soc *ipu;
struct device *dev;
struct dmfc_channel channels[DMFC_NUM_CHANNELS];
struct mutex mutex;
unsigned long bandwidth_per_slot;
void __iomem *base;
int use_count;
};
int ipu_dmfc_enable_channel(struct dmfc_channel *dmfc)
{
struct ipu_dmfc_priv *priv = dmfc->priv;
mutex_lock(&priv->mutex);
if (!priv->use_count)
ipu_module_enable(priv->ipu, IPU_CONF_DMFC_EN);
priv->use_count++;
mutex_unlock(&priv->mutex);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dmfc_enable_channel);
void ipu_dmfc_disable_channel(struct dmfc_channel *dmfc)
{
struct ipu_dmfc_priv *priv = dmfc->priv;
mutex_lock(&priv->mutex);
priv->use_count--;
if (!priv->use_count)
ipu_module_disable(priv->ipu, IPU_CONF_DMFC_EN);
if (priv->use_count < 0)
priv->use_count = 0;
mutex_unlock(&priv->mutex);
}
EXPORT_SYMBOL_GPL(ipu_dmfc_disable_channel);
static int ipu_dmfc_setup_channel(struct dmfc_channel *dmfc, int slots,
int segment, int burstsize)
{
struct ipu_dmfc_priv *priv = dmfc->priv;
u32 val, field;
dev_dbg(priv->dev,
"dmfc: using %d slots starting from segment %d for IPU channel %d\n",
slots, segment, dmfc->data->ipu_channel);
if (!dmfc)
return -EINVAL;
switch (slots) {
case 1:
field = DMFC_FIFO_SIZE_64;
break;
case 2:
field = DMFC_FIFO_SIZE_128;
break;
case 4:
field = DMFC_FIFO_SIZE_256;
break;
case 8:
field = DMFC_FIFO_SIZE_512;
break;
default:
return -EINVAL;
}
switch (burstsize) {
case 16:
field |= DMFC_BURSTSIZE_16;
break;
case 32:
field |= DMFC_BURSTSIZE_32;
break;
case 64:
field |= DMFC_BURSTSIZE_64;
break;
case 128:
field |= DMFC_BURSTSIZE_128;
break;
}
field |= DMFC_SEGMENT(segment);
val = readl(priv->base + dmfc->data->channel_reg);
val &= ~(0xff << dmfc->data->shift);
val |= field << dmfc->data->shift;
writel(val, priv->base + dmfc->data->channel_reg);
dmfc->slots = slots;
dmfc->segment = segment;
dmfc->burstsize = burstsize;
dmfc->slotmask = ((1 << slots) - 1) << segment;
return 0;
}
static int dmfc_bandwidth_to_slots(struct ipu_dmfc_priv *priv,
unsigned long bandwidth)
{
int slots = 1;
while (slots * priv->bandwidth_per_slot < bandwidth)
slots *= 2;
return slots;
}
static int dmfc_find_slots(struct ipu_dmfc_priv *priv, int slots)
{
unsigned slotmask_need, slotmask_used = 0;
int i, segment = 0;
slotmask_need = (1 << slots) - 1;
for (i = 0; i < DMFC_NUM_CHANNELS; i++)
slotmask_used |= priv->channels[i].slotmask;
while (slotmask_need <= 0xff) {
if (!(slotmask_used & slotmask_need))
return segment;
slotmask_need <<= 1;
segment++;
}
return -EBUSY;
}
void ipu_dmfc_free_bandwidth(struct dmfc_channel *dmfc)
{
struct ipu_dmfc_priv *priv = dmfc->priv;
int i;
dev_dbg(priv->dev, "dmfc: freeing %d slots starting from segment %d\n",
dmfc->slots, dmfc->segment);
mutex_lock(&priv->mutex);
if (!dmfc->slots)
goto out;
dmfc->slotmask = 0;
dmfc->slots = 0;
dmfc->segment = 0;
for (i = 0; i < DMFC_NUM_CHANNELS; i++)
priv->channels[i].slotmask = 0;
for (i = 0; i < DMFC_NUM_CHANNELS; i++) {
if (priv->channels[i].slots > 0) {
priv->channels[i].segment =
dmfc_find_slots(priv, priv->channels[i].slots);
priv->channels[i].slotmask =
((1 << priv->channels[i].slots) - 1) <<
priv->channels[i].segment;
}
}
for (i = 0; i < DMFC_NUM_CHANNELS; i++) {
if (priv->channels[i].slots > 0)
ipu_dmfc_setup_channel(&priv->channels[i],
priv->channels[i].slots,
priv->channels[i].segment,
priv->channels[i].burstsize);
}
out:
mutex_unlock(&priv->mutex);
}
EXPORT_SYMBOL_GPL(ipu_dmfc_free_bandwidth);
int ipu_dmfc_alloc_bandwidth(struct dmfc_channel *dmfc,
unsigned long bandwidth_pixel_per_second, int burstsize)
{
struct ipu_dmfc_priv *priv = dmfc->priv;
int slots = dmfc_bandwidth_to_slots(priv, bandwidth_pixel_per_second);
int segment = 0, ret = 0;
dev_dbg(priv->dev, "dmfc: trying to allocate %ldMpixel/s for IPU channel %d\n",
bandwidth_pixel_per_second / 1000000,
dmfc->data->ipu_channel);
ipu_dmfc_free_bandwidth(dmfc);
mutex_lock(&priv->mutex);
if (slots > 8) {
ret = -EBUSY;
goto out;
}
segment = dmfc_find_slots(priv, slots);
if (segment < 0) {
ret = -EBUSY;
goto out;
}
ipu_dmfc_setup_channel(dmfc, slots, segment, burstsize);
out:
mutex_unlock(&priv->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(ipu_dmfc_alloc_bandwidth);
int ipu_dmfc_init_channel(struct dmfc_channel *dmfc, int width)
{
struct ipu_dmfc_priv *priv = dmfc->priv;
u32 dmfc_gen1;
dmfc_gen1 = readl(priv->base + DMFC_GENERAL1);
if ((dmfc->slots * 64 * 4) / width > dmfc->data->max_fifo_lines)
dmfc_gen1 |= 1 << dmfc->data->eot_shift;
else
dmfc_gen1 &= ~(1 << dmfc->data->eot_shift);
writel(dmfc_gen1, priv->base + DMFC_GENERAL1);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dmfc_init_channel);
struct dmfc_channel *ipu_dmfc_get(struct ipu_soc *ipu, int ipu_channel)
{
struct ipu_dmfc_priv *priv = ipu->dmfc_priv;
int i;
for (i = 0; i < DMFC_NUM_CHANNELS; i++)
if (dmfcdata[i].ipu_channel == ipu_channel)
return &priv->channels[i];
return ERR_PTR(-ENODEV);
}
EXPORT_SYMBOL_GPL(ipu_dmfc_get);
void ipu_dmfc_put(struct dmfc_channel *dmfc)
{
ipu_dmfc_free_bandwidth(dmfc);
}
EXPORT_SYMBOL_GPL(ipu_dmfc_put);
int ipu_dmfc_init(struct ipu_soc *ipu, struct device *dev, unsigned long base,
struct clk *ipu_clk)
{
struct ipu_dmfc_priv *priv;
int i;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->base = devm_ioremap(dev, base, PAGE_SIZE);
if (!priv->base)
return -ENOMEM;
priv->dev = dev;
priv->ipu = ipu;
mutex_init(&priv->mutex);
ipu->dmfc_priv = priv;
for (i = 0; i < DMFC_NUM_CHANNELS; i++) {
priv->channels[i].priv = priv;
priv->channels[i].ipu = ipu;
priv->channels[i].data = &dmfcdata[i];
}
writel(0x0, priv->base + DMFC_WR_CHAN);
writel(0x0, priv->base + DMFC_DP_CHAN);
/*
* We have a total bandwidth of clkrate * 4pixel divided
* into 8 slots.
*/
priv->bandwidth_per_slot = clk_get_rate(ipu_clk) / 8;
dev_dbg(dev, "dmfc: 8 slots with %ldMpixel/s bandwidth each\n",
priv->bandwidth_per_slot / 1000000);
writel(0x202020f6, priv->base + DMFC_WR_CHAN_DEF);
writel(0x2020f6f6, priv->base + DMFC_DP_CHAN_DEF);
writel(0x00000003, priv->base + DMFC_GENERAL1);
return 0;
}
void ipu_dmfc_exit(struct ipu_soc *ipu)
{
}

View File

@@ -0,0 +1,335 @@
/*
* Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2005-2009 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/err.h>
#include "imx-ipu-v3.h"
#include "ipu-prv.h"
#define DP_SYNC 0
#define DP_ASYNC0 0x60
#define DP_ASYNC1 0xBC
#define DP_COM_CONF 0x0
#define DP_GRAPH_WIND_CTRL 0x0004
#define DP_FG_POS 0x0008
#define DP_CSC_A_0 0x0044
#define DP_CSC_A_1 0x0048
#define DP_CSC_A_2 0x004C
#define DP_CSC_A_3 0x0050
#define DP_CSC_0 0x0054
#define DP_CSC_1 0x0058
#define DP_COM_CONF_FG_EN (1 << 0)
#define DP_COM_CONF_GWSEL (1 << 1)
#define DP_COM_CONF_GWAM (1 << 2)
#define DP_COM_CONF_GWCKE (1 << 3)
#define DP_COM_CONF_CSC_DEF_MASK (3 << 8)
#define DP_COM_CONF_CSC_DEF_OFFSET 8
#define DP_COM_CONF_CSC_DEF_FG (3 << 8)
#define DP_COM_CONF_CSC_DEF_BG (2 << 8)
#define DP_COM_CONF_CSC_DEF_BOTH (1 << 8)
struct ipu_dp_priv;
struct ipu_dp {
u32 flow;
bool in_use;
bool foreground;
enum ipu_color_space in_cs;
};
struct ipu_flow {
struct ipu_dp foreground;
struct ipu_dp background;
enum ipu_color_space out_cs;
void __iomem *base;
struct ipu_dp_priv *priv;
};
struct ipu_dp_priv {
struct ipu_soc *ipu;
struct device *dev;
void __iomem *base;
struct ipu_flow flow[3];
struct mutex mutex;
int use_count;
};
static u32 ipu_dp_flow_base[] = {DP_SYNC, DP_ASYNC0, DP_ASYNC1};
static inline struct ipu_flow *to_flow(struct ipu_dp *dp)
{
if (dp->foreground)
return container_of(dp, struct ipu_flow, foreground);
else
return container_of(dp, struct ipu_flow, background);
}
int ipu_dp_set_global_alpha(struct ipu_dp *dp, bool enable,
u8 alpha, bool bg_chan)
{
struct ipu_flow *flow = to_flow(dp);
struct ipu_dp_priv *priv = flow->priv;
u32 reg;
mutex_lock(&priv->mutex);
reg = readl(flow->base + DP_COM_CONF);
if (bg_chan)
reg &= ~DP_COM_CONF_GWSEL;
else
reg |= DP_COM_CONF_GWSEL;
writel(reg, flow->base + DP_COM_CONF);
if (enable) {
reg = readl(flow->base + DP_GRAPH_WIND_CTRL) & 0x00FFFFFFL;
writel(reg | ((u32) alpha << 24),
flow->base + DP_GRAPH_WIND_CTRL);
reg = readl(flow->base + DP_COM_CONF);
writel(reg | DP_COM_CONF_GWAM, flow->base + DP_COM_CONF);
} else {
reg = readl(flow->base + DP_COM_CONF);
writel(reg & ~DP_COM_CONF_GWAM, flow->base + DP_COM_CONF);
}
ipu_srm_dp_sync_update(priv->ipu);
mutex_unlock(&priv->mutex);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dp_set_global_alpha);
int ipu_dp_set_window_pos(struct ipu_dp *dp, u16 x_pos, u16 y_pos)
{
struct ipu_flow *flow = to_flow(dp);
struct ipu_dp_priv *priv = flow->priv;
writel((x_pos << 16) | y_pos, flow->base + DP_FG_POS);
ipu_srm_dp_sync_update(priv->ipu);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dp_set_window_pos);
static void ipu_dp_csc_init(struct ipu_flow *flow,
enum ipu_color_space in,
enum ipu_color_space out,
u32 place)
{
u32 reg;
reg = readl(flow->base + DP_COM_CONF);
reg &= ~DP_COM_CONF_CSC_DEF_MASK;
if (in == out) {
writel(reg, flow->base + DP_COM_CONF);
return;
}
if (in == IPUV3_COLORSPACE_RGB && out == IPUV3_COLORSPACE_YUV) {
writel(0x099 | (0x12d << 16), flow->base + DP_CSC_A_0);
writel(0x03a | (0x3a9 << 16), flow->base + DP_CSC_A_1);
writel(0x356 | (0x100 << 16), flow->base + DP_CSC_A_2);
writel(0x100 | (0x329 << 16), flow->base + DP_CSC_A_3);
writel(0x3d6 | (0x0000 << 16) | (2 << 30),
flow->base + DP_CSC_0);
writel(0x200 | (2 << 14) | (0x200 << 16) | (2 << 30),
flow->base + DP_CSC_1);
} else {
writel(0x095 | (0x000 << 16), flow->base + DP_CSC_A_0);
writel(0x0cc | (0x095 << 16), flow->base + DP_CSC_A_1);
writel(0x3ce | (0x398 << 16), flow->base + DP_CSC_A_2);
writel(0x095 | (0x0ff << 16), flow->base + DP_CSC_A_3);
writel(0x000 | (0x3e42 << 16) | (1 << 30),
flow->base + DP_CSC_0);
writel(0x10a | (1 << 14) | (0x3dd6 << 16) | (1 << 30),
flow->base + DP_CSC_1);
}
reg |= place;
writel(reg, flow->base + DP_COM_CONF);
}
int ipu_dp_setup_channel(struct ipu_dp *dp,
enum ipu_color_space in,
enum ipu_color_space out)
{
struct ipu_flow *flow = to_flow(dp);
struct ipu_dp_priv *priv = flow->priv;
mutex_lock(&priv->mutex);
dp->in_cs = in;
if (!dp->foreground)
flow->out_cs = out;
if (flow->foreground.in_cs == flow->background.in_cs) {
/*
* foreground and background are of same colorspace, put
* colorspace converter after combining unit.
*/
ipu_dp_csc_init(flow, flow->foreground.in_cs, flow->out_cs,
DP_COM_CONF_CSC_DEF_BOTH);
} else {
if (flow->foreground.in_cs == flow->out_cs)
/*
* foreground identical to output, apply color
* conversion on background
*/
ipu_dp_csc_init(flow, flow->background.in_cs,
flow->out_cs, DP_COM_CONF_CSC_DEF_BG);
else
ipu_dp_csc_init(flow, flow->foreground.in_cs,
flow->out_cs, DP_COM_CONF_CSC_DEF_FG);
}
ipu_srm_dp_sync_update(priv->ipu);
mutex_unlock(&priv->mutex);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dp_setup_channel);
int ipu_dp_enable_channel(struct ipu_dp *dp)
{
struct ipu_flow *flow = to_flow(dp);
struct ipu_dp_priv *priv = flow->priv;
mutex_lock(&priv->mutex);
if (!priv->use_count)
ipu_module_enable(priv->ipu, IPU_CONF_DP_EN);
priv->use_count++;
if (dp->foreground) {
u32 reg;
reg = readl(flow->base + DP_COM_CONF);
reg |= DP_COM_CONF_FG_EN;
writel(reg, flow->base + DP_COM_CONF);
ipu_srm_dp_sync_update(priv->ipu);
}
mutex_unlock(&priv->mutex);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dp_enable_channel);
void ipu_dp_disable_channel(struct ipu_dp *dp)
{
struct ipu_flow *flow = to_flow(dp);
struct ipu_dp_priv *priv = flow->priv;
mutex_lock(&priv->mutex);
priv->use_count--;
if (dp->foreground) {
u32 reg, csc;
reg = readl(flow->base + DP_COM_CONF);
csc = reg & DP_COM_CONF_CSC_DEF_MASK;
if (csc == DP_COM_CONF_CSC_DEF_FG)
reg &= ~DP_COM_CONF_CSC_DEF_MASK;
reg &= ~DP_COM_CONF_FG_EN;
writel(reg, flow->base + DP_COM_CONF);
writel(0, flow->base + DP_FG_POS);
ipu_srm_dp_sync_update(priv->ipu);
}
if (!priv->use_count)
ipu_module_disable(priv->ipu, IPU_CONF_DP_EN);
if (priv->use_count < 0)
priv->use_count = 0;
mutex_unlock(&priv->mutex);
}
EXPORT_SYMBOL_GPL(ipu_dp_disable_channel);
struct ipu_dp *ipu_dp_get(struct ipu_soc *ipu, unsigned int flow)
{
struct ipu_dp_priv *priv = ipu->dp_priv;
struct ipu_dp *dp;
if (flow > 5)
return ERR_PTR(-EINVAL);
if (flow & 1)
dp = &priv->flow[flow >> 1].foreground;
else
dp = &priv->flow[flow >> 1].background;
if (dp->in_use)
return ERR_PTR(-EBUSY);
dp->in_use = true;
return dp;
}
EXPORT_SYMBOL_GPL(ipu_dp_get);
void ipu_dp_put(struct ipu_dp *dp)
{
dp->in_use = false;
}
EXPORT_SYMBOL_GPL(ipu_dp_put);
int ipu_dp_init(struct ipu_soc *ipu, struct device *dev, unsigned long base)
{
struct ipu_dp_priv *priv;
int i;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
priv->dev = dev;
priv->ipu = ipu;
ipu->dp_priv = priv;
priv->base = devm_ioremap(dev, base, PAGE_SIZE);
if (!priv->base) {
return -ENOMEM;
}
mutex_init(&priv->mutex);
for (i = 0; i < 3; i++) {
priv->flow[i].foreground.foreground = 1;
priv->flow[i].base = priv->base + ipu_dp_flow_base[i];
priv->flow[i].priv = priv;
}
return 0;
}
void ipu_dp_exit(struct ipu_soc *ipu)
{
}

View File

@@ -0,0 +1,206 @@
/*
* Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2005-2009 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#ifndef __IPU_PRV_H__
#define __IPU_PRV_H__
struct ipu_soc;
#include <linux/types.h>
#include <linux/device.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include "imx-ipu-v3.h"
#define IPUV3_CHANNEL_CSI0 0
#define IPUV3_CHANNEL_CSI1 1
#define IPUV3_CHANNEL_CSI2 2
#define IPUV3_CHANNEL_CSI3 3
#define IPUV3_CHANNEL_MEM_BG_SYNC 23
#define IPUV3_CHANNEL_MEM_FG_SYNC 27
#define IPUV3_CHANNEL_MEM_DC_SYNC 28
#define IPUV3_CHANNEL_MEM_FG_SYNC_ALPHA 31
#define IPUV3_CHANNEL_MEM_DC_ASYNC 41
#define IPUV3_CHANNEL_ROT_ENC_MEM 45
#define IPUV3_CHANNEL_ROT_VF_MEM 46
#define IPUV3_CHANNEL_ROT_PP_MEM 47
#define IPUV3_CHANNEL_ROT_ENC_MEM_OUT 48
#define IPUV3_CHANNEL_ROT_VF_MEM_OUT 49
#define IPUV3_CHANNEL_ROT_PP_MEM_OUT 50
#define IPUV3_CHANNEL_MEM_BG_SYNC_ALPHA 51
#define IPU_MCU_T_DEFAULT 8
#define IPU_CM_IDMAC_REG_OFS 0x00008000
#define IPU_CM_IC_REG_OFS 0x00020000
#define IPU_CM_IRT_REG_OFS 0x00028000
#define IPU_CM_CSI0_REG_OFS 0x00030000
#define IPU_CM_CSI1_REG_OFS 0x00038000
#define IPU_CM_SMFC_REG_OFS 0x00050000
#define IPU_CM_DC_REG_OFS 0x00058000
#define IPU_CM_DMFC_REG_OFS 0x00060000
/* Register addresses */
/* IPU Common registers */
#define IPU_CM_REG(offset) (offset)
#define IPU_CONF IPU_CM_REG(0)
#define IPU_SRM_PRI1 IPU_CM_REG(0x00a0)
#define IPU_SRM_PRI2 IPU_CM_REG(0x00a4)
#define IPU_FS_PROC_FLOW1 IPU_CM_REG(0x00a8)
#define IPU_FS_PROC_FLOW2 IPU_CM_REG(0x00ac)
#define IPU_FS_PROC_FLOW3 IPU_CM_REG(0x00b0)
#define IPU_FS_DISP_FLOW1 IPU_CM_REG(0x00b4)
#define IPU_FS_DISP_FLOW2 IPU_CM_REG(0x00b8)
#define IPU_SKIP IPU_CM_REG(0x00bc)
#define IPU_DISP_ALT_CONF IPU_CM_REG(0x00c0)
#define IPU_DISP_GEN IPU_CM_REG(0x00c4)
#define IPU_DISP_ALT1 IPU_CM_REG(0x00c8)
#define IPU_DISP_ALT2 IPU_CM_REG(0x00cc)
#define IPU_DISP_ALT3 IPU_CM_REG(0x00d0)
#define IPU_DISP_ALT4 IPU_CM_REG(0x00d4)
#define IPU_SNOOP IPU_CM_REG(0x00d8)
#define IPU_MEM_RST IPU_CM_REG(0x00dc)
#define IPU_PM IPU_CM_REG(0x00e0)
#define IPU_GPR IPU_CM_REG(0x00e4)
#define IPU_CHA_DB_MODE_SEL(ch) IPU_CM_REG(0x0150 + 4 * ((ch) / 32))
#define IPU_ALT_CHA_DB_MODE_SEL(ch) IPU_CM_REG(0x0168 + 4 * ((ch) / 32))
#define IPU_CHA_CUR_BUF(ch) IPU_CM_REG(0x023C + 4 * ((ch) / 32))
#define IPU_ALT_CUR_BUF0 IPU_CM_REG(0x0244)
#define IPU_ALT_CUR_BUF1 IPU_CM_REG(0x0248)
#define IPU_SRM_STAT IPU_CM_REG(0x024C)
#define IPU_PROC_TASK_STAT IPU_CM_REG(0x0250)
#define IPU_DISP_TASK_STAT IPU_CM_REG(0x0254)
#define IPU_CHA_BUF0_RDY(ch) IPU_CM_REG(0x0268 + 4 * ((ch) / 32))
#define IPU_CHA_BUF1_RDY(ch) IPU_CM_REG(0x0270 + 4 * ((ch) / 32))
#define IPU_ALT_CHA_BUF0_RDY(ch) IPU_CM_REG(0x0278 + 4 * ((ch) / 32))
#define IPU_ALT_CHA_BUF1_RDY(ch) IPU_CM_REG(0x0280 + 4 * ((ch) / 32))
#define IPU_INT_CTRL(n) IPU_CM_REG(0x003C + 4 * (n))
#define IPU_INT_STAT(n) IPU_CM_REG(0x0200 + 4 * (n))
#define IPU_DI0_COUNTER_RELEASE (1 << 24)
#define IPU_DI1_COUNTER_RELEASE (1 << 25)
#define IPU_IDMAC_REG(offset) (offset)
#define IDMAC_CONF IPU_IDMAC_REG(0x0000)
#define IDMAC_CHA_EN(ch) IPU_IDMAC_REG(0x0004 + 4 * ((ch) / 32))
#define IDMAC_SEP_ALPHA IPU_IDMAC_REG(0x000c)
#define IDMAC_ALT_SEP_ALPHA IPU_IDMAC_REG(0x0010)
#define IDMAC_CHA_PRI(ch) IPU_IDMAC_REG(0x0014 + 4 * ((ch) / 32))
#define IDMAC_WM_EN(ch) IPU_IDMAC_REG(0x001c + 4 * ((ch) / 32))
#define IDMAC_CH_LOCK_EN_1 IPU_IDMAC_REG(0x0024)
#define IDMAC_CH_LOCK_EN_2 IPU_IDMAC_REG(0x0028)
#define IDMAC_SUB_ADDR_0 IPU_IDMAC_REG(0x002c)
#define IDMAC_SUB_ADDR_1 IPU_IDMAC_REG(0x0030)
#define IDMAC_SUB_ADDR_2 IPU_IDMAC_REG(0x0034)
#define IDMAC_BAND_EN(ch) IPU_IDMAC_REG(0x0040 + 4 * ((ch) / 32))
#define IDMAC_CHA_BUSY(ch) IPU_IDMAC_REG(0x0100 + 4 * ((ch) / 32))
#define IPU_NUM_IRQS (32 * 5)
enum ipu_modules {
IPU_CONF_CSI0_EN = (1 << 0),
IPU_CONF_CSI1_EN = (1 << 1),
IPU_CONF_IC_EN = (1 << 2),
IPU_CONF_ROT_EN = (1 << 3),
IPU_CONF_ISP_EN = (1 << 4),
IPU_CONF_DP_EN = (1 << 5),
IPU_CONF_DI0_EN = (1 << 6),
IPU_CONF_DI1_EN = (1 << 7),
IPU_CONF_SMFC_EN = (1 << 8),
IPU_CONF_DC_EN = (1 << 9),
IPU_CONF_DMFC_EN = (1 << 10),
IPU_CONF_VDI_EN = (1 << 12),
IPU_CONF_IDMAC_DIS = (1 << 22),
IPU_CONF_IC_DMFC_SEL = (1 << 25),
IPU_CONF_IC_DMFC_SYNC = (1 << 26),
IPU_CONF_VDI_DMFC_SYNC = (1 << 27),
IPU_CONF_CSI0_DATA_SOURCE = (1 << 28),
IPU_CONF_CSI1_DATA_SOURCE = (1 << 29),
IPU_CONF_IC_INPUT = (1 << 30),
IPU_CONF_CSI_SEL = (1 << 31),
};
struct ipuv3_channel {
unsigned int num;
bool enabled;
bool busy;
struct ipu_soc *ipu;
};
struct ipu_dc_priv;
struct ipu_dmfc_priv;
struct ipu_di;
struct ipu_devtype;
struct ipu_soc {
struct device *dev;
const struct ipu_devtype *devtype;
enum ipuv3_type ipu_type;
spinlock_t lock;
struct mutex channel_lock;
void __iomem *cm_reg;
void __iomem *idmac_reg;
struct ipu_ch_param __iomem *cpmem_base;
int usecount;
struct clk *clk;
struct ipuv3_channel channel[64];
int irq_start;
int irq_sync;
int irq_err;
struct ipu_dc_priv *dc_priv;
struct ipu_dp_priv *dp_priv;
struct ipu_dmfc_priv *dmfc_priv;
struct ipu_di *di_priv[2];
};
void ipu_srm_dp_sync_update(struct ipu_soc *ipu);
int ipu_module_enable(struct ipu_soc *ipu, u32 mask);
int ipu_module_disable(struct ipu_soc *ipu, u32 mask);
int ipu_di_init(struct ipu_soc *ipu, struct device *dev, int id,
unsigned long base, u32 module, struct clk *ipu_clk);
void ipu_di_exit(struct ipu_soc *ipu, int id);
int ipu_dmfc_init(struct ipu_soc *ipu, struct device *dev, unsigned long base,
struct clk *ipu_clk);
void ipu_dmfc_exit(struct ipu_soc *ipu);
int ipu_dp_init(struct ipu_soc *ipu, struct device *dev, unsigned long base);
void ipu_dp_exit(struct ipu_soc *ipu);
int ipu_dc_init(struct ipu_soc *ipu, struct device *dev, unsigned long base,
unsigned long template_base);
void ipu_dc_exit(struct ipu_soc *ipu);
int ipu_cpmem_init(struct ipu_soc *ipu, struct device *dev, unsigned long base);
void ipu_cpmem_exit(struct ipu_soc *ipu);
#endif /* __IPU_PRV_H__ */

View File

@@ -0,0 +1,575 @@
/*
* i.MX IPUv3 Graphics driver
*
* Copyright (C) 2011 Sascha Hauer, Pengutronix
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <linux/module.h>
#include <linux/export.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <drm/drmP.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_crtc_helper.h>
#include <linux/fb.h>
#include <linux/clk.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include "ipu-v3/imx-ipu-v3.h"
#include "imx-drm.h"
#define DRIVER_DESC "i.MX IPUv3 Graphics"
struct ipu_framebuffer {
struct drm_framebuffer base;
void *virt;
dma_addr_t phys;
size_t len;
};
struct ipu_crtc {
struct drm_fb_helper fb_helper;
struct ipu_framebuffer ifb;
int num_crtcs;
struct device *dev;
struct drm_crtc base;
struct imx_drm_crtc *imx_crtc;
struct ipuv3_channel *ipu_ch;
struct ipu_dc *dc;
struct ipu_dp *dp;
struct dmfc_channel *dmfc;
struct ipu_di *di;
int enabled;
struct ipu_priv *ipu_priv;
struct drm_pending_vblank_event *page_flip_event;
struct drm_framebuffer *newfb;
int irq;
u32 interface_pix_fmt;
unsigned long di_clkflags;
int di_hsync_pin;
int di_vsync_pin;
};
#define to_ipu_crtc(x) container_of(x, struct ipu_crtc, base)
static int calc_vref(struct drm_display_mode *mode)
{
unsigned long htotal, vtotal;
htotal = mode->htotal;
vtotal = mode->vtotal;
if (!htotal || !vtotal)
return 60;
return mode->clock * 1000 / vtotal / htotal;
}
static int calc_bandwidth(struct drm_display_mode *mode, unsigned int vref)
{
return mode->hdisplay * mode->vdisplay * vref;
}
static void ipu_fb_enable(struct ipu_crtc *ipu_crtc)
{
if (ipu_crtc->enabled)
return;
ipu_di_enable(ipu_crtc->di);
ipu_dmfc_enable_channel(ipu_crtc->dmfc);
ipu_idmac_enable_channel(ipu_crtc->ipu_ch);
ipu_dc_enable_channel(ipu_crtc->dc);
if (ipu_crtc->dp)
ipu_dp_enable_channel(ipu_crtc->dp);
ipu_crtc->enabled = 1;
}
static void ipu_fb_disable(struct ipu_crtc *ipu_crtc)
{
if (!ipu_crtc->enabled)
return;
if (ipu_crtc->dp)
ipu_dp_disable_channel(ipu_crtc->dp);
ipu_dc_disable_channel(ipu_crtc->dc);
ipu_idmac_disable_channel(ipu_crtc->ipu_ch);
ipu_dmfc_disable_channel(ipu_crtc->dmfc);
ipu_di_disable(ipu_crtc->di);
ipu_crtc->enabled = 0;
}
static void ipu_crtc_dpms(struct drm_crtc *crtc, int mode)
{
struct ipu_crtc *ipu_crtc = to_ipu_crtc(crtc);
dev_dbg(ipu_crtc->dev, "%s mode: %d\n", __func__, mode);
switch (mode) {
case DRM_MODE_DPMS_ON:
ipu_fb_enable(ipu_crtc);
break;
case DRM_MODE_DPMS_STANDBY:
case DRM_MODE_DPMS_SUSPEND:
case DRM_MODE_DPMS_OFF:
ipu_fb_disable(ipu_crtc);
break;
}
}
static int ipu_page_flip(struct drm_crtc *crtc,
struct drm_framebuffer *fb,
struct drm_pending_vblank_event *event,
uint32_t page_flip_flags)
{
struct ipu_crtc *ipu_crtc = to_ipu_crtc(crtc);
int ret;
if (ipu_crtc->newfb)
return -EBUSY;
ret = imx_drm_crtc_vblank_get(ipu_crtc->imx_crtc);
if (ret) {
dev_dbg(ipu_crtc->dev, "failed to acquire vblank counter\n");
list_del(&event->base.link);
return ret;
}
ipu_crtc->newfb = fb;
ipu_crtc->page_flip_event = event;
return 0;
}
static const struct drm_crtc_funcs ipu_crtc_funcs = {
.set_config = drm_crtc_helper_set_config,
.destroy = drm_crtc_cleanup,
.page_flip = ipu_page_flip,
};
static int ipu_drm_set_base(struct drm_crtc *crtc, int x, int y)
{
struct ipu_crtc *ipu_crtc = to_ipu_crtc(crtc);
struct drm_gem_cma_object *cma_obj;
struct drm_framebuffer *fb = crtc->primary->fb;
unsigned long phys;
cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
if (!cma_obj) {
DRM_LOG_KMS("entry is null.\n");
return -EFAULT;
}
phys = cma_obj->paddr;
phys += x * (fb->bits_per_pixel >> 3);
phys += y * fb->pitches[0];
dev_dbg(ipu_crtc->dev, "%s: phys: 0x%lx\n", __func__, phys);
dev_dbg(ipu_crtc->dev, "%s: xy: %dx%d\n", __func__, x, y);
ipu_cpmem_set_stride(ipu_get_cpmem(ipu_crtc->ipu_ch), fb->pitches[0]);
ipu_cpmem_set_buffer(ipu_get_cpmem(ipu_crtc->ipu_ch),
0, phys);
return 0;
}
static int ipu_crtc_mode_set(struct drm_crtc *crtc,
struct drm_display_mode *orig_mode,
struct drm_display_mode *mode,
int x, int y,
struct drm_framebuffer *old_fb)
{
struct ipu_crtc *ipu_crtc = to_ipu_crtc(crtc);
struct drm_framebuffer *fb = ipu_crtc->base.primary->fb;
int ret;
struct ipu_di_signal_cfg sig_cfg = {};
u32 out_pixel_fmt;
struct ipu_ch_param __iomem *cpmem = ipu_get_cpmem(ipu_crtc->ipu_ch);
int bpp;
u32 v4l2_fmt;
dev_dbg(ipu_crtc->dev, "%s: mode->hdisplay: %d\n", __func__,
mode->hdisplay);
dev_dbg(ipu_crtc->dev, "%s: mode->vdisplay: %d\n", __func__,
mode->vdisplay);
ipu_ch_param_zero(cpmem);
switch (fb->pixel_format) {
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_ARGB8888:
v4l2_fmt = V4L2_PIX_FMT_RGB32;
bpp = 32;
break;
case DRM_FORMAT_RGB565:
v4l2_fmt = V4L2_PIX_FMT_RGB565;
bpp = 16;
break;
case DRM_FORMAT_RGB888:
v4l2_fmt = V4L2_PIX_FMT_RGB24;
bpp = 24;
break;
default:
dev_err(ipu_crtc->dev, "unsupported pixel format 0x%08x\n",
fb->pixel_format);
return -EINVAL;
}
out_pixel_fmt = ipu_crtc->interface_pix_fmt;
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
sig_cfg.interlaced = 1;
if (mode->flags & DRM_MODE_FLAG_PHSYNC)
sig_cfg.Hsync_pol = 1;
if (mode->flags & DRM_MODE_FLAG_PVSYNC)
sig_cfg.Vsync_pol = 1;
sig_cfg.enable_pol = 1;
sig_cfg.clk_pol = 0;
sig_cfg.width = mode->hdisplay;
sig_cfg.height = mode->vdisplay;
sig_cfg.pixel_fmt = out_pixel_fmt;
sig_cfg.h_start_width = mode->htotal - mode->hsync_end;
sig_cfg.h_sync_width = mode->hsync_end - mode->hsync_start;
sig_cfg.h_end_width = mode->hsync_start - mode->hdisplay;
sig_cfg.v_start_width = mode->vtotal - mode->vsync_end;
sig_cfg.v_sync_width = mode->vsync_end - mode->vsync_start;
sig_cfg.v_end_width = mode->vsync_start - mode->vdisplay;
sig_cfg.pixelclock = mode->clock * 1000;
sig_cfg.clkflags = ipu_crtc->di_clkflags;
sig_cfg.v_to_h_sync = 0;
sig_cfg.hsync_pin = ipu_crtc->di_hsync_pin;
sig_cfg.vsync_pin = ipu_crtc->di_vsync_pin;
if (ipu_crtc->dp) {
ret = ipu_dp_setup_channel(ipu_crtc->dp, IPUV3_COLORSPACE_RGB,
IPUV3_COLORSPACE_RGB);
if (ret) {
dev_err(ipu_crtc->dev,
"initializing display processor failed with %d\n",
ret);
return ret;
}
ipu_dp_set_global_alpha(ipu_crtc->dp, 1, 0, 1);
}
ret = ipu_dc_init_sync(ipu_crtc->dc, ipu_crtc->di, sig_cfg.interlaced,
out_pixel_fmt, mode->hdisplay);
if (ret) {
dev_err(ipu_crtc->dev,
"initializing display controller failed with %d\n",
ret);
return ret;
}
ret = ipu_di_init_sync_panel(ipu_crtc->di, &sig_cfg);
if (ret) {
dev_err(ipu_crtc->dev,
"initializing panel failed with %d\n", ret);
return ret;
}
ipu_cpmem_set_resolution(cpmem, mode->hdisplay, mode->vdisplay);
ipu_cpmem_set_fmt(cpmem, v4l2_fmt);
ipu_cpmem_set_high_priority(ipu_crtc->ipu_ch);
ret = ipu_dmfc_init_channel(ipu_crtc->dmfc, mode->hdisplay);
if (ret) {
dev_err(ipu_crtc->dev,
"initializing dmfc channel failed with %d\n",
ret);
return ret;
}
ret = ipu_dmfc_alloc_bandwidth(ipu_crtc->dmfc,
calc_bandwidth(mode, calc_vref(mode)), 64);
if (ret) {
dev_err(ipu_crtc->dev,
"allocating dmfc bandwidth failed with %d\n",
ret);
return ret;
}
ipu_drm_set_base(crtc, x, y);
return 0;
}
static void ipu_crtc_handle_pageflip(struct ipu_crtc *ipu_crtc)
{
unsigned long flags;
struct drm_device *drm = ipu_crtc->base.dev;
spin_lock_irqsave(&drm->event_lock, flags);
if (ipu_crtc->page_flip_event)
drm_send_vblank_event(drm, -1, ipu_crtc->page_flip_event);
ipu_crtc->page_flip_event = NULL;
imx_drm_crtc_vblank_put(ipu_crtc->imx_crtc);
spin_unlock_irqrestore(&drm->event_lock, flags);
}
static irqreturn_t ipu_irq_handler(int irq, void *dev_id)
{
struct ipu_crtc *ipu_crtc = dev_id;
imx_drm_handle_vblank(ipu_crtc->imx_crtc);
if (ipu_crtc->newfb) {
ipu_crtc->base.primary->fb = ipu_crtc->newfb;
ipu_crtc->newfb = NULL;
ipu_drm_set_base(&ipu_crtc->base, 0, 0);
ipu_crtc_handle_pageflip(ipu_crtc);
}
return IRQ_HANDLED;
}
static bool ipu_crtc_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
return true;
}
static void ipu_crtc_prepare(struct drm_crtc *crtc)
{
struct ipu_crtc *ipu_crtc = to_ipu_crtc(crtc);
ipu_fb_disable(ipu_crtc);
}
static void ipu_crtc_commit(struct drm_crtc *crtc)
{
struct ipu_crtc *ipu_crtc = to_ipu_crtc(crtc);
ipu_fb_enable(ipu_crtc);
}
static void ipu_crtc_load_lut(struct drm_crtc *crtc)
{
}
static struct drm_crtc_helper_funcs ipu_helper_funcs = {
.dpms = ipu_crtc_dpms,
.mode_fixup = ipu_crtc_mode_fixup,
.mode_set = ipu_crtc_mode_set,
.prepare = ipu_crtc_prepare,
.commit = ipu_crtc_commit,
.load_lut = ipu_crtc_load_lut,
};
static int ipu_enable_vblank(struct drm_crtc *crtc)
{
struct ipu_crtc *ipu_crtc = to_ipu_crtc(crtc);
enable_irq(ipu_crtc->irq);
return 0;
}
static void ipu_disable_vblank(struct drm_crtc *crtc)
{
struct ipu_crtc *ipu_crtc = to_ipu_crtc(crtc);
disable_irq(ipu_crtc->irq);
}
static int ipu_set_interface_pix_fmt(struct drm_crtc *crtc, u32 encoder_type,
u32 pixfmt, int hsync_pin, int vsync_pin)
{
struct ipu_crtc *ipu_crtc = to_ipu_crtc(crtc);
ipu_crtc->interface_pix_fmt = pixfmt;
ipu_crtc->di_hsync_pin = hsync_pin;
ipu_crtc->di_vsync_pin = vsync_pin;
switch (encoder_type) {
case DRM_MODE_ENCODER_DAC:
case DRM_MODE_ENCODER_TVDAC:
case DRM_MODE_ENCODER_LVDS:
ipu_crtc->di_clkflags = IPU_DI_CLKMODE_SYNC |
IPU_DI_CLKMODE_EXT;
break;
case DRM_MODE_ENCODER_NONE:
ipu_crtc->di_clkflags = 0;
break;
}
return 0;
}
static const struct imx_drm_crtc_helper_funcs ipu_crtc_helper_funcs = {
.enable_vblank = ipu_enable_vblank,
.disable_vblank = ipu_disable_vblank,
.set_interface_pix_fmt = ipu_set_interface_pix_fmt,
.crtc_funcs = &ipu_crtc_funcs,
.crtc_helper_funcs = &ipu_helper_funcs,
};
static void ipu_put_resources(struct ipu_crtc *ipu_crtc)
{
if (!IS_ERR_OR_NULL(ipu_crtc->ipu_ch))
ipu_idmac_put(ipu_crtc->ipu_ch);
if (!IS_ERR_OR_NULL(ipu_crtc->dmfc))
ipu_dmfc_put(ipu_crtc->dmfc);
if (!IS_ERR_OR_NULL(ipu_crtc->dp))
ipu_dp_put(ipu_crtc->dp);
if (!IS_ERR_OR_NULL(ipu_crtc->di))
ipu_di_put(ipu_crtc->di);
}
static int ipu_get_resources(struct ipu_crtc *ipu_crtc,
struct ipu_client_platformdata *pdata)
{
struct ipu_soc *ipu = dev_get_drvdata(ipu_crtc->dev->parent);
int ret;
ipu_crtc->ipu_ch = ipu_idmac_get(ipu, pdata->dma[0]);
if (IS_ERR(ipu_crtc->ipu_ch)) {
ret = PTR_ERR(ipu_crtc->ipu_ch);
goto err_out;
}
ipu_crtc->dc = ipu_dc_get(ipu, pdata->dc);
if (IS_ERR(ipu_crtc->dc)) {
ret = PTR_ERR(ipu_crtc->dc);
goto err_out;
}
ipu_crtc->dmfc = ipu_dmfc_get(ipu, pdata->dma[0]);
if (IS_ERR(ipu_crtc->dmfc)) {
ret = PTR_ERR(ipu_crtc->dmfc);
goto err_out;
}
if (pdata->dp >= 0) {
ipu_crtc->dp = ipu_dp_get(ipu, pdata->dp);
if (IS_ERR(ipu_crtc->dp)) {
ret = PTR_ERR(ipu_crtc->dp);
goto err_out;
}
}
ipu_crtc->di = ipu_di_get(ipu, pdata->di);
if (IS_ERR(ipu_crtc->di)) {
ret = PTR_ERR(ipu_crtc->di);
goto err_out;
}
return 0;
err_out:
ipu_put_resources(ipu_crtc);
return ret;
}
static int ipu_crtc_init(struct ipu_crtc *ipu_crtc,
struct ipu_client_platformdata *pdata)
{
struct ipu_soc *ipu = dev_get_drvdata(ipu_crtc->dev->parent);
int ret;
ret = ipu_get_resources(ipu_crtc, pdata);
if (ret) {
dev_err(ipu_crtc->dev, "getting resources failed with %d.\n",
ret);
return ret;
}
ret = imx_drm_add_crtc(&ipu_crtc->base,
&ipu_crtc->imx_crtc,
&ipu_crtc_helper_funcs, THIS_MODULE,
ipu_crtc->dev->parent->of_node, pdata->di);
if (ret) {
dev_err(ipu_crtc->dev, "adding crtc failed with %d.\n", ret);
goto err_put_resources;
}
ipu_crtc->irq = ipu_idmac_channel_irq(ipu, ipu_crtc->ipu_ch,
IPU_IRQ_EOF);
ret = devm_request_irq(ipu_crtc->dev, ipu_crtc->irq, ipu_irq_handler, 0,
"imx_drm", ipu_crtc);
if (ret < 0) {
dev_err(ipu_crtc->dev, "irq request failed with %d.\n", ret);
goto err_put_resources;
}
disable_irq(ipu_crtc->irq);
return 0;
err_put_resources:
ipu_put_resources(ipu_crtc);
return ret;
}
static int ipu_drm_probe(struct platform_device *pdev)
{
struct ipu_client_platformdata *pdata = pdev->dev.platform_data;
struct ipu_crtc *ipu_crtc;
int ret;
if (!pdata)
return -EINVAL;
pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
ipu_crtc = devm_kzalloc(&pdev->dev, sizeof(*ipu_crtc), GFP_KERNEL);
if (!ipu_crtc)
return -ENOMEM;
ipu_crtc->dev = &pdev->dev;
ret = ipu_crtc_init(ipu_crtc, pdata);
if (ret)
return ret;
platform_set_drvdata(pdev, ipu_crtc);
return 0;
}
static int ipu_drm_remove(struct platform_device *pdev)
{
struct ipu_crtc *ipu_crtc = platform_get_drvdata(pdev);
imx_drm_remove_crtc(ipu_crtc->imx_crtc);
ipu_put_resources(ipu_crtc);
return 0;
}
static struct platform_driver ipu_drm_driver = {
.driver = {
.name = "imx-ipuv3-crtc",
},
.probe = ipu_drm_probe,
.remove = ipu_drm_remove,
};
module_platform_driver(ipu_drm_driver);
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,283 @@
/*
* i.MX drm driver - parallel display implementation
*
* Copyright (C) 2012 Sascha Hauer, Pengutronix
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <linux/module.h>
#include <drm/drmP.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_crtc_helper.h>
#include <linux/videodev2.h>
#include <linux/pinctrl/consumer.h>
#include "imx-drm.h"
#define con_to_imxpd(x) container_of(x, struct imx_parallel_display, connector)
#define enc_to_imxpd(x) container_of(x, struct imx_parallel_display, encoder)
struct imx_parallel_display {
struct drm_connector connector;
struct imx_drm_connector *imx_drm_connector;
struct drm_encoder encoder;
struct imx_drm_encoder *imx_drm_encoder;
struct device *dev;
void *edid;
int edid_len;
u32 interface_pix_fmt;
int mode_valid;
struct drm_display_mode mode;
};
static enum drm_connector_status imx_pd_connector_detect(
struct drm_connector *connector, bool force)
{
return connector_status_connected;
}
static void imx_pd_connector_destroy(struct drm_connector *connector)
{
/* do not free here */
}
static int imx_pd_connector_get_modes(struct drm_connector *connector)
{
struct imx_parallel_display *imxpd = con_to_imxpd(connector);
struct device_node *np = imxpd->dev->of_node;
int num_modes = 0;
if (imxpd->edid) {
drm_mode_connector_update_edid_property(connector, imxpd->edid);
num_modes = drm_add_edid_modes(connector, imxpd->edid);
}
if (imxpd->mode_valid) {
struct drm_display_mode *mode = drm_mode_create(connector->dev);
drm_mode_copy(mode, &imxpd->mode);
mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
drm_mode_probed_add(connector, mode);
num_modes++;
}
if (np) {
struct drm_display_mode *mode = drm_mode_create(connector->dev);
of_get_drm_display_mode(np, &imxpd->mode, 0);
drm_mode_copy(mode, &imxpd->mode);
mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
drm_mode_probed_add(connector, mode);
num_modes++;
}
return num_modes;
}
static int imx_pd_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
return 0;
}
static struct drm_encoder *imx_pd_connector_best_encoder(
struct drm_connector *connector)
{
struct imx_parallel_display *imxpd = con_to_imxpd(connector);
return &imxpd->encoder;
}
static void imx_pd_encoder_dpms(struct drm_encoder *encoder, int mode)
{
}
static bool imx_pd_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
return true;
}
static void imx_pd_encoder_prepare(struct drm_encoder *encoder)
{
struct imx_parallel_display *imxpd = enc_to_imxpd(encoder);
imx_drm_crtc_panel_format(encoder->crtc, DRM_MODE_ENCODER_NONE,
imxpd->interface_pix_fmt);
}
static void imx_pd_encoder_commit(struct drm_encoder *encoder)
{
}
static void imx_pd_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
}
static void imx_pd_encoder_disable(struct drm_encoder *encoder)
{
}
static void imx_pd_encoder_destroy(struct drm_encoder *encoder)
{
/* do not free here */
}
static struct drm_connector_funcs imx_pd_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = imx_pd_connector_detect,
.destroy = imx_pd_connector_destroy,
};
static struct drm_connector_helper_funcs imx_pd_connector_helper_funcs = {
.get_modes = imx_pd_connector_get_modes,
.best_encoder = imx_pd_connector_best_encoder,
.mode_valid = imx_pd_connector_mode_valid,
};
static struct drm_encoder_funcs imx_pd_encoder_funcs = {
.destroy = imx_pd_encoder_destroy,
};
static struct drm_encoder_helper_funcs imx_pd_encoder_helper_funcs = {
.dpms = imx_pd_encoder_dpms,
.mode_fixup = imx_pd_encoder_mode_fixup,
.prepare = imx_pd_encoder_prepare,
.commit = imx_pd_encoder_commit,
.mode_set = imx_pd_encoder_mode_set,
.disable = imx_pd_encoder_disable,
};
static int imx_pd_register(struct imx_parallel_display *imxpd)
{
int ret;
drm_mode_connector_attach_encoder(&imxpd->connector, &imxpd->encoder);
imxpd->connector.funcs = &imx_pd_connector_funcs;
imxpd->encoder.funcs = &imx_pd_encoder_funcs;
imxpd->encoder.encoder_type = DRM_MODE_ENCODER_NONE;
imxpd->connector.connector_type = DRM_MODE_CONNECTOR_VGA;
drm_encoder_helper_add(&imxpd->encoder, &imx_pd_encoder_helper_funcs);
ret = imx_drm_add_encoder(&imxpd->encoder, &imxpd->imx_drm_encoder,
THIS_MODULE);
if (ret) {
dev_err(imxpd->dev, "adding encoder failed with %d\n", ret);
return ret;
}
drm_connector_helper_add(&imxpd->connector,
&imx_pd_connector_helper_funcs);
ret = imx_drm_add_connector(&imxpd->connector,
&imxpd->imx_drm_connector, THIS_MODULE);
if (ret) {
imx_drm_remove_encoder(imxpd->imx_drm_encoder);
dev_err(imxpd->dev, "adding connector failed with %d\n", ret);
return ret;
}
imxpd->connector.encoder = &imxpd->encoder;
return 0;
}
static int imx_pd_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
const u8 *edidp;
struct imx_parallel_display *imxpd;
int ret;
const char *fmt;
struct pinctrl *pinctrl;
imxpd = devm_kzalloc(&pdev->dev, sizeof(*imxpd), GFP_KERNEL);
if (!imxpd)
return -ENOMEM;
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
if (IS_ERR(pinctrl)) {
ret = PTR_ERR(pinctrl);
dev_warn(&pdev->dev, "pinctrl_get_select_default failed with %d",
ret);
return ret;
}
edidp = of_get_property(np, "edid", &imxpd->edid_len);
if (edidp)
imxpd->edid = kmemdup(edidp, imxpd->edid_len, GFP_KERNEL);
ret = of_property_read_string(np, "interface-pix-fmt", &fmt);
if (!ret) {
if (!strcmp(fmt, "rgb24"))
imxpd->interface_pix_fmt = V4L2_PIX_FMT_RGB24;
else if (!strcmp(fmt, "rgb565"))
imxpd->interface_pix_fmt = V4L2_PIX_FMT_RGB565;
else if (!strcmp(fmt, "bgr666"))
imxpd->interface_pix_fmt = V4L2_PIX_FMT_BGR666;
}
imxpd->dev = &pdev->dev;
ret = imx_pd_register(imxpd);
if (ret)
return ret;
ret = imx_drm_encoder_add_possible_crtcs(imxpd->imx_drm_encoder, np);
platform_set_drvdata(pdev, imxpd);
return 0;
}
static int imx_pd_remove(struct platform_device *pdev)
{
struct imx_parallel_display *imxpd = platform_get_drvdata(pdev);
struct drm_connector *connector = &imxpd->connector;
struct drm_encoder *encoder = &imxpd->encoder;
drm_mode_connector_detach_encoder(connector, encoder);
imx_drm_remove_connector(imxpd->imx_drm_connector);
imx_drm_remove_encoder(imxpd->imx_drm_encoder);
return 0;
}
static const struct of_device_id imx_pd_dt_ids[] = {
{ .compatible = "fsl,imx-parallel-display", },
{ /* sentinel */ }
};
static struct platform_driver imx_pd_driver = {
.probe = imx_pd_probe,
.remove = imx_pd_remove,
.driver = {
.of_match_table = imx_pd_dt_ids,
.name = "imx-parallel-display",
.owner = THIS_MODULE,
},
};
module_platform_driver(imx_pd_driver);
MODULE_DESCRIPTION("i.MX parallel display driver");
MODULE_AUTHOR("Sascha Hauer, Pengutronix");
MODULE_LICENSE("GPL");