提交 fef1c8d0 编写于 作者: T Tomasz Stanislawski 提交者: Mauro Carvalho Chehab

[media] v4l: s5p-tv: add TV Mixer driver for Samsung S5P platform

Add driver for TV Mixer on Samsung platforms from S5P family.
Mixer is responsible for merging images from three layers and
passing result to the output.

Drivers are using:
- v4l2 framework
- videobuf2
- runtime PM
Signed-off-by: NTomasz Stanislawski <t.stanislaws@samsung.com>
Signed-off-by: NKyungmin Park <kyungmin.park@samsung.com>
Reviewed-by: NMarek Szyprowski <m.szyprowski@samsung.com>
Reviewed-by: NSylwester Nawrocki <s.nawrocki@samsung.com>
Reviewed-by: NHans Verkuil <hverkuil@xs4all.nl>
Signed-off-by: NMauro Carvalho Chehab <mchehab@redhat.com>
上级 9a498400
......@@ -57,4 +57,20 @@ config VIDEO_SAMSUNG_S5P_SDO
subdev for use by other drivers. This driver requires
hdmiphy driver to work correctly.
config VIDEO_SAMSUNG_S5P_MIXER
tristate "Samsung Mixer and Video Processor Driver"
depends on VIDEO_DEV && VIDEO_V4L2
depends on VIDEO_SAMSUNG_S5P_TV
select VIDEOBUF2_DMA_CONTIG
help
Say Y here if you want support for the Mixer in Samsung S5P SoCs.
This device produce image data to one of output interfaces.
config VIDEO_SAMSUNG_S5P_MIXER_DEBUG
bool "Enable debug for Mixer Driver"
depends on VIDEO_SAMSUNG_S5P_MIXER
default n
help
Enables debugging for Mixer driver.
endif # VIDEO_SAMSUNG_S5P_TV
......@@ -12,4 +12,6 @@ obj-$(CONFIG_VIDEO_SAMSUNG_S5P_HDMI) += s5p-hdmi.o
s5p-hdmi-y += hdmi_drv.o
obj-$(CONFIG_VIDEO_SAMSUNG_S5P_SDO) += s5p-sdo.o
s5p-sdo-y += sdo_drv.o
obj-$(CONFIG_VIDEO_SAMSUNG_S5P_MIXER) += s5p-mixer.o
s5p-mixer-y += mixer_drv.o mixer_video.o mixer_reg.o mixer_grp_layer.o mixer_vp_layer.o
/*
* Samsung TV Mixer driver
*
* Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
*
* Tomasz Stanislawski, <t.stanislaws@samsung.com>
*
* 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 Foundiation. either version 2 of the License,
* or (at your option) any later version
*/
#ifndef SAMSUNG_MIXER_H
#define SAMSUNG_MIXER_H
#ifdef CONFIG_VIDEO_SAMSUNG_S5P_MIXER_DEBUG
#define DEBUG
#endif
#include <linux/fb.h>
#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <media/v4l2-device.h>
#include <media/videobuf2-core.h>
#include "regs-mixer.h"
/** maximum number of output interfaces */
#define MXR_MAX_OUTPUTS 2
/** maximum number of input interfaces (layers) */
#define MXR_MAX_LAYERS 3
#define MXR_DRIVER_NAME "s5p-mixer"
/** maximal number of planes for every layer */
#define MXR_MAX_PLANES 2
#define MXR_ENABLE 1
#define MXR_DISABLE 0
/** description of a macroblock for packed formats */
struct mxr_block {
/** vertical number of pixels in macroblock */
unsigned int width;
/** horizontal number of pixels in macroblock */
unsigned int height;
/** size of block in bytes */
unsigned int size;
};
/** description of supported format */
struct mxr_format {
/** format name/mnemonic */
const char *name;
/** fourcc identifier */
u32 fourcc;
/** colorspace identifier */
enum v4l2_colorspace colorspace;
/** number of planes in image data */
int num_planes;
/** description of block for each plane */
struct mxr_block plane[MXR_MAX_PLANES];
/** number of subframes in image data */
int num_subframes;
/** specifies to which subframe belong given plane */
int plane2subframe[MXR_MAX_PLANES];
/** internal code, driver dependant */
unsigned long cookie;
};
/** description of crop configuration for image */
struct mxr_crop {
/** width of layer in pixels */
unsigned int full_width;
/** height of layer in pixels */
unsigned int full_height;
/** horizontal offset of first pixel to be displayed */
unsigned int x_offset;
/** vertical offset of first pixel to be displayed */
unsigned int y_offset;
/** width of displayed data in pixels */
unsigned int width;
/** height of displayed data in pixels */
unsigned int height;
/** indicate which fields are present in buffer */
unsigned int field;
};
/** description of transformation from source to destination image */
struct mxr_geometry {
/** cropping for source image */
struct mxr_crop src;
/** cropping for destination image */
struct mxr_crop dst;
/** layer-dependant description of horizontal scaling */
unsigned int x_ratio;
/** layer-dependant description of vertical scaling */
unsigned int y_ratio;
};
/** instance of a buffer */
struct mxr_buffer {
/** common v4l buffer stuff -- must be first */
struct vb2_buffer vb;
/** node for layer's lists */
struct list_head list;
};
/** internal states of layer */
enum mxr_layer_state {
/** layers is not shown */
MXR_LAYER_IDLE = 0,
/** state between STREAMON and hardware start */
MXR_LAYER_STREAMING_START,
/** layer is shown */
MXR_LAYER_STREAMING,
/** state before STREAMOFF is finished */
MXR_LAYER_STREAMING_FINISH,
};
/** forward declarations */
struct mxr_device;
struct mxr_layer;
/** callback for layers operation */
struct mxr_layer_ops {
/* TODO: try to port it to subdev API */
/** handler for resource release function */
void (*release)(struct mxr_layer *);
/** setting buffer to HW */
void (*buffer_set)(struct mxr_layer *, struct mxr_buffer *);
/** setting format and geometry in HW */
void (*format_set)(struct mxr_layer *);
/** streaming stop/start */
void (*stream_set)(struct mxr_layer *, int);
/** adjusting geometry */
void (*fix_geometry)(struct mxr_layer *);
};
/** layer instance, a single window and content displayed on output */
struct mxr_layer {
/** parent mixer device */
struct mxr_device *mdev;
/** layer index (unique identifier) */
int idx;
/** callbacks for layer methods */
struct mxr_layer_ops ops;
/** format array */
const struct mxr_format **fmt_array;
/** size of format array */
unsigned long fmt_array_size;
/** lock for protection of list and state fields */
spinlock_t enq_slock;
/** list for enqueued buffers */
struct list_head enq_list;
/** buffer currently owned by hardware in temporary registers */
struct mxr_buffer *update_buf;
/** buffer currently owned by hardware in shadow registers */
struct mxr_buffer *shadow_buf;
/** state of layer IDLE/STREAMING */
enum mxr_layer_state state;
/** mutex for protection of fields below */
struct mutex mutex;
/** handler for video node */
struct video_device vfd;
/** queue for output buffers */
struct vb2_queue vb_queue;
/** current image format */
const struct mxr_format *fmt;
/** current geometry of image */
struct mxr_geometry geo;
};
/** description of mixers output interface */
struct mxr_output {
/** name of output */
char name[32];
/** output subdev */
struct v4l2_subdev *sd;
/** cookie used for configuration of registers */
int cookie;
};
/** specify source of output subdevs */
struct mxr_output_conf {
/** name of output (connector) */
char *output_name;
/** name of module that generates output subdev */
char *module_name;
/** cookie need for mixer HW */
int cookie;
};
struct clk;
struct regulator;
/** auxiliary resources used my mixer */
struct mxr_resources {
/** interrupt index */
int irq;
/** pointer to Mixer registers */
void __iomem *mxr_regs;
/** pointer to Video Processor registers */
void __iomem *vp_regs;
/** other resources, should used under mxr_device.mutex */
struct clk *mixer;
struct clk *vp;
struct clk *sclk_mixer;
struct clk *sclk_hdmi;
struct clk *sclk_dac;
};
/* event flags used */
enum mxr_devide_flags {
MXR_EVENT_VSYNC = 0,
};
/** drivers instance */
struct mxr_device {
/** master device */
struct device *dev;
/** state of each layer */
struct mxr_layer *layer[MXR_MAX_LAYERS];
/** state of each output */
struct mxr_output *output[MXR_MAX_OUTPUTS];
/** number of registered outputs */
int output_cnt;
/* video resources */
/** V4L2 device */
struct v4l2_device v4l2_dev;
/** context of allocator */
void *alloc_ctx;
/** event wait queue */
wait_queue_head_t event_queue;
/** state flags */
unsigned long event_flags;
/** spinlock for protection of registers */
spinlock_t reg_slock;
/** mutex for protection of fields below */
struct mutex mutex;
/** number of entities depndant on output configuration */
int n_output;
/** number of users that do streaming */
int n_streamer;
/** index of current output */
int current_output;
/** auxiliary resources used my mixer */
struct mxr_resources res;
};
/** transform device structure into mixer device */
static inline struct mxr_device *to_mdev(struct device *dev)
{
struct v4l2_device *vdev = dev_get_drvdata(dev);
return container_of(vdev, struct mxr_device, v4l2_dev);
}
/** get current output data, should be called under mdev's mutex */
static inline struct mxr_output *to_output(struct mxr_device *mdev)
{
return mdev->output[mdev->current_output];
}
/** get current output subdev, should be called under mdev's mutex */
static inline struct v4l2_subdev *to_outsd(struct mxr_device *mdev)
{
struct mxr_output *out = to_output(mdev);
return out ? out->sd : NULL;
}
/** forward declaration for mixer platform data */
struct mxr_platform_data;
/** acquiring common video resources */
int __devinit mxr_acquire_video(struct mxr_device *mdev,
struct mxr_output_conf *output_cont, int output_count);
/** releasing common video resources */
void __devexit mxr_release_video(struct mxr_device *mdev);
struct mxr_layer *mxr_graph_layer_create(struct mxr_device *mdev, int idx);
struct mxr_layer *mxr_vp_layer_create(struct mxr_device *mdev, int idx);
struct mxr_layer *mxr_base_layer_create(struct mxr_device *mdev,
int idx, char *name, struct mxr_layer_ops *ops);
void mxr_base_layer_release(struct mxr_layer *layer);
void mxr_layer_release(struct mxr_layer *layer);
int mxr_base_layer_register(struct mxr_layer *layer);
void mxr_base_layer_unregister(struct mxr_layer *layer);
unsigned long mxr_get_plane_size(const struct mxr_block *blk,
unsigned int width, unsigned int height);
/** adds new consumer for mixer's power */
int __must_check mxr_power_get(struct mxr_device *mdev);
/** removes consumer for mixer's power */
void mxr_power_put(struct mxr_device *mdev);
/** add new client for output configuration */
void mxr_output_get(struct mxr_device *mdev);
/** removes new client for output configuration */
void mxr_output_put(struct mxr_device *mdev);
/** add new client for streaming */
void mxr_streamer_get(struct mxr_device *mdev);
/** removes new client for streaming */
void mxr_streamer_put(struct mxr_device *mdev);
/** returns format of data delivared to current output */
void mxr_get_mbus_fmt(struct mxr_device *mdev,
struct v4l2_mbus_framefmt *mbus_fmt);
/* Debug */
#define mxr_err(mdev, fmt, ...) dev_err(mdev->dev, fmt, ##__VA_ARGS__)
#define mxr_warn(mdev, fmt, ...) dev_warn(mdev->dev, fmt, ##__VA_ARGS__)
#define mxr_info(mdev, fmt, ...) dev_info(mdev->dev, fmt, ##__VA_ARGS__)
#ifdef CONFIG_VIDEO_SAMSUNG_S5P_MIXER_DEBUG
#define mxr_dbg(mdev, fmt, ...) dev_dbg(mdev->dev, fmt, ##__VA_ARGS__)
#else
#define mxr_dbg(mdev, fmt, ...) do { (void) mdev; } while (0)
#endif
/* accessing Mixer's and Video Processor's registers */
void mxr_vsync_set_update(struct mxr_device *mdev, int en);
void mxr_reg_reset(struct mxr_device *mdev);
irqreturn_t mxr_irq_handler(int irq, void *dev_data);
void mxr_reg_s_output(struct mxr_device *mdev, int cookie);
void mxr_reg_streamon(struct mxr_device *mdev);
void mxr_reg_streamoff(struct mxr_device *mdev);
int mxr_reg_wait4vsync(struct mxr_device *mdev);
void mxr_reg_set_mbus_fmt(struct mxr_device *mdev,
struct v4l2_mbus_framefmt *fmt);
void mxr_reg_graph_layer_stream(struct mxr_device *mdev, int idx, int en);
void mxr_reg_graph_buffer(struct mxr_device *mdev, int idx, dma_addr_t addr);
void mxr_reg_graph_format(struct mxr_device *mdev, int idx,
const struct mxr_format *fmt, const struct mxr_geometry *geo);
void mxr_reg_vp_layer_stream(struct mxr_device *mdev, int en);
void mxr_reg_vp_buffer(struct mxr_device *mdev,
dma_addr_t luma_addr[2], dma_addr_t chroma_addr[2]);
void mxr_reg_vp_format(struct mxr_device *mdev,
const struct mxr_format *fmt, const struct mxr_geometry *geo);
void mxr_reg_dump(struct mxr_device *mdev);
#endif /* SAMSUNG_MIXER_H */
/*
* Samsung TV Mixer driver
*
* Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
*
* Tomasz Stanislawski, <t.stanislaws@samsung.com>
*
* 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 Foundiation. either version 2 of the License,
* or (at your option) any later version
*/
#include "mixer.h"
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/fb.h>
#include <linux/delay.h>
#include <linux/pm_runtime.h>
#include <linux/clk.h>
MODULE_AUTHOR("Tomasz Stanislawski, <t.stanislaws@samsung.com>");
MODULE_DESCRIPTION("Samsung MIXER");
MODULE_LICENSE("GPL");
/* --------- DRIVER PARAMETERS ---------- */
static struct mxr_output_conf mxr_output_conf[] = {
{
.output_name = "S5P HDMI connector",
.module_name = "s5p-hdmi",
.cookie = 1,
},
{
.output_name = "S5P SDO connector",
.module_name = "s5p-sdo",
.cookie = 0,
},
};
void mxr_get_mbus_fmt(struct mxr_device *mdev,
struct v4l2_mbus_framefmt *mbus_fmt)
{
struct v4l2_subdev *sd;
int ret;
mutex_lock(&mdev->mutex);
sd = to_outsd(mdev);
ret = v4l2_subdev_call(sd, video, g_mbus_fmt, mbus_fmt);
WARN(ret, "failed to get mbus_fmt for output %s\n", sd->name);
mutex_unlock(&mdev->mutex);
}
void mxr_streamer_get(struct mxr_device *mdev)
{
mutex_lock(&mdev->mutex);
++mdev->n_streamer;
mxr_dbg(mdev, "%s(%d)\n", __func__, mdev->n_streamer);
if (mdev->n_streamer == 1) {
struct v4l2_subdev *sd = to_outsd(mdev);
struct v4l2_mbus_framefmt mbus_fmt;
struct mxr_resources *res = &mdev->res;
int ret;
if (to_output(mdev)->cookie == 0)
clk_set_parent(res->sclk_mixer, res->sclk_dac);
else
clk_set_parent(res->sclk_mixer, res->sclk_hdmi);
mxr_reg_s_output(mdev, to_output(mdev)->cookie);
ret = v4l2_subdev_call(sd, video, g_mbus_fmt, &mbus_fmt);
WARN(ret, "failed to get mbus_fmt for output %s\n", sd->name);
ret = v4l2_subdev_call(sd, video, s_stream, 1);
WARN(ret, "starting stream failed for output %s\n", sd->name);
mxr_reg_set_mbus_fmt(mdev, &mbus_fmt);
mxr_reg_streamon(mdev);
ret = mxr_reg_wait4vsync(mdev);
WARN(ret, "failed to get vsync (%d) from output\n", ret);
}
mutex_unlock(&mdev->mutex);
mxr_reg_dump(mdev);
/* FIXME: what to do when streaming fails? */
}
void mxr_streamer_put(struct mxr_device *mdev)
{
mutex_lock(&mdev->mutex);
--mdev->n_streamer;
mxr_dbg(mdev, "%s(%d)\n", __func__, mdev->n_streamer);
if (mdev->n_streamer == 0) {
int ret;
struct v4l2_subdev *sd = to_outsd(mdev);
mxr_reg_streamoff(mdev);
/* vsync applies Mixer setup */
ret = mxr_reg_wait4vsync(mdev);
WARN(ret, "failed to get vsync (%d) from output\n", ret);
ret = v4l2_subdev_call(sd, video, s_stream, 0);
WARN(ret, "stopping stream failed for output %s\n", sd->name);
}
WARN(mdev->n_streamer < 0, "negative number of streamers (%d)\n",
mdev->n_streamer);
mutex_unlock(&mdev->mutex);
mxr_reg_dump(mdev);
}
void mxr_output_get(struct mxr_device *mdev)
{
mutex_lock(&mdev->mutex);
++mdev->n_output;
mxr_dbg(mdev, "%s(%d)\n", __func__, mdev->n_output);
/* turn on auxiliary driver */
if (mdev->n_output == 1)
v4l2_subdev_call(to_outsd(mdev), core, s_power, 1);
mutex_unlock(&mdev->mutex);
}
void mxr_output_put(struct mxr_device *mdev)
{
mutex_lock(&mdev->mutex);
--mdev->n_output;
mxr_dbg(mdev, "%s(%d)\n", __func__, mdev->n_output);
/* turn on auxiliary driver */
if (mdev->n_output == 0)
v4l2_subdev_call(to_outsd(mdev), core, s_power, 0);
WARN(mdev->n_output < 0, "negative number of output users (%d)\n",
mdev->n_output);
mutex_unlock(&mdev->mutex);
}
int mxr_power_get(struct mxr_device *mdev)
{
int ret = pm_runtime_get_sync(mdev->dev);
/* returning 1 means that power is already enabled,
* so zero success be returned */
if (IS_ERR_VALUE(ret))
return ret;
return 0;
}
void mxr_power_put(struct mxr_device *mdev)
{
pm_runtime_put_sync(mdev->dev);
}
/* --------- RESOURCE MANAGEMENT -------------*/
static int __devinit mxr_acquire_plat_resources(struct mxr_device *mdev,
struct platform_device *pdev)
{
struct resource *res;
int ret;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mxr");
if (res == NULL) {
mxr_err(mdev, "get memory resource failed.\n");
ret = -ENXIO;
goto fail;
}
mdev->res.mxr_regs = ioremap(res->start, resource_size(res));
if (mdev->res.mxr_regs == NULL) {
mxr_err(mdev, "register mapping failed.\n");
ret = -ENXIO;
goto fail;
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vp");
if (res == NULL) {
mxr_err(mdev, "get memory resource failed.\n");
ret = -ENXIO;
goto fail_mxr_regs;
}
mdev->res.vp_regs = ioremap(res->start, resource_size(res));
if (mdev->res.vp_regs == NULL) {
mxr_err(mdev, "register mapping failed.\n");
ret = -ENXIO;
goto fail_mxr_regs;
}
res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "irq");
if (res == NULL) {
mxr_err(mdev, "get interrupt resource failed.\n");
ret = -ENXIO;
goto fail_vp_regs;
}
ret = request_irq(res->start, mxr_irq_handler, 0, "s5p-mixer", mdev);
if (ret) {
mxr_err(mdev, "request interrupt failed.\n");
goto fail_vp_regs;
}
mdev->res.irq = res->start;
return 0;
fail_vp_regs:
iounmap(mdev->res.vp_regs);
fail_mxr_regs:
iounmap(mdev->res.mxr_regs);
fail:
return ret;
}
static void mxr_release_plat_resources(struct mxr_device *mdev)
{
free_irq(mdev->res.irq, mdev);
iounmap(mdev->res.vp_regs);
iounmap(mdev->res.mxr_regs);
}
static void mxr_release_clocks(struct mxr_device *mdev)
{
struct mxr_resources *res = &mdev->res;
if (!IS_ERR_OR_NULL(res->sclk_dac))
clk_put(res->sclk_dac);
if (!IS_ERR_OR_NULL(res->sclk_hdmi))
clk_put(res->sclk_hdmi);
if (!IS_ERR_OR_NULL(res->sclk_mixer))
clk_put(res->sclk_mixer);
if (!IS_ERR_OR_NULL(res->vp))
clk_put(res->vp);
if (!IS_ERR_OR_NULL(res->mixer))
clk_put(res->mixer);
}
static int mxr_acquire_clocks(struct mxr_device *mdev)
{
struct mxr_resources *res = &mdev->res;
struct device *dev = mdev->dev;
res->mixer = clk_get(dev, "mixer");
if (IS_ERR_OR_NULL(res->mixer)) {
mxr_err(mdev, "failed to get clock 'mixer'\n");
goto fail;
}
res->vp = clk_get(dev, "vp");
if (IS_ERR_OR_NULL(res->vp)) {
mxr_err(mdev, "failed to get clock 'vp'\n");
goto fail;
}
res->sclk_mixer = clk_get(dev, "sclk_mixer");
if (IS_ERR_OR_NULL(res->sclk_mixer)) {
mxr_err(mdev, "failed to get clock 'sclk_mixer'\n");
goto fail;
}
res->sclk_hdmi = clk_get(dev, "sclk_hdmi");
if (IS_ERR_OR_NULL(res->sclk_hdmi)) {
mxr_err(mdev, "failed to get clock 'sclk_hdmi'\n");
goto fail;
}
res->sclk_dac = clk_get(dev, "sclk_dac");
if (IS_ERR_OR_NULL(res->sclk_dac)) {
mxr_err(mdev, "failed to get clock 'sclk_dac'\n");
goto fail;
}
return 0;
fail:
mxr_release_clocks(mdev);
return -ENODEV;
}
static int __devinit mxr_acquire_resources(struct mxr_device *mdev,
struct platform_device *pdev)
{
int ret;
ret = mxr_acquire_plat_resources(mdev, pdev);
if (ret)
goto fail;
ret = mxr_acquire_clocks(mdev);
if (ret)
goto fail_plat;
mxr_info(mdev, "resources acquired\n");
return 0;
fail_plat:
mxr_release_plat_resources(mdev);
fail:
mxr_err(mdev, "resources acquire failed\n");
return ret;
}
static void mxr_release_resources(struct mxr_device *mdev)
{
mxr_release_clocks(mdev);
mxr_release_plat_resources(mdev);
memset(&mdev->res, 0, sizeof mdev->res);
}
static void mxr_release_layers(struct mxr_device *mdev)
{
int i;
for (i = 0; i < ARRAY_SIZE(mdev->layer); ++i)
if (mdev->layer[i])
mxr_layer_release(mdev->layer[i]);
}
static int __devinit mxr_acquire_layers(struct mxr_device *mdev,
struct mxr_platform_data *pdata)
{
mdev->layer[0] = mxr_graph_layer_create(mdev, 0);
mdev->layer[1] = mxr_graph_layer_create(mdev, 1);
mdev->layer[2] = mxr_vp_layer_create(mdev, 0);
if (!mdev->layer[0] || !mdev->layer[1] || !mdev->layer[2]) {
mxr_err(mdev, "failed to acquire layers\n");
goto fail;
}
return 0;
fail:
mxr_release_layers(mdev);
return -ENODEV;
}
/* ---------- POWER MANAGEMENT ----------- */
static int mxr_runtime_resume(struct device *dev)
{
struct mxr_device *mdev = to_mdev(dev);
struct mxr_resources *res = &mdev->res;
mxr_dbg(mdev, "resume - start\n");
mutex_lock(&mdev->mutex);
/* turn clocks on */
clk_enable(res->mixer);
clk_enable(res->vp);
clk_enable(res->sclk_mixer);
/* apply default configuration */
mxr_reg_reset(mdev);
mxr_dbg(mdev, "resume - finished\n");
mutex_unlock(&mdev->mutex);
return 0;
}
static int mxr_runtime_suspend(struct device *dev)
{
struct mxr_device *mdev = to_mdev(dev);
struct mxr_resources *res = &mdev->res;
mxr_dbg(mdev, "suspend - start\n");
mutex_lock(&mdev->mutex);
/* turn clocks off */
clk_disable(res->sclk_mixer);
clk_disable(res->vp);
clk_disable(res->mixer);
mutex_unlock(&mdev->mutex);
mxr_dbg(mdev, "suspend - finished\n");
return 0;
}
static const struct dev_pm_ops mxr_pm_ops = {
.runtime_suspend = mxr_runtime_suspend,
.runtime_resume = mxr_runtime_resume,
};
/* --------- DRIVER INITIALIZATION ---------- */
static int __devinit mxr_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct mxr_platform_data *pdata = dev->platform_data;
struct mxr_device *mdev;
int ret;
/* mdev does not exist yet so no mxr_dbg is used */
dev_info(dev, "probe start\n");
mdev = kzalloc(sizeof *mdev, GFP_KERNEL);
if (!mdev) {
mxr_err(mdev, "not enough memory.\n");
ret = -ENOMEM;
goto fail;
}
/* setup pointer to master device */
mdev->dev = dev;
mutex_init(&mdev->mutex);
spin_lock_init(&mdev->reg_slock);
init_waitqueue_head(&mdev->event_queue);
/* acquire resources: regs, irqs, clocks, regulators */
ret = mxr_acquire_resources(mdev, pdev);
if (ret)
goto fail_mem;
/* configure resources for video output */
ret = mxr_acquire_video(mdev, mxr_output_conf,
ARRAY_SIZE(mxr_output_conf));
if (ret)
goto fail_resources;
/* configure layers */
ret = mxr_acquire_layers(mdev, pdata);
if (ret)
goto fail_video;
pm_runtime_enable(dev);
mxr_info(mdev, "probe successful\n");
return 0;
fail_video:
mxr_release_video(mdev);
fail_resources:
mxr_release_resources(mdev);
fail_mem:
kfree(mdev);
fail:
dev_info(dev, "probe failed\n");
return ret;
}
static int __devexit mxr_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct mxr_device *mdev = to_mdev(dev);
pm_runtime_disable(dev);
mxr_release_layers(mdev);
mxr_release_video(mdev);
mxr_release_resources(mdev);
kfree(mdev);
dev_info(dev, "remove sucessful\n");
return 0;
}
static struct platform_driver mxr_driver __refdata = {
.probe = mxr_probe,
.remove = __devexit_p(mxr_remove),
.driver = {
.name = MXR_DRIVER_NAME,
.owner = THIS_MODULE,
.pm = &mxr_pm_ops,
}
};
static int __init mxr_init(void)
{
int i, ret;
static const char banner[] __initdata = KERN_INFO
"Samsung TV Mixer driver, "
"(c) 2010-2011 Samsung Electronics Co., Ltd.\n";
printk(banner);
/* Loading auxiliary modules */
for (i = 0; i < ARRAY_SIZE(mxr_output_conf); ++i)
request_module(mxr_output_conf[i].module_name);
ret = platform_driver_register(&mxr_driver);
if (ret != 0) {
printk(KERN_ERR "registration of MIXER driver failed\n");
return -ENXIO;
}
return 0;
}
module_init(mxr_init);
static void __exit mxr_exit(void)
{
platform_driver_unregister(&mxr_driver);
}
module_exit(mxr_exit);
/*
* Samsung TV Mixer driver
*
* Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
*
* Tomasz Stanislawski, <t.stanislaws@samsung.com>
*
* 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 Foundiation. either version 2 of the License,
* or (at your option) any later version
*/
#include "mixer.h"
#include <media/videobuf2-dma-contig.h>
/* FORMAT DEFINITIONS */
static const struct mxr_format mxr_fb_fmt_rgb565 = {
.name = "RGB565",
.fourcc = V4L2_PIX_FMT_RGB565,
.colorspace = V4L2_COLORSPACE_SRGB,
.num_planes = 1,
.plane = {
{ .width = 1, .height = 1, .size = 2 },
},
.num_subframes = 1,
.cookie = 4,
};
static const struct mxr_format mxr_fb_fmt_argb1555 = {
.name = "ARGB1555",
.num_planes = 1,
.fourcc = V4L2_PIX_FMT_RGB555,
.colorspace = V4L2_COLORSPACE_SRGB,
.plane = {
{ .width = 1, .height = 1, .size = 2 },
},
.num_subframes = 1,
.cookie = 5,
};
static const struct mxr_format mxr_fb_fmt_argb4444 = {
.name = "ARGB4444",
.num_planes = 1,
.fourcc = V4L2_PIX_FMT_RGB444,
.colorspace = V4L2_COLORSPACE_SRGB,
.plane = {
{ .width = 1, .height = 1, .size = 2 },
},
.num_subframes = 1,
.cookie = 6,
};
static const struct mxr_format mxr_fb_fmt_argb8888 = {
.name = "ARGB8888",
.fourcc = V4L2_PIX_FMT_BGR32,
.colorspace = V4L2_COLORSPACE_SRGB,
.num_planes = 1,
.plane = {
{ .width = 1, .height = 1, .size = 4 },
},
.num_subframes = 1,
.cookie = 7,
};
static const struct mxr_format *mxr_graph_format[] = {
&mxr_fb_fmt_rgb565,
&mxr_fb_fmt_argb1555,
&mxr_fb_fmt_argb4444,
&mxr_fb_fmt_argb8888,
};
/* AUXILIARY CALLBACKS */
static void mxr_graph_layer_release(struct mxr_layer *layer)
{
mxr_base_layer_unregister(layer);
mxr_base_layer_release(layer);
}
static void mxr_graph_buffer_set(struct mxr_layer *layer,
struct mxr_buffer *buf)
{
dma_addr_t addr = 0;
if (buf)
addr = vb2_dma_contig_plane_paddr(&buf->vb, 0);
mxr_reg_graph_buffer(layer->mdev, layer->idx, addr);
}
static void mxr_graph_stream_set(struct mxr_layer *layer, int en)
{
mxr_reg_graph_layer_stream(layer->mdev, layer->idx, en);
}
static void mxr_graph_format_set(struct mxr_layer *layer)
{
mxr_reg_graph_format(layer->mdev, layer->idx,
layer->fmt, &layer->geo);
}
static void mxr_graph_fix_geometry(struct mxr_layer *layer)
{
struct mxr_geometry *geo = &layer->geo;
/* limit to boundary size */
geo->src.full_width = clamp_val(geo->src.full_width, 1, 32767);
geo->src.full_height = clamp_val(geo->src.full_height, 1, 2047);
geo->src.width = clamp_val(geo->src.width, 1, geo->src.full_width);
geo->src.width = min(geo->src.width, 2047U);
/* not possible to crop of Y axis */
geo->src.y_offset = min(geo->src.y_offset, geo->src.full_height - 1);
geo->src.height = geo->src.full_height - geo->src.y_offset;
/* limitting offset */
geo->src.x_offset = min(geo->src.x_offset,
geo->src.full_width - geo->src.width);
/* setting position in output */
geo->dst.width = min(geo->dst.width, geo->dst.full_width);
geo->dst.height = min(geo->dst.height, geo->dst.full_height);
/* Mixer supports only 1x and 2x scaling */
if (geo->dst.width >= 2 * geo->src.width) {
geo->x_ratio = 1;
geo->dst.width = 2 * geo->src.width;
} else {
geo->x_ratio = 0;
geo->dst.width = geo->src.width;
}
if (geo->dst.height >= 2 * geo->src.height) {
geo->y_ratio = 1;
geo->dst.height = 2 * geo->src.height;
} else {
geo->y_ratio = 0;
geo->dst.height = geo->src.height;
}
geo->dst.x_offset = min(geo->dst.x_offset,
geo->dst.full_width - geo->dst.width);
geo->dst.y_offset = min(geo->dst.y_offset,
geo->dst.full_height - geo->dst.height);
}
/* PUBLIC API */
struct mxr_layer *mxr_graph_layer_create(struct mxr_device *mdev, int idx)
{
struct mxr_layer *layer;
int ret;
struct mxr_layer_ops ops = {
.release = mxr_graph_layer_release,
.buffer_set = mxr_graph_buffer_set,
.stream_set = mxr_graph_stream_set,
.format_set = mxr_graph_format_set,
.fix_geometry = mxr_graph_fix_geometry,
};
char name[32];
sprintf(name, "graph%d", idx);
layer = mxr_base_layer_create(mdev, idx, name, &ops);
if (layer == NULL) {
mxr_err(mdev, "failed to initialize layer(%d) base\n", idx);
goto fail;
}
layer->fmt_array = mxr_graph_format;
layer->fmt_array_size = ARRAY_SIZE(mxr_graph_format);
ret = mxr_base_layer_register(layer);
if (ret)
goto fail_layer;
return layer;
fail_layer:
mxr_base_layer_release(layer);
fail:
return NULL;
}
/*
* Samsung TV Mixer driver
*
* Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
*
* Tomasz Stanislawski, <t.stanislaws@samsung.com>
*
* 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 Foundiation. either version 2 of the License,
* or (at your option) any later version
*/
#include "mixer.h"
#include "regs-mixer.h"
#include "regs-vp.h"
#include <linux/delay.h>
/* Register access subroutines */
static inline u32 vp_read(struct mxr_device *mdev, u32 reg_id)
{
return readl(mdev->res.vp_regs + reg_id);
}
static inline void vp_write(struct mxr_device *mdev, u32 reg_id, u32 val)
{
writel(val, mdev->res.vp_regs + reg_id);
}
static inline void vp_write_mask(struct mxr_device *mdev, u32 reg_id,
u32 val, u32 mask)
{
u32 old = vp_read(mdev, reg_id);
val = (val & mask) | (old & ~mask);
writel(val, mdev->res.vp_regs + reg_id);
}
static inline u32 mxr_read(struct mxr_device *mdev, u32 reg_id)
{
return readl(mdev->res.mxr_regs + reg_id);
}
static inline void mxr_write(struct mxr_device *mdev, u32 reg_id, u32 val)
{
writel(val, mdev->res.mxr_regs + reg_id);
}
static inline void mxr_write_mask(struct mxr_device *mdev, u32 reg_id,
u32 val, u32 mask)
{
u32 old = mxr_read(mdev, reg_id);
val = (val & mask) | (old & ~mask);
writel(val, mdev->res.mxr_regs + reg_id);
}
void mxr_vsync_set_update(struct mxr_device *mdev, int en)
{
/* block update on vsync */
mxr_write_mask(mdev, MXR_STATUS, en ? MXR_STATUS_SYNC_ENABLE : 0,
MXR_STATUS_SYNC_ENABLE);
vp_write(mdev, VP_SHADOW_UPDATE, en ? VP_SHADOW_UPDATE_ENABLE : 0);
}
static void __mxr_reg_vp_reset(struct mxr_device *mdev)
{
int tries = 100;
vp_write(mdev, VP_SRESET, VP_SRESET_PROCESSING);
for (tries = 100; tries; --tries) {
/* waiting until VP_SRESET_PROCESSING is 0 */
if (~vp_read(mdev, VP_SRESET) & VP_SRESET_PROCESSING)
break;
mdelay(10);
}
WARN(tries == 0, "failed to reset Video Processor\n");
}
static void mxr_reg_vp_default_filter(struct mxr_device *mdev);
void mxr_reg_reset(struct mxr_device *mdev)
{
unsigned long flags;
u32 val; /* value stored to register */
spin_lock_irqsave(&mdev->reg_slock, flags);
mxr_vsync_set_update(mdev, MXR_DISABLE);
/* set output in RGB888 mode */
mxr_write(mdev, MXR_CFG, MXR_CFG_OUT_YUV444);
/* 16 beat burst in DMA */
mxr_write_mask(mdev, MXR_STATUS, MXR_STATUS_16_BURST,
MXR_STATUS_BURST_MASK);
/* setting default layer priority: layer1 > video > layer0
* because typical usage scenario would be
* layer0 - framebuffer
* video - video overlay
* layer1 - OSD
*/
val = MXR_LAYER_CFG_GRP0_VAL(1);
val |= MXR_LAYER_CFG_VP_VAL(2);
val |= MXR_LAYER_CFG_GRP1_VAL(3);
mxr_write(mdev, MXR_LAYER_CFG, val);
/* use dark gray background color */
mxr_write(mdev, MXR_BG_COLOR0, 0x808080);
mxr_write(mdev, MXR_BG_COLOR1, 0x808080);
mxr_write(mdev, MXR_BG_COLOR2, 0x808080);
/* setting graphical layers */
val = MXR_GRP_CFG_COLOR_KEY_DISABLE; /* no blank key */
val |= MXR_GRP_CFG_BLEND_PRE_MUL; /* premul mode */
val |= MXR_GRP_CFG_ALPHA_VAL(0xff); /* non-transparent alpha */
/* the same configuration for both layers */
mxr_write(mdev, MXR_GRAPHIC_CFG(0), val);
mxr_write(mdev, MXR_GRAPHIC_CFG(1), val);
/* configuration of Video Processor Registers */
__mxr_reg_vp_reset(mdev);
mxr_reg_vp_default_filter(mdev);
/* enable all interrupts */
mxr_write_mask(mdev, MXR_INT_EN, ~0, MXR_INT_EN_ALL);
mxr_vsync_set_update(mdev, MXR_ENABLE);
spin_unlock_irqrestore(&mdev->reg_slock, flags);
}
void mxr_reg_graph_format(struct mxr_device *mdev, int idx,
const struct mxr_format *fmt, const struct mxr_geometry *geo)
{
u32 val;
unsigned long flags;
spin_lock_irqsave(&mdev->reg_slock, flags);
mxr_vsync_set_update(mdev, MXR_DISABLE);
/* setup format */
mxr_write_mask(mdev, MXR_GRAPHIC_CFG(idx),
MXR_GRP_CFG_FORMAT_VAL(fmt->cookie), MXR_GRP_CFG_FORMAT_MASK);
/* setup geometry */
mxr_write(mdev, MXR_GRAPHIC_SPAN(idx), geo->src.full_width);
val = MXR_GRP_WH_WIDTH(geo->src.width);
val |= MXR_GRP_WH_HEIGHT(geo->src.height);
val |= MXR_GRP_WH_H_SCALE(geo->x_ratio);
val |= MXR_GRP_WH_V_SCALE(geo->y_ratio);
mxr_write(mdev, MXR_GRAPHIC_WH(idx), val);
/* setup offsets in source image */
val = MXR_GRP_SXY_SX(geo->src.x_offset);
val |= MXR_GRP_SXY_SY(geo->src.y_offset);
mxr_write(mdev, MXR_GRAPHIC_SXY(idx), val);
/* setup offsets in display image */
val = MXR_GRP_DXY_DX(geo->dst.x_offset);
val |= MXR_GRP_DXY_DY(geo->dst.y_offset);
mxr_write(mdev, MXR_GRAPHIC_DXY(idx), val);
mxr_vsync_set_update(mdev, MXR_ENABLE);
spin_unlock_irqrestore(&mdev->reg_slock, flags);
}
void mxr_reg_vp_format(struct mxr_device *mdev,
const struct mxr_format *fmt, const struct mxr_geometry *geo)
{
unsigned long flags;
spin_lock_irqsave(&mdev->reg_slock, flags);
mxr_vsync_set_update(mdev, MXR_DISABLE);
vp_write_mask(mdev, VP_MODE, fmt->cookie, VP_MODE_FMT_MASK);
/* setting size of input image */
vp_write(mdev, VP_IMG_SIZE_Y, VP_IMG_HSIZE(geo->src.full_width) |
VP_IMG_VSIZE(geo->src.full_height));
/* chroma height has to reduced by 2 to avoid chroma distorions */
vp_write(mdev, VP_IMG_SIZE_C, VP_IMG_HSIZE(geo->src.full_width) |
VP_IMG_VSIZE(geo->src.full_height / 2));
vp_write(mdev, VP_SRC_WIDTH, geo->src.width);
vp_write(mdev, VP_SRC_HEIGHT, geo->src.height);
vp_write(mdev, VP_SRC_H_POSITION,
VP_SRC_H_POSITION_VAL(geo->src.x_offset));
vp_write(mdev, VP_SRC_V_POSITION, geo->src.y_offset);
vp_write(mdev, VP_DST_WIDTH, geo->dst.width);
vp_write(mdev, VP_DST_H_POSITION, geo->dst.x_offset);
if (geo->dst.field == V4L2_FIELD_INTERLACED) {
vp_write(mdev, VP_DST_HEIGHT, geo->dst.height / 2);
vp_write(mdev, VP_DST_V_POSITION, geo->dst.y_offset / 2);
} else {
vp_write(mdev, VP_DST_HEIGHT, geo->dst.height);
vp_write(mdev, VP_DST_V_POSITION, geo->dst.y_offset);
}
vp_write(mdev, VP_H_RATIO, geo->x_ratio);
vp_write(mdev, VP_V_RATIO, geo->y_ratio);
vp_write(mdev, VP_ENDIAN_MODE, VP_ENDIAN_MODE_LITTLE);
mxr_vsync_set_update(mdev, MXR_ENABLE);
spin_unlock_irqrestore(&mdev->reg_slock, flags);
}
void mxr_reg_graph_buffer(struct mxr_device *mdev, int idx, dma_addr_t addr)
{
u32 val = addr ? ~0 : 0;
unsigned long flags;
spin_lock_irqsave(&mdev->reg_slock, flags);
mxr_vsync_set_update(mdev, MXR_DISABLE);
if (idx == 0)
mxr_write_mask(mdev, MXR_CFG, val, MXR_CFG_GRP0_ENABLE);
else
mxr_write_mask(mdev, MXR_CFG, val, MXR_CFG_GRP1_ENABLE);
mxr_write(mdev, MXR_GRAPHIC_BASE(idx), addr);
mxr_vsync_set_update(mdev, MXR_ENABLE);
spin_unlock_irqrestore(&mdev->reg_slock, flags);
}
void mxr_reg_vp_buffer(struct mxr_device *mdev,
dma_addr_t luma_addr[2], dma_addr_t chroma_addr[2])
{
u32 val = luma_addr[0] ? ~0 : 0;
unsigned long flags;
spin_lock_irqsave(&mdev->reg_slock, flags);
mxr_vsync_set_update(mdev, MXR_DISABLE);
mxr_write_mask(mdev, MXR_CFG, val, MXR_CFG_VP_ENABLE);
vp_write_mask(mdev, VP_ENABLE, val, VP_ENABLE_ON);
/* TODO: fix tiled mode */
vp_write(mdev, VP_TOP_Y_PTR, luma_addr[0]);
vp_write(mdev, VP_TOP_C_PTR, chroma_addr[0]);
vp_write(mdev, VP_BOT_Y_PTR, luma_addr[1]);
vp_write(mdev, VP_BOT_C_PTR, chroma_addr[1]);
mxr_vsync_set_update(mdev, MXR_ENABLE);
spin_unlock_irqrestore(&mdev->reg_slock, flags);
}
static void mxr_irq_layer_handle(struct mxr_layer *layer)
{
struct list_head *head = &layer->enq_list;
struct mxr_buffer *done;
/* skip non-existing layer */
if (layer == NULL)
return;
spin_lock(&layer->enq_slock);
if (layer->state == MXR_LAYER_IDLE)
goto done;
done = layer->shadow_buf;
layer->shadow_buf = layer->update_buf;
if (list_empty(head)) {
if (layer->state != MXR_LAYER_STREAMING)
layer->update_buf = NULL;
} else {
struct mxr_buffer *next;
next = list_first_entry(head, struct mxr_buffer, list);
list_del(&next->list);
layer->update_buf = next;
}
layer->ops.buffer_set(layer, layer->update_buf);
if (done && done != layer->shadow_buf)
vb2_buffer_done(&done->vb, VB2_BUF_STATE_DONE);
done:
spin_unlock(&layer->enq_slock);
}
irqreturn_t mxr_irq_handler(int irq, void *dev_data)
{
struct mxr_device *mdev = dev_data;
u32 i, val;
spin_lock(&mdev->reg_slock);
val = mxr_read(mdev, MXR_INT_STATUS);
/* wake up process waiting for VSYNC */
if (val & MXR_INT_STATUS_VSYNC) {
set_bit(MXR_EVENT_VSYNC, &mdev->event_flags);
wake_up(&mdev->event_queue);
}
/* clear interrupts */
if (~val & MXR_INT_EN_VSYNC) {
/* vsync interrupt use different bit for read and clear */
val &= ~MXR_INT_EN_VSYNC;
val |= MXR_INT_CLEAR_VSYNC;
}
mxr_write(mdev, MXR_INT_STATUS, val);
spin_unlock(&mdev->reg_slock);
/* leave on non-vsync event */
if (~val & MXR_INT_CLEAR_VSYNC)
return IRQ_HANDLED;
for (i = 0; i < MXR_MAX_LAYERS; ++i)
mxr_irq_layer_handle(mdev->layer[i]);
return IRQ_HANDLED;
}
void mxr_reg_s_output(struct mxr_device *mdev, int cookie)
{
u32 val;
val = cookie == 0 ? MXR_CFG_DST_SDO : MXR_CFG_DST_HDMI;
mxr_write_mask(mdev, MXR_CFG, val, MXR_CFG_DST_MASK);
}
void mxr_reg_streamon(struct mxr_device *mdev)
{
unsigned long flags;
spin_lock_irqsave(&mdev->reg_slock, flags);
/* single write -> no need to block vsync update */
/* start MIXER */
mxr_write_mask(mdev, MXR_STATUS, ~0, MXR_STATUS_REG_RUN);
spin_unlock_irqrestore(&mdev->reg_slock, flags);
}
void mxr_reg_streamoff(struct mxr_device *mdev)
{
unsigned long flags;
spin_lock_irqsave(&mdev->reg_slock, flags);
/* single write -> no need to block vsync update */
/* stop MIXER */
mxr_write_mask(mdev, MXR_STATUS, 0, MXR_STATUS_REG_RUN);
spin_unlock_irqrestore(&mdev->reg_slock, flags);
}
int mxr_reg_wait4vsync(struct mxr_device *mdev)
{
int ret;
clear_bit(MXR_EVENT_VSYNC, &mdev->event_flags);
/* TODO: consider adding interruptible */
ret = wait_event_timeout(mdev->event_queue,
test_bit(MXR_EVENT_VSYNC, &mdev->event_flags),
msecs_to_jiffies(1000));
if (ret > 0)
return 0;
if (ret < 0)
return ret;
mxr_warn(mdev, "no vsync detected - timeout\n");
return -ETIME;
}
void mxr_reg_set_mbus_fmt(struct mxr_device *mdev,
struct v4l2_mbus_framefmt *fmt)
{
u32 val = 0;
unsigned long flags;
spin_lock_irqsave(&mdev->reg_slock, flags);
mxr_vsync_set_update(mdev, MXR_DISABLE);
/* choosing between interlace and progressive mode */
if (fmt->field == V4L2_FIELD_INTERLACED)
val |= MXR_CFG_SCAN_INTERLACE;
else
val |= MXR_CFG_SCAN_PROGRASSIVE;
/* choosing between porper HD and SD mode */
if (fmt->height == 480)
val |= MXR_CFG_SCAN_NTSC | MXR_CFG_SCAN_SD;
else if (fmt->height == 576)
val |= MXR_CFG_SCAN_PAL | MXR_CFG_SCAN_SD;
else if (fmt->height == 720)
val |= MXR_CFG_SCAN_HD_720 | MXR_CFG_SCAN_HD;
else if (fmt->height == 1080)
val |= MXR_CFG_SCAN_HD_1080 | MXR_CFG_SCAN_HD;
else
WARN(1, "unrecognized mbus height %u!\n", fmt->height);
mxr_write_mask(mdev, MXR_CFG, val, MXR_CFG_SCAN_MASK);
val = (fmt->field == V4L2_FIELD_INTERLACED) ? ~0 : 0;
vp_write_mask(mdev, VP_MODE, val,
VP_MODE_LINE_SKIP | VP_MODE_FIELD_ID_AUTO_TOGGLING);
mxr_vsync_set_update(mdev, MXR_ENABLE);
spin_unlock_irqrestore(&mdev->reg_slock, flags);
}
void mxr_reg_graph_layer_stream(struct mxr_device *mdev, int idx, int en)
{
/* no extra actions need to be done */
}
void mxr_reg_vp_layer_stream(struct mxr_device *mdev, int en)
{
/* no extra actions need to be done */
}
static const u8 filter_y_horiz_tap8[] = {
0, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, 0, 0, 0,
0, 2, 4, 5, 6, 6, 6, 6,
6, 5, 5, 4, 3, 2, 1, 1,
0, -6, -12, -16, -18, -20, -21, -20,
-20, -18, -16, -13, -10, -8, -5, -2,
127, 126, 125, 121, 114, 107, 99, 89,
79, 68, 57, 46, 35, 25, 16, 8,
};
static const u8 filter_y_vert_tap4[] = {
0, -3, -6, -8, -8, -8, -8, -7,
-6, -5, -4, -3, -2, -1, -1, 0,
127, 126, 124, 118, 111, 102, 92, 81,
70, 59, 48, 37, 27, 19, 11, 5,
0, 5, 11, 19, 27, 37, 48, 59,
70, 81, 92, 102, 111, 118, 124, 126,
0, 0, -1, -1, -2, -3, -4, -5,
-6, -7, -8, -8, -8, -8, -6, -3,
};
static const u8 filter_cr_horiz_tap4[] = {
0, -3, -6, -8, -8, -8, -8, -7,
-6, -5, -4, -3, -2, -1, -1, 0,
127, 126, 124, 118, 111, 102, 92, 81,
70, 59, 48, 37, 27, 19, 11, 5,
};
static inline void mxr_reg_vp_filter_set(struct mxr_device *mdev,
int reg_id, const u8 *data, unsigned int size)
{
/* assure 4-byte align */
BUG_ON(size & 3);
for (; size; size -= 4, reg_id += 4, data += 4) {
u32 val = (data[0] << 24) | (data[1] << 16) |
(data[2] << 8) | data[3];
vp_write(mdev, reg_id, val);
}
}
static void mxr_reg_vp_default_filter(struct mxr_device *mdev)
{
mxr_reg_vp_filter_set(mdev, VP_POLY8_Y0_LL,
filter_y_horiz_tap8, sizeof filter_y_horiz_tap8);
mxr_reg_vp_filter_set(mdev, VP_POLY4_Y0_LL,
filter_y_vert_tap4, sizeof filter_y_vert_tap4);
mxr_reg_vp_filter_set(mdev, VP_POLY4_C0_LL,
filter_cr_horiz_tap4, sizeof filter_cr_horiz_tap4);
}
static void mxr_reg_mxr_dump(struct mxr_device *mdev)
{
#define DUMPREG(reg_id) \
do { \
mxr_dbg(mdev, #reg_id " = %08x\n", \
(u32)readl(mdev->res.mxr_regs + reg_id)); \
} while (0)
DUMPREG(MXR_STATUS);
DUMPREG(MXR_CFG);
DUMPREG(MXR_INT_EN);
DUMPREG(MXR_INT_STATUS);
DUMPREG(MXR_LAYER_CFG);
DUMPREG(MXR_VIDEO_CFG);
DUMPREG(MXR_GRAPHIC0_CFG);
DUMPREG(MXR_GRAPHIC0_BASE);
DUMPREG(MXR_GRAPHIC0_SPAN);
DUMPREG(MXR_GRAPHIC0_WH);
DUMPREG(MXR_GRAPHIC0_SXY);
DUMPREG(MXR_GRAPHIC0_DXY);
DUMPREG(MXR_GRAPHIC1_CFG);
DUMPREG(MXR_GRAPHIC1_BASE);
DUMPREG(MXR_GRAPHIC1_SPAN);
DUMPREG(MXR_GRAPHIC1_WH);
DUMPREG(MXR_GRAPHIC1_SXY);
DUMPREG(MXR_GRAPHIC1_DXY);
#undef DUMPREG
}
static void mxr_reg_vp_dump(struct mxr_device *mdev)
{
#define DUMPREG(reg_id) \
do { \
mxr_dbg(mdev, #reg_id " = %08x\n", \
(u32) readl(mdev->res.vp_regs + reg_id)); \
} while (0)
DUMPREG(VP_ENABLE);
DUMPREG(VP_SRESET);
DUMPREG(VP_SHADOW_UPDATE);
DUMPREG(VP_FIELD_ID);
DUMPREG(VP_MODE);
DUMPREG(VP_IMG_SIZE_Y);
DUMPREG(VP_IMG_SIZE_C);
DUMPREG(VP_PER_RATE_CTRL);
DUMPREG(VP_TOP_Y_PTR);
DUMPREG(VP_BOT_Y_PTR);
DUMPREG(VP_TOP_C_PTR);
DUMPREG(VP_BOT_C_PTR);
DUMPREG(VP_ENDIAN_MODE);
DUMPREG(VP_SRC_H_POSITION);
DUMPREG(VP_SRC_V_POSITION);
DUMPREG(VP_SRC_WIDTH);
DUMPREG(VP_SRC_HEIGHT);
DUMPREG(VP_DST_H_POSITION);
DUMPREG(VP_DST_V_POSITION);
DUMPREG(VP_DST_WIDTH);
DUMPREG(VP_DST_HEIGHT);
DUMPREG(VP_H_RATIO);
DUMPREG(VP_V_RATIO);
#undef DUMPREG
}
void mxr_reg_dump(struct mxr_device *mdev)
{
mxr_reg_mxr_dump(mdev);
mxr_reg_vp_dump(mdev);
}
此差异已折叠。
/*
* Samsung TV Mixer driver
*
* Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
*
* Tomasz Stanislawski, <t.stanislaws@samsung.com>
*
* 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 Foundiation. either version 2 of the License,
* or (at your option) any later version
*/
#include "mixer.h"
#include "regs-vp.h"
#include <media/videobuf2-dma-contig.h>
/* FORMAT DEFINITIONS */
static const struct mxr_format mxr_fmt_nv12 = {
.name = "NV12",
.fourcc = V4L2_PIX_FMT_NV12,
.colorspace = V4L2_COLORSPACE_JPEG,
.num_planes = 2,
.plane = {
{ .width = 1, .height = 1, .size = 1 },
{ .width = 2, .height = 2, .size = 2 },
},
.num_subframes = 1,
.cookie = VP_MODE_NV12 | VP_MODE_MEM_LINEAR,
};
static const struct mxr_format mxr_fmt_nv21 = {
.name = "NV21",
.fourcc = V4L2_PIX_FMT_NV21,
.colorspace = V4L2_COLORSPACE_JPEG,
.num_planes = 2,
.plane = {
{ .width = 1, .height = 1, .size = 1 },
{ .width = 2, .height = 2, .size = 2 },
},
.num_subframes = 1,
.cookie = VP_MODE_NV21 | VP_MODE_MEM_LINEAR,
};
static const struct mxr_format mxr_fmt_nv12m = {
.name = "NV12 (mplane)",
.fourcc = V4L2_PIX_FMT_NV12M,
.colorspace = V4L2_COLORSPACE_JPEG,
.num_planes = 2,
.plane = {
{ .width = 1, .height = 1, .size = 1 },
{ .width = 2, .height = 2, .size = 2 },
},
.num_subframes = 2,
.plane2subframe = {0, 1},
.cookie = VP_MODE_NV12 | VP_MODE_MEM_LINEAR,
};
static const struct mxr_format mxr_fmt_nv12mt = {
.name = "NV12 tiled (mplane)",
.fourcc = V4L2_PIX_FMT_NV12MT,
.colorspace = V4L2_COLORSPACE_JPEG,
.num_planes = 2,
.plane = {
{ .width = 128, .height = 32, .size = 4096 },
{ .width = 128, .height = 32, .size = 2048 },
},
.num_subframes = 2,
.plane2subframe = {0, 1},
.cookie = VP_MODE_NV12 | VP_MODE_MEM_TILED,
};
static const struct mxr_format *mxr_video_format[] = {
&mxr_fmt_nv12,
&mxr_fmt_nv21,
&mxr_fmt_nv12m,
&mxr_fmt_nv12mt,
};
/* AUXILIARY CALLBACKS */
static void mxr_vp_layer_release(struct mxr_layer *layer)
{
mxr_base_layer_unregister(layer);
mxr_base_layer_release(layer);
}
static void mxr_vp_buffer_set(struct mxr_layer *layer,
struct mxr_buffer *buf)
{
dma_addr_t luma_addr[2] = {0, 0};
dma_addr_t chroma_addr[2] = {0, 0};
if (buf == NULL) {
mxr_reg_vp_buffer(layer->mdev, luma_addr, chroma_addr);
return;
}
luma_addr[0] = vb2_dma_contig_plane_paddr(&buf->vb, 0);
if (layer->fmt->num_subframes == 2) {
chroma_addr[0] = vb2_dma_contig_plane_paddr(&buf->vb, 1);
} else {
/* FIXME: mxr_get_plane_size compute integer division,
* which is slow and should not be performed in interrupt */
chroma_addr[0] = luma_addr[0] + mxr_get_plane_size(
&layer->fmt->plane[0], layer->geo.src.full_width,
layer->geo.src.full_height);
}
if (layer->fmt->cookie & VP_MODE_MEM_TILED) {
luma_addr[1] = luma_addr[0] + 0x40;
chroma_addr[1] = chroma_addr[0] + 0x40;
} else {
luma_addr[1] = luma_addr[0] + layer->geo.src.full_width;
chroma_addr[1] = chroma_addr[0];
}
mxr_reg_vp_buffer(layer->mdev, luma_addr, chroma_addr);
}
static void mxr_vp_stream_set(struct mxr_layer *layer, int en)
{
mxr_reg_vp_layer_stream(layer->mdev, en);
}
static void mxr_vp_format_set(struct mxr_layer *layer)
{
mxr_reg_vp_format(layer->mdev, layer->fmt, &layer->geo);
}
static void mxr_vp_fix_geometry(struct mxr_layer *layer)
{
struct mxr_geometry *geo = &layer->geo;
/* align horizontal size to 8 pixels */
geo->src.full_width = ALIGN(geo->src.full_width, 8);
/* limit to boundary size */
geo->src.full_width = clamp_val(geo->src.full_width, 8, 8192);
geo->src.full_height = clamp_val(geo->src.full_height, 1, 8192);
geo->src.width = clamp_val(geo->src.width, 32, geo->src.full_width);
geo->src.width = min(geo->src.width, 2047U);
geo->src.height = clamp_val(geo->src.height, 4, geo->src.full_height);
geo->src.height = min(geo->src.height, 2047U);
/* setting size of output window */
geo->dst.width = clamp_val(geo->dst.width, 8, geo->dst.full_width);
geo->dst.height = clamp_val(geo->dst.height, 1, geo->dst.full_height);
/* ensure that scaling is in range 1/4x to 16x */
if (geo->src.width >= 4 * geo->dst.width)
geo->src.width = 4 * geo->dst.width;
if (geo->dst.width >= 16 * geo->src.width)
geo->dst.width = 16 * geo->src.width;
if (geo->src.height >= 4 * geo->dst.height)
geo->src.height = 4 * geo->dst.height;
if (geo->dst.height >= 16 * geo->src.height)
geo->dst.height = 16 * geo->src.height;
/* setting scaling ratio */
geo->x_ratio = (geo->src.width << 16) / geo->dst.width;
geo->y_ratio = (geo->src.height << 16) / geo->dst.height;
/* adjust offsets */
geo->src.x_offset = min(geo->src.x_offset,
geo->src.full_width - geo->src.width);
geo->src.y_offset = min(geo->src.y_offset,
geo->src.full_height - geo->src.height);
geo->dst.x_offset = min(geo->dst.x_offset,
geo->dst.full_width - geo->dst.width);
geo->dst.y_offset = min(geo->dst.y_offset,
geo->dst.full_height - geo->dst.height);
}
/* PUBLIC API */
struct mxr_layer *mxr_vp_layer_create(struct mxr_device *mdev, int idx)
{
struct mxr_layer *layer;
int ret;
struct mxr_layer_ops ops = {
.release = mxr_vp_layer_release,
.buffer_set = mxr_vp_buffer_set,
.stream_set = mxr_vp_stream_set,
.format_set = mxr_vp_format_set,
.fix_geometry = mxr_vp_fix_geometry,
};
char name[32];
sprintf(name, "video%d", idx);
layer = mxr_base_layer_create(mdev, idx, name, &ops);
if (layer == NULL) {
mxr_err(mdev, "failed to initialize layer(%d) base\n", idx);
goto fail;
}
layer->fmt_array = mxr_video_format;
layer->fmt_array_size = ARRAY_SIZE(mxr_video_format);
ret = mxr_base_layer_register(layer);
if (ret)
goto fail_layer;
return layer;
fail_layer:
mxr_base_layer_release(layer);
fail:
return NULL;
}
/*
* Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
* http://www.samsung.com/
*
* Mixer register header file for Samsung Mixer driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef SAMSUNG_REGS_MIXER_H
#define SAMSUNG_REGS_MIXER_H
/*
* Register part
*/
#define MXR_STATUS 0x0000
#define MXR_CFG 0x0004
#define MXR_INT_EN 0x0008
#define MXR_INT_STATUS 0x000C
#define MXR_LAYER_CFG 0x0010
#define MXR_VIDEO_CFG 0x0014
#define MXR_GRAPHIC0_CFG 0x0020
#define MXR_GRAPHIC0_BASE 0x0024
#define MXR_GRAPHIC0_SPAN 0x0028
#define MXR_GRAPHIC0_SXY 0x002C
#define MXR_GRAPHIC0_WH 0x0030
#define MXR_GRAPHIC0_DXY 0x0034
#define MXR_GRAPHIC0_BLANK 0x0038
#define MXR_GRAPHIC1_CFG 0x0040
#define MXR_GRAPHIC1_BASE 0x0044
#define MXR_GRAPHIC1_SPAN 0x0048
#define MXR_GRAPHIC1_SXY 0x004C
#define MXR_GRAPHIC1_WH 0x0050
#define MXR_GRAPHIC1_DXY 0x0054
#define MXR_GRAPHIC1_BLANK 0x0058
#define MXR_BG_CFG 0x0060
#define MXR_BG_COLOR0 0x0064
#define MXR_BG_COLOR1 0x0068
#define MXR_BG_COLOR2 0x006C
/* for parametrized access to layer registers */
#define MXR_GRAPHIC_CFG(i) (0x0020 + (i) * 0x20)
#define MXR_GRAPHIC_BASE(i) (0x0024 + (i) * 0x20)
#define MXR_GRAPHIC_SPAN(i) (0x0028 + (i) * 0x20)
#define MXR_GRAPHIC_SXY(i) (0x002C + (i) * 0x20)
#define MXR_GRAPHIC_WH(i) (0x0030 + (i) * 0x20)
#define MXR_GRAPHIC_DXY(i) (0x0034 + (i) * 0x20)
/*
* Bit definition part
*/
/* generates mask for range of bits */
#define MXR_MASK(high_bit, low_bit) \
(((2 << ((high_bit) - (low_bit))) - 1) << (low_bit))
#define MXR_MASK_VAL(val, high_bit, low_bit) \
(((val) << (low_bit)) & MXR_MASK(high_bit, low_bit))
/* bits for MXR_STATUS */
#define MXR_STATUS_16_BURST (1 << 7)
#define MXR_STATUS_BURST_MASK (1 << 7)
#define MXR_STATUS_SYNC_ENABLE (1 << 2)
#define MXR_STATUS_REG_RUN (1 << 0)
/* bits for MXR_CFG */
#define MXR_CFG_OUT_YUV444 (0 << 8)
#define MXR_CFG_OUT_RGB888 (1 << 8)
#define MXR_CFG_DST_SDO (0 << 7)
#define MXR_CFG_DST_HDMI (1 << 7)
#define MXR_CFG_DST_MASK (1 << 7)
#define MXR_CFG_SCAN_HD_720 (0 << 6)
#define MXR_CFG_SCAN_HD_1080 (1 << 6)
#define MXR_CFG_GRP1_ENABLE (1 << 5)
#define MXR_CFG_GRP0_ENABLE (1 << 4)
#define MXR_CFG_VP_ENABLE (1 << 3)
#define MXR_CFG_SCAN_INTERLACE (0 << 2)
#define MXR_CFG_SCAN_PROGRASSIVE (1 << 2)
#define MXR_CFG_SCAN_NTSC (0 << 1)
#define MXR_CFG_SCAN_PAL (1 << 1)
#define MXR_CFG_SCAN_SD (0 << 0)
#define MXR_CFG_SCAN_HD (1 << 0)
#define MXR_CFG_SCAN_MASK 0x47
/* bits for MXR_GRAPHICn_CFG */
#define MXR_GRP_CFG_COLOR_KEY_DISABLE (1 << 21)
#define MXR_GRP_CFG_BLEND_PRE_MUL (1 << 20)
#define MXR_GRP_CFG_FORMAT_VAL(x) MXR_MASK_VAL(x, 11, 8)
#define MXR_GRP_CFG_FORMAT_MASK MXR_GRP_CFG_FORMAT_VAL(~0)
#define MXR_GRP_CFG_ALPHA_VAL(x) MXR_MASK_VAL(x, 7, 0)
/* bits for MXR_GRAPHICn_WH */
#define MXR_GRP_WH_H_SCALE(x) MXR_MASK_VAL(x, 28, 28)
#define MXR_GRP_WH_V_SCALE(x) MXR_MASK_VAL(x, 12, 12)
#define MXR_GRP_WH_WIDTH(x) MXR_MASK_VAL(x, 26, 16)
#define MXR_GRP_WH_HEIGHT(x) MXR_MASK_VAL(x, 10, 0)
/* bits for MXR_GRAPHICn_SXY */
#define MXR_GRP_SXY_SX(x) MXR_MASK_VAL(x, 26, 16)
#define MXR_GRP_SXY_SY(x) MXR_MASK_VAL(x, 10, 0)
/* bits for MXR_GRAPHICn_DXY */
#define MXR_GRP_DXY_DX(x) MXR_MASK_VAL(x, 26, 16)
#define MXR_GRP_DXY_DY(x) MXR_MASK_VAL(x, 10, 0)
/* bits for MXR_INT_EN */
#define MXR_INT_EN_VSYNC (1 << 11)
#define MXR_INT_EN_ALL (0x0f << 8)
/* bit for MXR_INT_STATUS */
#define MXR_INT_CLEAR_VSYNC (1 << 11)
#define MXR_INT_STATUS_VSYNC (1 << 0)
/* bit for MXR_LAYER_CFG */
#define MXR_LAYER_CFG_GRP1_VAL(x) MXR_MASK_VAL(x, 11, 8)
#define MXR_LAYER_CFG_GRP0_VAL(x) MXR_MASK_VAL(x, 7, 4)
#define MXR_LAYER_CFG_VP_VAL(x) MXR_MASK_VAL(x, 3, 0)
#endif /* SAMSUNG_REGS_MIXER_H */
/*
* Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
* http://www.samsung.com/
*
* Video processor register header file for Samsung Mixer driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef SAMSUNG_REGS_VP_H
#define SAMSUNG_REGS_VP_H
/*
* Register part
*/
#define VP_ENABLE 0x0000
#define VP_SRESET 0x0004
#define VP_SHADOW_UPDATE 0x0008
#define VP_FIELD_ID 0x000C
#define VP_MODE 0x0010
#define VP_IMG_SIZE_Y 0x0014
#define VP_IMG_SIZE_C 0x0018
#define VP_PER_RATE_CTRL 0x001C
#define VP_TOP_Y_PTR 0x0028
#define VP_BOT_Y_PTR 0x002C
#define VP_TOP_C_PTR 0x0030
#define VP_BOT_C_PTR 0x0034
#define VP_ENDIAN_MODE 0x03CC
#define VP_SRC_H_POSITION 0x0044
#define VP_SRC_V_POSITION 0x0048
#define VP_SRC_WIDTH 0x004C
#define VP_SRC_HEIGHT 0x0050
#define VP_DST_H_POSITION 0x0054
#define VP_DST_V_POSITION 0x0058
#define VP_DST_WIDTH 0x005C
#define VP_DST_HEIGHT 0x0060
#define VP_H_RATIO 0x0064
#define VP_V_RATIO 0x0068
#define VP_POLY8_Y0_LL 0x006C
#define VP_POLY4_Y0_LL 0x00EC
#define VP_POLY4_C0_LL 0x012C
/*
* Bit definition part
*/
/* generates mask for range of bits */
#define VP_MASK(high_bit, low_bit) \
(((2 << ((high_bit) - (low_bit))) - 1) << (low_bit))
#define VP_MASK_VAL(val, high_bit, low_bit) \
(((val) << (low_bit)) & VP_MASK(high_bit, low_bit))
/* VP_ENABLE */
#define VP_ENABLE_ON (1 << 0)
/* VP_SRESET */
#define VP_SRESET_PROCESSING (1 << 0)
/* VP_SHADOW_UPDATE */
#define VP_SHADOW_UPDATE_ENABLE (1 << 0)
/* VP_MODE */
#define VP_MODE_NV12 (0 << 6)
#define VP_MODE_NV21 (1 << 6)
#define VP_MODE_LINE_SKIP (1 << 5)
#define VP_MODE_MEM_LINEAR (0 << 4)
#define VP_MODE_MEM_TILED (1 << 4)
#define VP_MODE_FMT_MASK (5 << 4)
#define VP_MODE_FIELD_ID_AUTO_TOGGLING (1 << 2)
#define VP_MODE_2D_IPC (1 << 1)
/* VP_IMG_SIZE_Y */
/* VP_IMG_SIZE_C */
#define VP_IMG_HSIZE(x) VP_MASK_VAL(x, 29, 16)
#define VP_IMG_VSIZE(x) VP_MASK_VAL(x, 13, 0)
/* VP_SRC_H_POSITION */
#define VP_SRC_H_POSITION_VAL(x) VP_MASK_VAL(x, 14, 4)
/* VP_ENDIAN_MODE */
#define VP_ENDIAN_MODE_LITTLE (1 << 0)
#endif /* SAMSUNG_REGS_VP_H */
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册