730 lines
20 KiB
C
730 lines
20 KiB
C
/*
|
|
* linux-3.10/drivers/media/platform/sunxi-vin/vin-vipp/sunxi_scaler.c
|
|
*
|
|
* Copyright (c) 2007-2017 Allwinnertech Co., Ltd.
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
|
|
/*
|
|
*****************************************************************************
|
|
*
|
|
* sunxi_scaler.c
|
|
*
|
|
* Hawkview ISP - sunxi_scaler.c module
|
|
*
|
|
* Copyright (c) 2014 by Allwinnertech Co., Ltd. http://www.allwinnertech.com
|
|
*
|
|
* Version Author Date Description
|
|
*
|
|
* 3.0 Zhao Wei 2014/12/11 ISP Tuning Tools Support
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include <linux/platform_device.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <media/v4l2-device.h>
|
|
#include <media/v4l2-mediabus.h>
|
|
#include <media/v4l2-subdev.h>
|
|
#include "../platform/platform_cfg.h"
|
|
#include "sunxi_scaler.h"
|
|
#include "../vin-video/vin_core.h"
|
|
#include "vipp_reg.h"
|
|
|
|
#define SCALER_MODULE_NAME "vin_scaler"
|
|
|
|
#define ALIGN_4B(x) (((x) + (3)) & ~(3))
|
|
|
|
static LIST_HEAD(scaler_drv_list);
|
|
|
|
#define MIN_IN_WIDTH 32
|
|
#define MIN_IN_HEIGHT 32
|
|
#define MAX_IN_WIDTH 4095
|
|
#define MAX_IN_HEIGHT 4095
|
|
|
|
#define MIN_OUT_WIDTH 16
|
|
#define MIN_OUT_HEIGHT 2
|
|
#define MAX_OUT_WIDTH 4095
|
|
#define MAX_OUT_HEIGHT 4095
|
|
|
|
static struct v4l2_mbus_framefmt *__scaler_get_format(struct scaler_dev *scaler,
|
|
struct v4l2_subdev_fh *fh,
|
|
unsigned int pad,
|
|
enum v4l2_subdev_format_whence which)
|
|
{
|
|
if (which == V4L2_SUBDEV_FORMAT_TRY)
|
|
return fh ? v4l2_subdev_get_try_format(fh, pad) : NULL;
|
|
else
|
|
return &scaler->formats[pad];
|
|
}
|
|
|
|
static struct v4l2_rect *__scaler_get_crop(struct scaler_dev *scaler,
|
|
struct v4l2_subdev_fh *fh,
|
|
enum v4l2_subdev_format_whence which)
|
|
{
|
|
if (which == V4L2_SUBDEV_FORMAT_TRY)
|
|
return fh ? v4l2_subdev_get_try_crop(fh, SCALER_PAD_SINK) : NULL;
|
|
else
|
|
return &scaler->crop.request;
|
|
}
|
|
|
|
static void __scaler_try_crop(const struct v4l2_mbus_framefmt *sink,
|
|
const struct v4l2_mbus_framefmt *source,
|
|
struct v4l2_rect *crop)
|
|
{
|
|
|
|
unsigned int min_width = source->width;
|
|
unsigned int min_height = source->height;
|
|
unsigned int max_width = sink->width;
|
|
unsigned int max_height = sink->height;
|
|
|
|
crop->width = clamp_t(u32, crop->width, min_width, max_width);
|
|
crop->height = clamp_t(u32, crop->height, min_height, max_height);
|
|
|
|
/* Crop can not go beyond of the input rectangle */
|
|
crop->left = clamp_t(u32, crop->left, 0, sink->width - MIN_IN_WIDTH);
|
|
crop->width = \
|
|
clamp_t(u32, crop->width, MIN_IN_WIDTH, sink->width - crop->left);
|
|
crop->top = clamp_t(u32, crop->top, 0, sink->height - MIN_IN_HEIGHT);
|
|
crop->height = \
|
|
clamp_t(u32, crop->height, MIN_IN_HEIGHT, sink->height - crop->top);
|
|
}
|
|
|
|
static int __scaler_w_shift(int x_ratio, int y_ratio)
|
|
{
|
|
int m, n;
|
|
int sum_weight = 0;
|
|
int weight_shift = -8;
|
|
int xr = (x_ratio >> 8) + 1;
|
|
int yr = (y_ratio >> 8) + 1;
|
|
|
|
for (m = 0; m <= xr; m++) {
|
|
for (n = 0; n <= yr; n++) {
|
|
sum_weight += (y_ratio - abs((n << 8) - (yr << 7)))
|
|
* (x_ratio - abs((m << 8) - (xr << 7)));
|
|
}
|
|
}
|
|
sum_weight >>= 8;
|
|
while (sum_weight != 0) {
|
|
weight_shift++;
|
|
sum_weight >>= 1;
|
|
}
|
|
return weight_shift;
|
|
}
|
|
|
|
static void __scaler_calc_ratios(struct scaler_dev *scaler,
|
|
struct v4l2_rect *input,
|
|
struct v4l2_mbus_framefmt *output,
|
|
struct scaler_para *para)
|
|
{
|
|
unsigned int width;
|
|
unsigned int height;
|
|
unsigned int r_min;
|
|
|
|
output->width = clamp_t(u32, output->width, MIN_IN_WIDTH, input->width);
|
|
output->height = \
|
|
clamp_t(u32, output->height, MIN_IN_HEIGHT, input->height);
|
|
|
|
para->xratio = 256 * input->width / output->width;
|
|
para->yratio = 256 * input->height / output->height;
|
|
para->xratio = clamp_t(u32, para->xratio, 256, 2048);
|
|
para->yratio = clamp_t(u32, para->yratio, 256, 2048);
|
|
|
|
r_min = min(para->xratio, para->yratio);
|
|
#ifdef CROP_AFTER_SCALER
|
|
width = ALIGN_4B(256 * input->width / r_min);
|
|
height = 256 * input->height / r_min;
|
|
para->xratio = 256 * input->width / width;
|
|
para->yratio = 256 * input->height / height;
|
|
para->xratio = clamp_t(u32, para->xratio, 256, 2048);
|
|
para->yratio = clamp_t(u32, para->yratio, 256, 2048);
|
|
para->width = width;
|
|
para->height = height;
|
|
vin_log(VIN_LOG_SCALER, "para: xr = %d, yr = %d, w = %d, h = %d\n",
|
|
para->xratio, para->yratio, para->width, para->height);
|
|
|
|
output->width = width;
|
|
output->height = height;
|
|
#else
|
|
width = output->width * r_min / 256;
|
|
height = output->height * r_min / 256;
|
|
para->xratio = r_min;
|
|
para->yratio = r_min;
|
|
para->width = output->width;
|
|
para->height = output->height;
|
|
vin_log(VIN_LOG_SCALER, "para: xr = %d, yr = %d, w = %d, h = %d\n",
|
|
para->xratio, para->yratio, para->width, para->height);
|
|
/* Center the new crop rectangle.
|
|
* crop is before scaler
|
|
*/
|
|
input->left += (input->width - width) / 2;
|
|
input->top += (input->height - height) / 2;
|
|
input->width = width;
|
|
input->height = height;
|
|
#endif
|
|
vin_log(VIN_LOG_SCALER, "crop: left = %d, top = %d, w = %d, h = %d\n",
|
|
input->left, input->top, input->width, input->height);
|
|
}
|
|
|
|
static void __scaler_try_format(struct scaler_dev *scaler,
|
|
struct v4l2_subdev_fh *fh, unsigned int pad,
|
|
struct v4l2_mbus_framefmt *fmt,
|
|
enum v4l2_subdev_format_whence which)
|
|
{
|
|
struct v4l2_mbus_framefmt *format;
|
|
struct scaler_para ratio;
|
|
struct v4l2_rect crop;
|
|
|
|
switch (pad) {
|
|
case SCALER_PAD_SINK:
|
|
fmt->width =
|
|
clamp_t(u32, fmt->width, MIN_IN_WIDTH, MAX_IN_WIDTH);
|
|
fmt->height =
|
|
clamp_t(u32, fmt->height, MIN_IN_HEIGHT, MAX_IN_HEIGHT);
|
|
break;
|
|
case SCALER_PAD_SOURCE:
|
|
format =
|
|
__scaler_get_format(scaler, fh, SCALER_PAD_SINK, which);
|
|
fmt->code = format->code;
|
|
|
|
crop = *__scaler_get_crop(scaler, fh, which);
|
|
__scaler_calc_ratios(scaler, &crop, fmt, &ratio);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int sunxi_scaler_enum_mbus_code(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_fh *fh,
|
|
struct v4l2_subdev_mbus_code_enum *code)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_scaler_enum_frame_size(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_fh *fh,
|
|
struct v4l2_subdev_frame_size_enum *fse)
|
|
{
|
|
struct scaler_dev *scaler = v4l2_get_subdevdata(sd);
|
|
struct v4l2_mbus_framefmt format;
|
|
|
|
if (fse->index != 0)
|
|
return -EINVAL;
|
|
|
|
format.code = fse->code;
|
|
format.width = 1;
|
|
format.height = 1;
|
|
__scaler_try_format(scaler, fh, fse->pad, &format,
|
|
V4L2_SUBDEV_FORMAT_ACTIVE);
|
|
fse->min_width = format.width;
|
|
fse->min_height = format.height;
|
|
|
|
if (format.code != fse->code)
|
|
return -EINVAL;
|
|
|
|
format.code = fse->code;
|
|
format.width = -1;
|
|
format.height = -1;
|
|
__scaler_try_format(scaler, fh, fse->pad, &format,
|
|
V4L2_SUBDEV_FORMAT_ACTIVE);
|
|
fse->max_width = format.width;
|
|
fse->max_height = format.height;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_scaler_subdev_get_fmt(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_fh *fh,
|
|
struct v4l2_subdev_format *fmt)
|
|
{
|
|
struct scaler_dev *scaler = v4l2_get_subdevdata(sd);
|
|
struct v4l2_mbus_framefmt *format;
|
|
|
|
format = __scaler_get_format(scaler, fh, fmt->pad, fmt->which);
|
|
if (format == NULL)
|
|
return -EINVAL;
|
|
|
|
fmt->format = *format;
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_scaler_subdev_set_fmt(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_fh *fh,
|
|
struct v4l2_subdev_format *fmt)
|
|
{
|
|
struct scaler_dev *scaler = v4l2_get_subdevdata(sd);
|
|
struct v4l2_mbus_framefmt *format;
|
|
struct v4l2_rect *crop;
|
|
|
|
format = __scaler_get_format(scaler, fh, fmt->pad, fmt->which);
|
|
if (format == NULL)
|
|
return -EINVAL;
|
|
vin_log(VIN_LOG_FMT, "%s %d*%d %x %d\n", __func__, fmt->format.width,
|
|
fmt->format.height, fmt->format.code, fmt->format.field);
|
|
__scaler_try_format(scaler, fh, fmt->pad, &fmt->format, fmt->which);
|
|
*format = fmt->format;
|
|
|
|
if (fmt->pad == SCALER_PAD_SINK) {
|
|
/* reset crop rectangle */
|
|
crop = __scaler_get_crop(scaler, fh, fmt->which);
|
|
crop->left = 0;
|
|
crop->top = 0;
|
|
crop->width = fmt->format.width;
|
|
crop->height = fmt->format.height;
|
|
|
|
/* Propagate the format from sink to source */
|
|
format = __scaler_get_format(scaler, fh, SCALER_PAD_SOURCE,\
|
|
fmt->which);
|
|
*format = fmt->format;
|
|
__scaler_try_format(scaler, fh, SCALER_PAD_SOURCE, format,\
|
|
fmt->which);
|
|
}
|
|
|
|
if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
|
|
scaler->crop.active = scaler->crop.request;
|
|
__scaler_calc_ratios(scaler, &scaler->crop.active, format,\
|
|
&scaler->para);
|
|
}
|
|
|
|
/*return the format for other subdev*/
|
|
fmt->format = *format;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_scaler_subdev_get_selection(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_fh *fh,
|
|
struct v4l2_subdev_selection *sel)
|
|
{
|
|
struct scaler_dev *scaler = v4l2_get_subdevdata(sd);
|
|
struct v4l2_mbus_framefmt *format_source;
|
|
struct v4l2_mbus_framefmt *format_sink;
|
|
struct scaler_para para;
|
|
|
|
vin_print("%s\n", __func__);
|
|
|
|
if (sel->pad != SCALER_PAD_SINK)
|
|
return -EINVAL;
|
|
|
|
format_sink = \
|
|
__scaler_get_format(scaler, fh, SCALER_PAD_SINK, sel->which);
|
|
format_source = \
|
|
__scaler_get_format(scaler, fh, SCALER_PAD_SOURCE, sel->which);
|
|
|
|
switch (sel->target) {
|
|
case V4L2_SEL_TGT_CROP_BOUNDS:
|
|
sel->r.left = 0;
|
|
sel->r.top = 0;
|
|
sel->r.width = INT_MAX;
|
|
sel->r.height = INT_MAX;
|
|
__scaler_try_crop(format_sink, format_source, &sel->r);
|
|
__scaler_calc_ratios(scaler, &sel->r, format_source, ¶);
|
|
break;
|
|
case V4L2_SEL_TGT_CROP:
|
|
sel->r = *__scaler_get_crop(scaler, fh, sel->which);
|
|
__scaler_calc_ratios(scaler, &sel->r, format_source, ¶);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_scaler_subdev_set_selection(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_fh *fh,
|
|
struct v4l2_subdev_selection *sel)
|
|
{
|
|
struct scaler_dev *scaler = v4l2_get_subdevdata(sd);
|
|
struct v4l2_mbus_framefmt *format_sink, *format_source;
|
|
struct scaler_para para;
|
|
|
|
if (sel->target != V4L2_SEL_TGT_CROP || sel->pad != SCALER_PAD_SINK)
|
|
return -EINVAL;
|
|
|
|
format_sink = \
|
|
__scaler_get_format(scaler, fh, SCALER_PAD_SINK, sel->which);
|
|
format_source = \
|
|
__scaler_get_format(scaler, fh, SCALER_PAD_SOURCE, sel->which);
|
|
|
|
vin_print("%s: L = %d, T = %d, W = %d, H = %d\n", __func__,
|
|
sel->r.left, sel->r.top, sel->r.width, sel->r.height);
|
|
|
|
vin_print("%s: input = %dx%d, output = %dx%d\n", __func__,
|
|
format_sink->width, format_sink->height,
|
|
format_source->width, format_source->height);
|
|
|
|
__scaler_try_crop(format_sink, format_source, &sel->r);
|
|
*__scaler_get_crop(scaler, fh, sel->which) = sel->r;
|
|
__scaler_calc_ratios(scaler, &sel->r, format_source, ¶);
|
|
|
|
if (sel->which == V4L2_SUBDEV_FORMAT_TRY)
|
|
return 0;
|
|
|
|
scaler->para = para;
|
|
scaler->crop.active = sel->r;
|
|
vin_log(VIN_LOG_SCALER, "active crop: l = %d, t = %d, w = %d, h = %d\n",
|
|
scaler->crop.active.left, scaler->crop.active.top,
|
|
scaler->crop.active.width, scaler->crop.active.height);
|
|
return 0;
|
|
}
|
|
|
|
int sunxi_scaler_subdev_init(struct v4l2_subdev *sd, u32 val)
|
|
{
|
|
struct scaler_dev *scaler = v4l2_get_subdevdata(sd);
|
|
|
|
vin_print("%s, val = %d.\n", __func__, val);
|
|
if (val) {
|
|
vipp_set_reg_load_addr(scaler->id, (unsigned long)scaler->vipp_reg.dma_addr);
|
|
vipp_set_osd_para_load_addr(scaler->id, (unsigned long)scaler->osd_para.dma_addr);
|
|
vipp_set_osd_stat_load_addr(scaler->id, (unsigned long)scaler->osd_stat.dma_addr);
|
|
vipp_set_osd_cv_update(scaler->id, NOT_UPDATED);
|
|
vipp_set_osd_ov_update(scaler->id, NOT_UPDATED);
|
|
vipp_set_para_ready(scaler->id, NOT_READY);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_scaler_subdev_s_power(struct v4l2_subdev *sd, int enable)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_scaler_subdev_s_stream(struct v4l2_subdev *sd, int enable)
|
|
{
|
|
struct scaler_dev *scaler = v4l2_get_subdevdata(sd);
|
|
struct v4l2_mbus_framefmt *mf = &scaler->formats[SCALER_PAD_SOURCE];
|
|
struct mbus_framefmt_res *res = (void *)scaler->formats[SCALER_PAD_SOURCE].reserved;
|
|
struct vipp_scaler_config scaler_cfg;
|
|
struct vipp_scaler_size scaler_size;
|
|
struct vipp_crop crop;
|
|
enum vipp_format out_fmt;
|
|
|
|
switch (res->res_pix_fmt) {
|
|
case V4L2_PIX_FMT_SBGGR8:
|
|
case V4L2_PIX_FMT_SGBRG8:
|
|
case V4L2_PIX_FMT_SGRBG8:
|
|
case V4L2_PIX_FMT_SRGGB8:
|
|
case V4L2_PIX_FMT_SBGGR10:
|
|
case V4L2_PIX_FMT_SGBRG10:
|
|
case V4L2_PIX_FMT_SGRBG10:
|
|
case V4L2_PIX_FMT_SRGGB10:
|
|
case V4L2_PIX_FMT_SBGGR12:
|
|
case V4L2_PIX_FMT_SGBRG12:
|
|
case V4L2_PIX_FMT_SGRBG12:
|
|
case V4L2_PIX_FMT_SRGGB12:
|
|
vin_print("%s output fmt is raw, return directly\n", __func__);
|
|
return 0;
|
|
default:
|
|
break;
|
|
}
|
|
if (mf->field == V4L2_FIELD_INTERLACED || mf->field == V4L2_FIELD_TOP ||
|
|
mf->field == V4L2_FIELD_BOTTOM) {
|
|
vin_print("Scaler not support field mode, return directly!\n");
|
|
return 0;
|
|
}
|
|
|
|
if (enable) {
|
|
crop.hor = scaler->crop.active.left;
|
|
crop.ver = scaler->crop.active.top;
|
|
crop.width = scaler->crop.active.width;
|
|
crop.height = scaler->crop.active.height;
|
|
vipp_set_crop(scaler->id, &crop);
|
|
scaler_size.sc_width = scaler->para.width;
|
|
scaler_size.sc_height = scaler->para.height;
|
|
vipp_scaler_output_size(scaler->id, &scaler_size);
|
|
|
|
switch (res->res_pix_fmt) {
|
|
case V4L2_PIX_FMT_YUV420:
|
|
case V4L2_PIX_FMT_YUV420M:
|
|
case V4L2_PIX_FMT_YVU420:
|
|
case V4L2_PIX_FMT_YVU420M:
|
|
case V4L2_PIX_FMT_NV21:
|
|
case V4L2_PIX_FMT_NV21M:
|
|
case V4L2_PIX_FMT_NV12:
|
|
case V4L2_PIX_FMT_NV12M:
|
|
out_fmt = YUV420;
|
|
break;
|
|
case V4L2_PIX_FMT_YUV422P:
|
|
case V4L2_PIX_FMT_NV16:
|
|
case V4L2_PIX_FMT_NV61:
|
|
out_fmt = YUV422;
|
|
break;
|
|
default:
|
|
out_fmt = YUV420;
|
|
break;
|
|
}
|
|
if (scaler->is_osd_en)
|
|
scaler_cfg.sc_out_fmt = YUV422;
|
|
else
|
|
scaler_cfg.sc_out_fmt = out_fmt;
|
|
scaler_cfg.sc_x_ratio = scaler->para.xratio;
|
|
scaler_cfg.sc_y_ratio = scaler->para.yratio;
|
|
scaler_cfg.sc_w_shift = __scaler_w_shift(scaler->para.xratio, scaler->para.yratio);
|
|
vipp_scaler_cfg(scaler->id, &scaler_cfg);
|
|
vipp_output_fmt_cfg(scaler->id, out_fmt);
|
|
vipp_scaler_en(scaler->id, 1);
|
|
vipp_set_para_ready(scaler->id, HAS_READY);
|
|
vipp_set_osd_ov_update(scaler->id, HAS_UPDATED);
|
|
vipp_set_osd_cv_update(scaler->id, HAS_UPDATED);
|
|
vipp_top_clk_en(scaler->id, enable);
|
|
vipp_enable(scaler->id);
|
|
} else {
|
|
vipp_disable(scaler->id);
|
|
vipp_top_clk_en(scaler->id, enable);
|
|
vipp_scaler_en(scaler->id, 0);
|
|
vipp_set_para_ready(scaler->id, NOT_READY);
|
|
vipp_set_osd_ov_update(scaler->id, NOT_UPDATED);
|
|
vipp_set_osd_cv_update(scaler->id, NOT_UPDATED);
|
|
}
|
|
vin_print("%s on = %d, %d*%d xr = %d yr = %d\n", __func__, enable,
|
|
scaler->para.width, scaler->para.height,
|
|
scaler->para.xratio, scaler->para.yratio);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct v4l2_subdev_core_ops sunxi_scaler_subdev_core_ops = {
|
|
.s_power = sunxi_scaler_subdev_s_power,
|
|
.init = sunxi_scaler_subdev_init,
|
|
};
|
|
static const struct v4l2_subdev_video_ops sunxi_scaler_subdev_video_ops = {
|
|
.s_stream = sunxi_scaler_subdev_s_stream,
|
|
};
|
|
/* subdev pad operations */
|
|
static const struct v4l2_subdev_pad_ops sunxi_scaler_subdev_pad_ops = {
|
|
.enum_mbus_code = sunxi_scaler_enum_mbus_code,
|
|
.enum_frame_size = sunxi_scaler_enum_frame_size,
|
|
.get_fmt = sunxi_scaler_subdev_get_fmt,
|
|
.set_fmt = sunxi_scaler_subdev_set_fmt,
|
|
.get_selection = sunxi_scaler_subdev_get_selection,
|
|
.set_selection = sunxi_scaler_subdev_set_selection,
|
|
};
|
|
|
|
static struct v4l2_subdev_ops sunxi_scaler_subdev_ops = {
|
|
.core = &sunxi_scaler_subdev_core_ops,
|
|
.video = &sunxi_scaler_subdev_video_ops,
|
|
.pad = &sunxi_scaler_subdev_pad_ops,
|
|
};
|
|
|
|
int __scaler_init_subdev(struct scaler_dev *scaler)
|
|
{
|
|
int ret;
|
|
struct v4l2_subdev *sd = &scaler->subdev;
|
|
mutex_init(&scaler->subdev_lock);
|
|
|
|
v4l2_subdev_init(sd, &sunxi_scaler_subdev_ops);
|
|
sd->grp_id = VIN_GRP_ID_SCALER;
|
|
sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE;
|
|
snprintf(sd->name, sizeof(sd->name), "sunxi_scaler.%u", scaler->id);
|
|
v4l2_set_subdevdata(sd, scaler);
|
|
|
|
/*sd->entity->ops = &isp_media_ops;*/
|
|
scaler->scaler_pads[SCALER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
|
|
scaler->scaler_pads[SCALER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
|
|
sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV;
|
|
|
|
ret = media_entity_init(&sd->entity, SCALER_PAD_NUM,
|
|
scaler->scaler_pads, 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
return 0;
|
|
}
|
|
|
|
static int scaler_resource_alloc(struct scaler_dev *scaler)
|
|
{
|
|
int ret = 0;
|
|
scaler->vipp_reg.size = VIPP_REG_SIZE;
|
|
scaler->osd_para.size = OSD_PARA_SIZE;
|
|
scaler->osd_stat.size = OSD_STAT_SIZE;
|
|
|
|
ret = os_mem_alloc(&scaler->pdev->dev, &scaler->vipp_reg);
|
|
if (ret < 0) {
|
|
vin_err("scaler regs load addr requset failed!\n");
|
|
return -ENOMEM;
|
|
}
|
|
ret = os_mem_alloc(&scaler->pdev->dev, &scaler->osd_para);
|
|
if (ret < 0) {
|
|
vin_err("scaler para load addr requset failed!\n");
|
|
return -ENOMEM;
|
|
}
|
|
ret = os_mem_alloc(&scaler->pdev->dev, &scaler->osd_stat);
|
|
if (ret < 0) {
|
|
vin_err("scaler statistic load addr requset failed!\n");
|
|
return -ENOMEM;
|
|
}
|
|
return 0;
|
|
|
|
}
|
|
|
|
static void scaler_resource_free(struct scaler_dev *scaler)
|
|
{
|
|
os_mem_free(&scaler->pdev->dev, &scaler->vipp_reg);
|
|
os_mem_free(&scaler->pdev->dev, &scaler->osd_para);
|
|
os_mem_free(&scaler->pdev->dev, &scaler->osd_stat);
|
|
}
|
|
|
|
static int scaler_probe(struct platform_device *pdev)
|
|
{
|
|
struct device_node *np = pdev->dev.of_node;
|
|
struct scaler_dev *scaler = NULL;
|
|
int ret = 0;
|
|
if (np == NULL) {
|
|
vin_err("Scaler failed to get of node\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
scaler = kzalloc(sizeof(struct scaler_dev), GFP_KERNEL);
|
|
if (!scaler) {
|
|
ret = -ENOMEM;
|
|
vin_err("sunxi scaler kzalloc failed!\n");
|
|
goto ekzalloc;
|
|
}
|
|
of_property_read_u32(np, "device_id", &pdev->id);
|
|
if (pdev->id < 0) {
|
|
vin_err("Scaler failed to get device id\n");
|
|
ret = -EINVAL;
|
|
goto freedev;
|
|
}
|
|
|
|
scaler->id = pdev->id;
|
|
scaler->pdev = pdev;
|
|
|
|
scaler->base = of_iomap(np, 0);
|
|
if (!scaler->base) {
|
|
scaler->is_empty = 1;
|
|
scaler->base = kzalloc(0x400, GFP_KERNEL);
|
|
if (!scaler->base) {
|
|
ret = -EIO;
|
|
goto freedev;
|
|
}
|
|
}
|
|
|
|
list_add_tail(&scaler->scaler_list, &scaler_drv_list);
|
|
__scaler_init_subdev(scaler);
|
|
|
|
spin_lock_init(&scaler->slock);
|
|
init_waitqueue_head(&scaler->wait);
|
|
|
|
if (scaler_resource_alloc(scaler) < 0) {
|
|
ret = -ENOMEM;
|
|
goto unmap;
|
|
}
|
|
|
|
vipp_set_base_addr(scaler->id, (unsigned long)scaler->base);
|
|
vipp_map_reg_load_addr(scaler->id, (unsigned long)scaler->vipp_reg.vir_addr);
|
|
vipp_map_osd_para_load_addr(scaler->id, (unsigned long)scaler->osd_para.vir_addr);
|
|
memset(scaler->vipp_reg.vir_addr, 0, VIPP_REG_SIZE);
|
|
memset(scaler->osd_para.vir_addr, 0, OSD_PARA_SIZE);
|
|
memset(scaler->osd_stat.vir_addr, 0, OSD_STAT_SIZE);
|
|
|
|
platform_set_drvdata(pdev, scaler);
|
|
|
|
vin_print("scaler%d probe end\n", scaler->id);
|
|
return 0;
|
|
unmap:
|
|
if (!scaler->is_empty)
|
|
iounmap(scaler->base);
|
|
else
|
|
kfree(scaler->base);
|
|
freedev:
|
|
kfree(scaler);
|
|
ekzalloc:
|
|
vin_err("scaler%d probe err!\n", scaler->id);
|
|
return ret;
|
|
}
|
|
|
|
static int scaler_remove(struct platform_device *pdev)
|
|
{
|
|
struct scaler_dev *scaler = platform_get_drvdata(pdev);
|
|
struct v4l2_subdev *sd = &scaler->subdev;
|
|
scaler_resource_free(scaler);
|
|
platform_set_drvdata(pdev, NULL);
|
|
v4l2_device_unregister_subdev(sd);
|
|
v4l2_set_subdevdata(sd, NULL);
|
|
list_del(&scaler->scaler_list);
|
|
if (scaler->base) {
|
|
if (!scaler->is_empty)
|
|
iounmap(scaler->base);
|
|
else
|
|
kfree(scaler->base);
|
|
}
|
|
kfree(scaler);
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id sunxi_scaler_match[] = {
|
|
{.compatible = "allwinner,sunxi-scaler",},
|
|
{},
|
|
};
|
|
|
|
static struct platform_driver scaler_platform_driver = {
|
|
.probe = scaler_probe,
|
|
.remove = scaler_remove,
|
|
.driver = {
|
|
.name = SCALER_MODULE_NAME,
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = sunxi_scaler_match,
|
|
}
|
|
};
|
|
|
|
struct v4l2_subdev *sunxi_scaler_get_subdev(int id)
|
|
{
|
|
struct scaler_dev *scaler;
|
|
list_for_each_entry(scaler, &scaler_drv_list, scaler_list) {
|
|
if (scaler->id == id) {
|
|
scaler->use_cnt++;
|
|
return &scaler->subdev;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int sunxi_vipp_get_osd_stat(int id, unsigned int *stat)
|
|
{
|
|
struct v4l2_subdev *sd = sunxi_scaler_get_subdev(id);
|
|
struct scaler_dev *vipp = v4l2_get_subdevdata(sd);
|
|
unsigned long long *stat_buf = vipp->osd_stat.vir_addr;
|
|
int i;
|
|
|
|
if (stat_buf == NULL || stat == NULL)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < MAX_OVERLAY_NUM; i++)
|
|
stat[i] = stat_buf[i];
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sunxi_osd_change_sc_fmt(int id, enum vipp_format out_fmt, int en)
|
|
{
|
|
struct v4l2_subdev *sd = sunxi_scaler_get_subdev(id);
|
|
struct scaler_dev *vipp = v4l2_get_subdevdata(sd);
|
|
|
|
vipp->is_osd_en = en;
|
|
|
|
vipp_scaler_output_fmt(id, out_fmt);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sunxi_scaler_platform_register(void)
|
|
{
|
|
return platform_driver_register(&scaler_platform_driver);
|
|
}
|
|
|
|
void sunxi_scaler_platform_unregister(void)
|
|
{
|
|
platform_driver_unregister(&scaler_platform_driver);
|
|
vin_print("scaler_exit end\n");
|
|
}
|