diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2013-09-05 22:55:59 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2013-09-05 22:55:59 +0400 |
commit | 27c053aa8d18d1fa7b83041e36bad20bcdf55514 (patch) | |
tree | c59dce17a248dd8f4757eca3823032334c626dcd /drivers | |
parent | a09e9a7a4b907f2dfa9bdb2b98a1828ab4b340b2 (diff) | |
parent | f66b2a1c7f2ae3fb0d5b67d07ab4f5055fd3cf16 (diff) | |
download | linux-27c053aa8d18d1fa7b83041e36bad20bcdf55514.tar.xz |
Merge branch 'v4l_for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media
Pull media updates from Mauro Carvalho Chehab:
"This series contains:
- Exynos s5p-mfc driver got support for VP8 encoder
- Some SoC drivers gained support for asynchronous registration
(needed for DT)
- The RC subsystem gained support for RC activity LED;
- New drivers added: a video decoder(adv7842), a video encoder
(adv7511), a new GSPCA driver (stk1135) and support for Renesas
R-Car (vsp1)
- the first SDR kernel driver: mirics msi3101. Due to some troubles
with the driver, and because the API is still under discussion, it
will be merged at staging for 3.12. Need to rework on it
- usual new boards additions, fixes, cleanups and driver
improvements"
* 'v4l_for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media: (242 commits)
[media] cx88: Fix regression: CX88_AUDIO_WM8775 can't be 0
[media] exynos4-is: Fix entity unregistration on error path
[media] exynos-gsc: Register v4l2 device
[media] exynos4-is: Fix fimc-lite bayer formats
[media] em28xx: fix assignment of the eeprom data
[media] hdpvr: fix iteration over uninitialized lists in hdpvr_probe()
[media] usbtv: Throw corrupted frames away
[media] usbtv: Fix deinterlacing
[media] v4l2: added missing mutex.h include to v4l2-ctrls.h
[media] DocBook: upgrade media_api DocBook version to 4.2
[media] ml86v7667: fix compile warning: 'ret' set but not used
[media] s5p-g2d: Fix registration failure
[media] media: coda: Fix DT driver data pointer for i.MX27
[media] s5p-mfc: Fix input/output format reporting
[media] v4l: vsp1: Fix mutex double lock at streamon time
[media] v4l: vsp1: Add support for RT clock
[media] v4l: vsp1: Initialize media device bus_info field
[media] davinci: vpif_capture: fix error return code in vpif_probe()
[media] davinci: vpif_display: fix error return code in vpif_probe()
[media] MAINTAINERS: add entries for adv7511 and adv7842
...
Diffstat (limited to 'drivers')
160 files changed, 17865 insertions, 2111 deletions
diff --git a/drivers/media/common/siano/Kconfig b/drivers/media/common/siano/Kconfig index f3f5ec44e685..f953d33ee151 100644 --- a/drivers/media/common/siano/Kconfig +++ b/drivers/media/common/siano/Kconfig @@ -23,6 +23,8 @@ config SMS_SIANO_DEBUGFS depends on SMS_SIANO_MDTV depends on DEBUG_FS depends on SMS_USB_DRV + depends on CONFIG_SMS_USB_DRV = CONFIG_SMS_SDIO_DRV + ---help--- Choose Y to enable visualizing a dump of the frontend statistics response packets via debugfs. Currently, works diff --git a/drivers/media/common/siano/smsdvb-main.c b/drivers/media/common/siano/smsdvb-main.c index 086262252230..63676a8b024c 100644 --- a/drivers/media/common/siano/smsdvb-main.c +++ b/drivers/media/common/siano/smsdvb-main.c @@ -276,7 +276,8 @@ static void smsdvb_update_per_slices(struct smsdvb_client_t *client, /* Legacy PER/BER */ tmp = p->ets_packets * 65535; - do_div(tmp, p->ts_packets + p->ets_packets); + if (p->ts_packets + p->ets_packets) + do_div(tmp, p->ts_packets + p->ets_packets); client->legacy_per = tmp; } diff --git a/drivers/media/dvb-core/dvb-usb-ids.h b/drivers/media/dvb-core/dvb-usb-ids.h index 886da16e14f2..419a2d6b4349 100644 --- a/drivers/media/dvb-core/dvb-usb-ids.h +++ b/drivers/media/dvb-core/dvb-usb-ids.h @@ -369,4 +369,6 @@ #define USB_PID_TECHNISAT_USB2_DVB_S2 0x0500 #define USB_PID_CPYTO_REDI_PC50A 0xa803 #define USB_PID_CTVDIGDUAL_V2 0xe410 +#define USB_PID_PCTV_2002E 0x025c +#define USB_PID_PCTV_2002E_SE 0x025d #endif diff --git a/drivers/media/dvb-frontends/mb86a20s.c b/drivers/media/dvb-frontends/mb86a20s.c index 856374bd3676..2c7217fb1415 100644 --- a/drivers/media/dvb-frontends/mb86a20s.c +++ b/drivers/media/dvb-frontends/mb86a20s.c @@ -157,7 +157,6 @@ static struct regdata mb86a20s_init2[] = { { 0x45, 0x04 }, /* CN symbol 4 */ { 0x48, 0x04 }, /* CN manual mode */ - { 0x50, 0xd5 }, { 0x51, 0x01 }, /* Serial */ { 0x50, 0xd6 }, { 0x51, 0x1f }, { 0x50, 0xd2 }, { 0x51, 0x03 }, { 0x50, 0xd7 }, { 0x51, 0xbf }, @@ -1860,16 +1859,15 @@ static int mb86a20s_initfe(struct dvb_frontend *fe) dev_dbg(&state->i2c->dev, "%s: IF=%d, IF reg=0x%06llx\n", __func__, state->if_freq, (long long)pll); - if (!state->config->is_serial) { + if (!state->config->is_serial) regD5 &= ~1; - rc = mb86a20s_writereg(state, 0x50, 0xd5); - if (rc < 0) - goto err; - rc = mb86a20s_writereg(state, 0x51, regD5); - if (rc < 0) - goto err; - } + rc = mb86a20s_writereg(state, 0x50, 0xd5); + if (rc < 0) + goto err; + rc = mb86a20s_writereg(state, 0x51, regD5); + if (rc < 0) + goto err; rc = mb86a20s_writeregdata(state, mb86a20s_init2); if (rc < 0) diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index b2cd8ca51af7..d18be19c96cd 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -206,6 +206,18 @@ config VIDEO_ADV7604 To compile this driver as a module, choose M here: the module will be called adv7604. +config VIDEO_ADV7842 + tristate "Analog Devices ADV7842 decoder" + depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API + ---help--- + Support for the Analog Devices ADV7842 video decoder. + + This is a Analog Devices Component/Graphics/SD Digitizer + with 2:1 Multiplexed HDMI Receiver. + + To compile this driver as a module, choose M here: the + module will be called adv7842. + config VIDEO_BT819 tristate "BT819A VideoStream decoder" depends on VIDEO_V4L2 && I2C @@ -417,6 +429,17 @@ config VIDEO_ADV7393 To compile this driver as a module, choose M here: the module will be called adv7393. +config VIDEO_ADV7511 + tristate "Analog Devices ADV7511 encoder" + depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API + ---help--- + Support for the Analog Devices ADV7511 video encoder. + + This is a Analog Devices HDMI transmitter. + + To compile this driver as a module, choose M here: the + module will be called adv7511. + config VIDEO_AD9389B tristate "Analog Devices AD9389B encoder" depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index dc20653bb5ad..9f462df77b4a 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -26,7 +26,9 @@ obj-$(CONFIG_VIDEO_ADV7183) += adv7183.o obj-$(CONFIG_VIDEO_ADV7343) += adv7343.o obj-$(CONFIG_VIDEO_ADV7393) += adv7393.o obj-$(CONFIG_VIDEO_ADV7604) += adv7604.o +obj-$(CONFIG_VIDEO_ADV7842) += adv7842.o obj-$(CONFIG_VIDEO_AD9389B) += ad9389b.o +obj-$(CONFIG_VIDEO_ADV7511) += adv7511.o obj-$(CONFIG_VIDEO_VPX3220) += vpx3220.o obj-$(CONFIG_VIDEO_VS6624) += vs6624.o obj-$(CONFIG_VIDEO_BT819) += bt819.o diff --git a/drivers/media/i2c/ad9389b.c b/drivers/media/i2c/ad9389b.c index ba4364dfae66..bb0c99d7a4f1 100644 --- a/drivers/media/i2c/ad9389b.c +++ b/drivers/media/i2c/ad9389b.c @@ -33,6 +33,7 @@ #include <linux/v4l2-dv-timings.h> #include <media/v4l2-device.h> #include <media/v4l2-common.h> +#include <media/v4l2-dv-timings.h> #include <media/v4l2-ctrls.h> #include <media/ad9389b.h> @@ -442,22 +443,11 @@ static int ad9389b_log_status(struct v4l2_subdev *sd) vic_detect, vic_sent); } } - if (state->dv_timings.type == V4L2_DV_BT_656_1120) { - struct v4l2_bt_timings *bt = bt = &state->dv_timings.bt; - u32 frame_width = bt->width + bt->hfrontporch + - bt->hsync + bt->hbackporch; - u32 frame_height = bt->height + bt->vfrontporch + - bt->vsync + bt->vbackporch; - u32 frame_size = frame_width * frame_height; - - v4l2_info(sd, "timings: %ux%u%s%u (%ux%u). Pix freq. = %u Hz. Polarities = 0x%x\n", - bt->width, bt->height, bt->interlaced ? "i" : "p", - frame_size > 0 ? (unsigned)bt->pixelclock / frame_size : 0, - frame_width, frame_height, - (unsigned)bt->pixelclock, bt->polarities); - } else { + if (state->dv_timings.type == V4L2_DV_BT_656_1120) + v4l2_print_dv_timings(sd->name, "timings: ", + &state->dv_timings, false); + else v4l2_info(sd, "no timings set\n"); - } return 0; } @@ -636,95 +626,34 @@ static int ad9389b_s_stream(struct v4l2_subdev *sd, int enable) return 0; } -static const struct v4l2_dv_timings ad9389b_timings[] = { - V4L2_DV_BT_CEA_720X480P59_94, - V4L2_DV_BT_CEA_720X576P50, - V4L2_DV_BT_CEA_1280X720P24, - V4L2_DV_BT_CEA_1280X720P25, - V4L2_DV_BT_CEA_1280X720P30, - V4L2_DV_BT_CEA_1280X720P50, - V4L2_DV_BT_CEA_1280X720P60, - V4L2_DV_BT_CEA_1920X1080P24, - V4L2_DV_BT_CEA_1920X1080P25, - V4L2_DV_BT_CEA_1920X1080P30, - V4L2_DV_BT_CEA_1920X1080P50, - V4L2_DV_BT_CEA_1920X1080P60, - - V4L2_DV_BT_DMT_640X350P85, - V4L2_DV_BT_DMT_640X400P85, - V4L2_DV_BT_DMT_720X400P85, - V4L2_DV_BT_DMT_640X480P60, - V4L2_DV_BT_DMT_640X480P72, - V4L2_DV_BT_DMT_640X480P75, - V4L2_DV_BT_DMT_640X480P85, - V4L2_DV_BT_DMT_800X600P56, - V4L2_DV_BT_DMT_800X600P60, - V4L2_DV_BT_DMT_800X600P72, - V4L2_DV_BT_DMT_800X600P75, - V4L2_DV_BT_DMT_800X600P85, - V4L2_DV_BT_DMT_848X480P60, - V4L2_DV_BT_DMT_1024X768P60, - V4L2_DV_BT_DMT_1024X768P70, - V4L2_DV_BT_DMT_1024X768P75, - V4L2_DV_BT_DMT_1024X768P85, - V4L2_DV_BT_DMT_1152X864P75, - V4L2_DV_BT_DMT_1280X768P60_RB, - V4L2_DV_BT_DMT_1280X768P60, - V4L2_DV_BT_DMT_1280X768P75, - V4L2_DV_BT_DMT_1280X768P85, - V4L2_DV_BT_DMT_1280X800P60_RB, - V4L2_DV_BT_DMT_1280X800P60, - V4L2_DV_BT_DMT_1280X800P75, - V4L2_DV_BT_DMT_1280X800P85, - V4L2_DV_BT_DMT_1280X960P60, - V4L2_DV_BT_DMT_1280X960P85, - V4L2_DV_BT_DMT_1280X1024P60, - V4L2_DV_BT_DMT_1280X1024P75, - V4L2_DV_BT_DMT_1280X1024P85, - V4L2_DV_BT_DMT_1360X768P60, - V4L2_DV_BT_DMT_1400X1050P60_RB, - V4L2_DV_BT_DMT_1400X1050P60, - V4L2_DV_BT_DMT_1400X1050P75, - V4L2_DV_BT_DMT_1400X1050P85, - V4L2_DV_BT_DMT_1440X900P60_RB, - V4L2_DV_BT_DMT_1440X900P60, - V4L2_DV_BT_DMT_1600X1200P60, - V4L2_DV_BT_DMT_1680X1050P60_RB, - V4L2_DV_BT_DMT_1680X1050P60, - V4L2_DV_BT_DMT_1792X1344P60, - V4L2_DV_BT_DMT_1856X1392P60, - V4L2_DV_BT_DMT_1920X1200P60_RB, - V4L2_DV_BT_DMT_1366X768P60, - V4L2_DV_BT_DMT_1920X1080P60, - {}, +static const struct v4l2_dv_timings_cap ad9389b_timings_cap = { + .type = V4L2_DV_BT_656_1120, + .bt = { + .max_width = 1920, + .max_height = 1200, + .min_pixelclock = 25000000, + .max_pixelclock = 170000000, + .standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + .capabilities = V4L2_DV_BT_CAP_PROGRESSIVE | + V4L2_DV_BT_CAP_REDUCED_BLANKING | V4L2_DV_BT_CAP_CUSTOM, + }, }; static int ad9389b_s_dv_timings(struct v4l2_subdev *sd, struct v4l2_dv_timings *timings) { struct ad9389b_state *state = get_ad9389b_state(sd); - int i; v4l2_dbg(1, debug, sd, "%s:\n", __func__); /* quick sanity check */ - if (timings->type != V4L2_DV_BT_656_1120) - return -EINVAL; - - if (timings->bt.interlaced) - return -EINVAL; - if (timings->bt.pixelclock < 27000000 || - timings->bt.pixelclock > 170000000) + if (!v4l2_valid_dv_timings(timings, &ad9389b_timings_cap, NULL, NULL)) return -EINVAL; /* Fill the optional fields .standards and .flags in struct v4l2_dv_timings - if the format is listed in ad9389b_timings[] */ - for (i = 0; ad9389b_timings[i].bt.width; i++) { - if (v4l_match_dv_timings(timings, &ad9389b_timings[i], 0)) { - *timings = ad9389b_timings[i]; - break; - } - } + if the format is one of the CEA or DMT timings. */ + v4l2_find_dv_timings_cap(timings, &ad9389b_timings_cap, 0, NULL, NULL); timings->bt.flags &= ~V4L2_DV_FL_REDUCED_FPS; @@ -762,26 +691,14 @@ static int ad9389b_g_dv_timings(struct v4l2_subdev *sd, static int ad9389b_enum_dv_timings(struct v4l2_subdev *sd, struct v4l2_enum_dv_timings *timings) { - if (timings->index >= ARRAY_SIZE(ad9389b_timings)) - return -EINVAL; - - memset(timings->reserved, 0, sizeof(timings->reserved)); - timings->timings = ad9389b_timings[timings->index]; - return 0; + return v4l2_enum_dv_timings_cap(timings, &ad9389b_timings_cap, + NULL, NULL); } static int ad9389b_dv_timings_cap(struct v4l2_subdev *sd, struct v4l2_dv_timings_cap *cap) { - cap->type = V4L2_DV_BT_656_1120; - cap->bt.max_width = 1920; - cap->bt.max_height = 1200; - cap->bt.min_pixelclock = 27000000; - cap->bt.max_pixelclock = 170000000; - cap->bt.standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | - V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT; - cap->bt.capabilities = V4L2_DV_BT_CAP_PROGRESSIVE | - V4L2_DV_BT_CAP_REDUCED_BLANKING | V4L2_DV_BT_CAP_CUSTOM; + *cap = ad9389b_timings_cap; return 0; } @@ -930,8 +847,10 @@ static void ad9389b_edid_handler(struct work_struct *work) * (DVI connectors are particularly prone to this problem). */ if (state->edid.read_retries) { state->edid.read_retries--; - /* EDID read failed, trigger a retry */ - ad9389b_wr(sd, 0xc9, 0xf); + v4l2_dbg(1, debug, sd, "%s: edid read failed\n", __func__); + state->have_monitor = false; + ad9389b_s_power(sd, false); + ad9389b_s_power(sd, true); queue_delayed_work(state->work_queue, &state->edid_handler, EDID_DELAY); return; @@ -967,11 +886,9 @@ static void ad9389b_setup(struct v4l2_subdev *sd) ad9389b_wr_and_or(sd, 0x15, 0xf1, 0x0); /* Output format: RGB 4:4:4 */ ad9389b_wr_and_or(sd, 0x16, 0x3f, 0x0); - /* CSC fixed point: +/-2, 1st order interpolation 4:2:2 -> 4:4:4 up - conversion, Aspect ratio: 16:9 */ - ad9389b_wr_and_or(sd, 0x17, 0xe1, 0x0e); - /* Disable pixel repetition and CSC */ - ad9389b_wr_and_or(sd, 0x3b, 0x9e, 0x0); + /* 1st order interpolation 4:2:2 -> 4:4:4 up conversion, + Aspect ratio: 16:9 */ + ad9389b_wr_and_or(sd, 0x17, 0xf9, 0x06); /* Output format: RGB 4:4:4, Active Format Information is valid. */ ad9389b_wr_and_or(sd, 0x45, 0xc7, 0x08); /* Underscanned */ @@ -1056,12 +973,12 @@ static void ad9389b_check_monitor_present_status(struct v4l2_subdev *sd) static bool edid_block_verify_crc(u8 *edid_block) { - int i; u8 sum = 0; + int i; - for (i = 0; i < 127; i++) - sum += *(edid_block + i); - return ((255 - sum + 1) == edid_block[127]); + for (i = 0; i < 128; i++) + sum += edid_block[i]; + return sum == 0; } static bool edid_segment_verify_crc(struct v4l2_subdev *sd, u32 segment) @@ -1107,6 +1024,8 @@ static bool ad9389b_check_edid_status(struct v4l2_subdev *sd) } if (!edid_segment_verify_crc(sd, segment)) { /* edid crc error, force reread of edid segment */ + v4l2_err(sd, "%s: edid crc error\n", __func__); + state->have_monitor = false; ad9389b_s_power(sd, false); ad9389b_s_power(sd, true); return false; @@ -1190,27 +1109,27 @@ static int ad9389b_probe(struct i2c_client *client, const struct i2c_device_id * state->hdmi_mode_ctrl = v4l2_ctrl_new_std_menu(hdl, &ad9389b_ctrl_ops, V4L2_CID_DV_TX_MODE, V4L2_DV_TX_MODE_HDMI, 0, V4L2_DV_TX_MODE_DVI_D); - state->hdmi_mode_ctrl->is_private = true; state->hotplug_ctrl = v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_DV_TX_HOTPLUG, 0, 1, 0, 0); - state->hotplug_ctrl->is_private = true; state->rx_sense_ctrl = v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_DV_TX_RXSENSE, 0, 1, 0, 0); - state->rx_sense_ctrl->is_private = true; state->have_edid0_ctrl = v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_DV_TX_EDID_PRESENT, 0, 1, 0, 0); - state->have_edid0_ctrl->is_private = true; state->rgb_quantization_range_ctrl = v4l2_ctrl_new_std_menu(hdl, &ad9389b_ctrl_ops, V4L2_CID_DV_TX_RGB_RANGE, V4L2_DV_RGB_RANGE_FULL, 0, V4L2_DV_RGB_RANGE_AUTO); - state->rgb_quantization_range_ctrl->is_private = true; sd->ctrl_handler = hdl; if (hdl->error) { err = hdl->error; goto err_hdl; } + state->hdmi_mode_ctrl->is_private = true; + state->hotplug_ctrl->is_private = true; + state->rx_sense_ctrl->is_private = true; + state->have_edid0_ctrl->is_private = true; + state->rgb_quantization_range_ctrl->is_private = true; state->pad.flags = MEDIA_PAD_FL_SINK; err = media_entity_init(&sd->entity, 1, &state->pad, 0); diff --git a/drivers/media/i2c/adv7343.c b/drivers/media/i2c/adv7343.c index 7606218ec4a7..aeb56c53e39f 100644 --- a/drivers/media/i2c/adv7343.c +++ b/drivers/media/i2c/adv7343.c @@ -27,8 +27,10 @@ #include <linux/uaccess.h> #include <media/adv7343.h> +#include <media/v4l2-async.h> #include <media/v4l2-device.h> #include <media/v4l2-ctrls.h> +#include <media/v4l2-of.h> #include "adv7343_regs.h" @@ -226,12 +228,12 @@ static int adv7343_setoutput(struct v4l2_subdev *sd, u32 output_type) else val = state->pdata->mode_config.sleep_mode << 0 | state->pdata->mode_config.pll_control << 1 | - state->pdata->mode_config.dac_3 << 2 | - state->pdata->mode_config.dac_2 << 3 | - state->pdata->mode_config.dac_1 << 4 | - state->pdata->mode_config.dac_6 << 5 | - state->pdata->mode_config.dac_5 << 6 | - state->pdata->mode_config.dac_4 << 7; + state->pdata->mode_config.dac[2] << 2 | + state->pdata->mode_config.dac[1] << 3 | + state->pdata->mode_config.dac[0] << 4 | + state->pdata->mode_config.dac[5] << 5 | + state->pdata->mode_config.dac[4] << 6 | + state->pdata->mode_config.dac[3] << 7; err = adv7343_write(sd, ADV7343_POWER_MODE_REG, val); if (err < 0) @@ -250,15 +252,15 @@ static int adv7343_setoutput(struct v4l2_subdev *sd, u32 output_type) /* configure SD DAC Output 2 and SD DAC Output 1 bit to zero */ val = state->reg82 & (SD_DAC_1_DI & SD_DAC_2_DI); - if (state->pdata && state->pdata->sd_config.sd_dac_out1) - val = val | (state->pdata->sd_config.sd_dac_out1 << 1); - else if (state->pdata && !state->pdata->sd_config.sd_dac_out1) - val = val & ~(state->pdata->sd_config.sd_dac_out1 << 1); + if (state->pdata && state->pdata->sd_config.sd_dac_out[0]) + val = val | (state->pdata->sd_config.sd_dac_out[0] << 1); + else if (state->pdata && !state->pdata->sd_config.sd_dac_out[0]) + val = val & ~(state->pdata->sd_config.sd_dac_out[0] << 1); - if (state->pdata && state->pdata->sd_config.sd_dac_out2) - val = val | (state->pdata->sd_config.sd_dac_out2 << 2); - else if (state->pdata && !state->pdata->sd_config.sd_dac_out2) - val = val & ~(state->pdata->sd_config.sd_dac_out2 << 2); + if (state->pdata && state->pdata->sd_config.sd_dac_out[1]) + val = val | (state->pdata->sd_config.sd_dac_out[1] << 2); + else if (state->pdata && !state->pdata->sd_config.sd_dac_out[1]) + val = val & ~(state->pdata->sd_config.sd_dac_out[1] << 2); err = adv7343_write(sd, ADV7343_SD_MODE_REG2, val); if (err < 0) @@ -398,6 +400,40 @@ static int adv7343_initialize(struct v4l2_subdev *sd) return err; } +static struct adv7343_platform_data * +adv7343_get_pdata(struct i2c_client *client) +{ + struct adv7343_platform_data *pdata; + struct device_node *np; + + if (!IS_ENABLED(CONFIG_OF) || !client->dev.of_node) + return client->dev.platform_data; + + np = v4l2_of_get_next_endpoint(client->dev.of_node, NULL); + if (!np) + return NULL; + + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + goto done; + + pdata->mode_config.sleep_mode = + of_property_read_bool(np, "adi,power-mode-sleep-mode"); + + pdata->mode_config.pll_control = + of_property_read_bool(np, "adi,power-mode-pll-ctrl"); + + of_property_read_u32_array(np, "adi,dac-enable", + pdata->mode_config.dac, 6); + + of_property_read_u32_array(np, "adi,sd-dac-enable", + pdata->sd_config.sd_dac_out, 2); + +done: + of_node_put(np); + return pdata; +} + static int adv7343_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -416,7 +452,7 @@ static int adv7343_probe(struct i2c_client *client, return -ENOMEM; /* Copy board specific information here */ - state->pdata = client->dev.platform_data; + state->pdata = adv7343_get_pdata(client); state->reg00 = 0x80; state->reg01 = 0x00; @@ -445,16 +481,21 @@ static int adv7343_probe(struct i2c_client *client, ADV7343_GAIN_DEF); state->sd.ctrl_handler = &state->hdl; if (state->hdl.error) { - int err = state->hdl.error; - - v4l2_ctrl_handler_free(&state->hdl); - return err; + err = state->hdl.error; + goto done; } v4l2_ctrl_handler_setup(&state->hdl); err = adv7343_initialize(&state->sd); if (err) + goto done; + + err = v4l2_async_register_subdev(&state->sd); + +done: + if (err < 0) v4l2_ctrl_handler_free(&state->hdl); + return err; } @@ -463,6 +504,7 @@ static int adv7343_remove(struct i2c_client *client) struct v4l2_subdev *sd = i2c_get_clientdata(client); struct adv7343_state *state = to_state(sd); + v4l2_async_unregister_subdev(&state->sd); v4l2_device_unregister_subdev(sd); v4l2_ctrl_handler_free(&state->hdl); @@ -476,8 +518,17 @@ static const struct i2c_device_id adv7343_id[] = { MODULE_DEVICE_TABLE(i2c, adv7343_id); +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id adv7343_of_match[] = { + {.compatible = "adi,adv7343", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, adv7343_of_match); +#endif + static struct i2c_driver adv7343_driver = { .driver = { + .of_match_table = of_match_ptr(adv7343_of_match), .owner = THIS_MODULE, .name = "adv7343", }, diff --git a/drivers/media/i2c/adv7511.c b/drivers/media/i2c/adv7511.c new file mode 100644 index 000000000000..7a576097471f --- /dev/null +++ b/drivers/media/i2c/adv7511.c @@ -0,0 +1,1198 @@ +/* + * Analog Devices ADV7511 HDMI Transmitter Device Driver + * + * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/videodev2.h> +#include <linux/gpio.h> +#include <linux/workqueue.h> +#include <linux/v4l2-dv-timings.h> +#include <media/v4l2-device.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-dv-timings.h> +#include <media/adv7511.h> + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +MODULE_DESCRIPTION("Analog Devices ADV7511 HDMI Transmitter Device Driver"); +MODULE_AUTHOR("Hans Verkuil"); +MODULE_LICENSE("GPL"); + +#define MASK_ADV7511_EDID_RDY_INT 0x04 +#define MASK_ADV7511_MSEN_INT 0x40 +#define MASK_ADV7511_HPD_INT 0x80 + +#define MASK_ADV7511_HPD_DETECT 0x40 +#define MASK_ADV7511_MSEN_DETECT 0x20 +#define MASK_ADV7511_EDID_RDY 0x10 + +#define EDID_MAX_RETRIES (8) +#define EDID_DELAY 250 +#define EDID_MAX_SEGM 8 + +#define ADV7511_MAX_WIDTH 1920 +#define ADV7511_MAX_HEIGHT 1200 +#define ADV7511_MIN_PIXELCLOCK 20000000 +#define ADV7511_MAX_PIXELCLOCK 225000000 + +/* +********************************************************************** +* +* Arrays with configuration parameters for the ADV7511 +* +********************************************************************** +*/ + +struct i2c_reg_value { + unsigned char reg; + unsigned char value; +}; + +struct adv7511_state_edid { + /* total number of blocks */ + u32 blocks; + /* Number of segments read */ + u32 segments; + uint8_t data[EDID_MAX_SEGM * 256]; + /* Number of EDID read retries left */ + unsigned read_retries; + bool complete; +}; + +struct adv7511_state { + struct adv7511_platform_data pdata; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler hdl; + int chip_revision; + uint8_t i2c_edid_addr; + uint8_t i2c_cec_addr; + /* Is the adv7511 powered on? */ + bool power_on; + /* Did we receive hotplug and rx-sense signals? */ + bool have_monitor; + /* timings from s_dv_timings */ + struct v4l2_dv_timings dv_timings; + /* controls */ + struct v4l2_ctrl *hdmi_mode_ctrl; + struct v4l2_ctrl *hotplug_ctrl; + struct v4l2_ctrl *rx_sense_ctrl; + struct v4l2_ctrl *have_edid0_ctrl; + struct v4l2_ctrl *rgb_quantization_range_ctrl; + struct i2c_client *i2c_edid; + struct adv7511_state_edid edid; + /* Running counter of the number of detected EDIDs (for debugging) */ + unsigned edid_detect_counter; + struct workqueue_struct *work_queue; + struct delayed_work edid_handler; /* work entry */ +}; + +static void adv7511_check_monitor_present_status(struct v4l2_subdev *sd); +static bool adv7511_check_edid_status(struct v4l2_subdev *sd); +static void adv7511_setup(struct v4l2_subdev *sd); +static int adv7511_s_i2s_clock_freq(struct v4l2_subdev *sd, u32 freq); +static int adv7511_s_clock_freq(struct v4l2_subdev *sd, u32 freq); + + +static const struct v4l2_dv_timings_cap adv7511_timings_cap = { + .type = V4L2_DV_BT_656_1120, + .bt = { + .max_width = ADV7511_MAX_WIDTH, + .max_height = ADV7511_MAX_HEIGHT, + .min_pixelclock = ADV7511_MIN_PIXELCLOCK, + .max_pixelclock = ADV7511_MAX_PIXELCLOCK, + .standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + .capabilities = V4L2_DV_BT_CAP_PROGRESSIVE | + V4L2_DV_BT_CAP_REDUCED_BLANKING | V4L2_DV_BT_CAP_CUSTOM, + }, +}; + +static inline struct adv7511_state *get_adv7511_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7511_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct adv7511_state, hdl)->sd; +} + +/* ------------------------ I2C ----------------------------------------------- */ + +static s32 adv_smbus_read_byte_data_check(struct i2c_client *client, + u8 command, bool check) +{ + union i2c_smbus_data data; + + if (!i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_READ, command, + I2C_SMBUS_BYTE_DATA, &data)) + return data.byte; + if (check) + v4l_err(client, "error reading %02x, %02x\n", + client->addr, command); + return -1; +} + +static s32 adv_smbus_read_byte_data(struct i2c_client *client, u8 command) +{ + int i; + for (i = 0; i < 3; i++) { + int ret = adv_smbus_read_byte_data_check(client, command, true); + if (ret >= 0) { + if (i) + v4l_err(client, "read ok after %d retries\n", i); + return ret; + } + } + v4l_err(client, "read failed\n"); + return -1; +} + +static int adv7511_rd(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return adv_smbus_read_byte_data(client, reg); +} + +static int adv7511_wr(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + int i; + + for (i = 0; i < 3; i++) { + ret = i2c_smbus_write_byte_data(client, reg, val); + if (ret == 0) + return 0; + } + v4l2_err(sd, "%s: i2c write error\n", __func__); + return ret; +} + +/* To set specific bits in the register, a clear-mask is given (to be AND-ed), + and then the value-mask (to be OR-ed). */ +static inline void adv7511_wr_and_or(struct v4l2_subdev *sd, u8 reg, uint8_t clr_mask, uint8_t val_mask) +{ + adv7511_wr(sd, reg, (adv7511_rd(sd, reg) & clr_mask) | val_mask); +} + +static int adv_smbus_read_i2c_block_data(struct i2c_client *client, + u8 command, unsigned length, u8 *values) +{ + union i2c_smbus_data data; + int ret; + + if (length > I2C_SMBUS_BLOCK_MAX) + length = I2C_SMBUS_BLOCK_MAX; + data.block[0] = length; + + ret = i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_READ, command, + I2C_SMBUS_I2C_BLOCK_DATA, &data); + memcpy(values, data.block + 1, length); + return ret; +} + +static inline void adv7511_edid_rd(struct v4l2_subdev *sd, uint16_t len, uint8_t *buf) +{ + struct adv7511_state *state = get_adv7511_state(sd); + int i; + int err = 0; + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + for (i = 0; !err && i < len; i += I2C_SMBUS_BLOCK_MAX) + err = adv_smbus_read_i2c_block_data(state->i2c_edid, i, + I2C_SMBUS_BLOCK_MAX, buf + i); + if (err) + v4l2_err(sd, "%s: i2c read error\n", __func__); +} + +static inline bool adv7511_have_hotplug(struct v4l2_subdev *sd) +{ + return adv7511_rd(sd, 0x42) & MASK_ADV7511_HPD_DETECT; +} + +static inline bool adv7511_have_rx_sense(struct v4l2_subdev *sd) +{ + return adv7511_rd(sd, 0x42) & MASK_ADV7511_MSEN_DETECT; +} + +static void adv7511_csc_conversion_mode(struct v4l2_subdev *sd, uint8_t mode) +{ + adv7511_wr_and_or(sd, 0x18, 0x9f, (mode & 0x3)<<5); +} + +static void adv7511_csc_coeff(struct v4l2_subdev *sd, + u16 A1, u16 A2, u16 A3, u16 A4, + u16 B1, u16 B2, u16 B3, u16 B4, + u16 C1, u16 C2, u16 C3, u16 C4) +{ + /* A */ + adv7511_wr_and_or(sd, 0x18, 0xe0, A1>>8); + adv7511_wr(sd, 0x19, A1); + adv7511_wr_and_or(sd, 0x1A, 0xe0, A2>>8); + adv7511_wr(sd, 0x1B, A2); + adv7511_wr_and_or(sd, 0x1c, 0xe0, A3>>8); + adv7511_wr(sd, 0x1d, A3); + adv7511_wr_and_or(sd, 0x1e, 0xe0, A4>>8); + adv7511_wr(sd, 0x1f, A4); + + /* B */ + adv7511_wr_and_or(sd, 0x20, 0xe0, B1>>8); + adv7511_wr(sd, 0x21, B1); + adv7511_wr_and_or(sd, 0x22, 0xe0, B2>>8); + adv7511_wr(sd, 0x23, B2); + adv7511_wr_and_or(sd, 0x24, 0xe0, B3>>8); + adv7511_wr(sd, 0x25, B3); + adv7511_wr_and_or(sd, 0x26, 0xe0, B4>>8); + adv7511_wr(sd, 0x27, B4); + + /* C */ + adv7511_wr_and_or(sd, 0x28, 0xe0, C1>>8); + adv7511_wr(sd, 0x29, C1); + adv7511_wr_and_or(sd, 0x2A, 0xe0, C2>>8); + adv7511_wr(sd, 0x2B, C2); + adv7511_wr_and_or(sd, 0x2C, 0xe0, C3>>8); + adv7511_wr(sd, 0x2D, C3); + adv7511_wr_and_or(sd, 0x2E, 0xe0, C4>>8); + adv7511_wr(sd, 0x2F, C4); +} + +static void adv7511_csc_rgb_full2limit(struct v4l2_subdev *sd, bool enable) +{ + if (enable) { + uint8_t csc_mode = 0; + adv7511_csc_conversion_mode(sd, csc_mode); + adv7511_csc_coeff(sd, + 4096-564, 0, 0, 256, + 0, 4096-564, 0, 256, + 0, 0, 4096-564, 256); + /* enable CSC */ + adv7511_wr_and_or(sd, 0x18, 0x7f, 0x80); + /* AVI infoframe: Limited range RGB (16-235) */ + adv7511_wr_and_or(sd, 0x57, 0xf3, 0x04); + } else { + /* disable CSC */ + adv7511_wr_and_or(sd, 0x18, 0x7f, 0x0); + /* AVI infoframe: Full range RGB (0-255) */ + adv7511_wr_and_or(sd, 0x57, 0xf3, 0x08); + } +} + +static void adv7511_set_IT_content_AVI_InfoFrame(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + if (state->dv_timings.bt.standards & V4L2_DV_BT_STD_CEA861) { + /* CEA format, not IT */ + adv7511_wr_and_or(sd, 0x57, 0x7f, 0x00); + } else { + /* IT format */ + adv7511_wr_and_or(sd, 0x57, 0x7f, 0x80); + } +} + +static int adv7511_set_rgb_quantization_mode(struct v4l2_subdev *sd, struct v4l2_ctrl *ctrl) +{ + switch (ctrl->val) { + default: + return -EINVAL; + break; + case V4L2_DV_RGB_RANGE_AUTO: { + /* automatic */ + struct adv7511_state *state = get_adv7511_state(sd); + + if (state->dv_timings.bt.standards & V4L2_DV_BT_STD_CEA861) { + /* cea format, RGB limited range (16-235) */ + adv7511_csc_rgb_full2limit(sd, true); + } else { + /* not cea format, RGB full range (0-255) */ + adv7511_csc_rgb_full2limit(sd, false); + } + } + break; + case V4L2_DV_RGB_RANGE_LIMITED: + /* RGB limited range (16-235) */ + adv7511_csc_rgb_full2limit(sd, true); + break; + case V4L2_DV_RGB_RANGE_FULL: + /* RGB full range (0-255) */ + adv7511_csc_rgb_full2limit(sd, false); + break; + } + return 0; +} + +/* ------------------------------ CTRL OPS ------------------------------ */ + +static int adv7511_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct adv7511_state *state = get_adv7511_state(sd); + + v4l2_dbg(1, debug, sd, "%s: ctrl id: %d, ctrl->val %d\n", __func__, ctrl->id, ctrl->val); + + if (state->hdmi_mode_ctrl == ctrl) { + /* Set HDMI or DVI-D */ + adv7511_wr_and_or(sd, 0xaf, 0xfd, ctrl->val == V4L2_DV_TX_MODE_HDMI ? 0x02 : 0x00); + return 0; + } + if (state->rgb_quantization_range_ctrl == ctrl) + return adv7511_set_rgb_quantization_mode(sd, ctrl); + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops adv7511_ctrl_ops = { + .s_ctrl = adv7511_s_ctrl, +}; + +/* ---------------------------- CORE OPS ------------------------------------------- */ + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static void adv7511_inv_register(struct v4l2_subdev *sd) +{ + v4l2_info(sd, "0x000-0x0ff: Main Map\n"); +} + +static int adv7511_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + reg->size = 1; + switch (reg->reg >> 8) { + case 0: + reg->val = adv7511_rd(sd, reg->reg & 0xff); + break; + default: + v4l2_info(sd, "Register %03llx not supported\n", reg->reg); + adv7511_inv_register(sd); + break; + } + return 0; +} + +static int adv7511_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + switch (reg->reg >> 8) { + case 0: + adv7511_wr(sd, reg->reg & 0xff, reg->val & 0xff); + break; + default: + v4l2_info(sd, "Register %03llx not supported\n", reg->reg); + adv7511_inv_register(sd); + break; + } + return 0; +} +#endif + +static int adv7511_log_status(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + struct adv7511_state_edid *edid = &state->edid; + + static const char * const states[] = { + "in reset", + "reading EDID", + "idle", + "initializing HDCP", + "HDCP enabled", + "initializing HDCP repeater", + "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" + }; + static const char * const errors[] = { + "no error", + "bad receiver BKSV", + "Ri mismatch", + "Pj mismatch", + "i2c error", + "timed out", + "max repeater cascade exceeded", + "hash check failed", + "too many devices", + "9", "A", "B", "C", "D", "E", "F" + }; + + v4l2_info(sd, "power %s\n", state->power_on ? "on" : "off"); + v4l2_info(sd, "%s hotplug, %s Rx Sense, %s EDID (%d block(s))\n", + (adv7511_rd(sd, 0x42) & MASK_ADV7511_HPD_DETECT) ? "detected" : "no", + (adv7511_rd(sd, 0x42) & MASK_ADV7511_MSEN_DETECT) ? "detected" : "no", + edid->segments ? "found" : "no", + edid->blocks); + v4l2_info(sd, "%s output %s\n", + (adv7511_rd(sd, 0xaf) & 0x02) ? + "HDMI" : "DVI-D", + (adv7511_rd(sd, 0xa1) & 0x3c) ? + "disabled" : "enabled"); + v4l2_info(sd, "state: %s, error: %s, detect count: %u, msk/irq: %02x/%02x\n", + states[adv7511_rd(sd, 0xc8) & 0xf], + errors[adv7511_rd(sd, 0xc8) >> 4], state->edid_detect_counter, + adv7511_rd(sd, 0x94), adv7511_rd(sd, 0x96)); + v4l2_info(sd, "RGB quantization: %s range\n", adv7511_rd(sd, 0x18) & 0x80 ? "limited" : "full"); + if (state->dv_timings.type == V4L2_DV_BT_656_1120) + v4l2_print_dv_timings(sd->name, "timings: ", + &state->dv_timings, false); + else + v4l2_info(sd, "no timings set\n"); + v4l2_info(sd, "i2c edid addr: 0x%x\n", state->i2c_edid_addr); + v4l2_info(sd, "i2c cec addr: 0x%x\n", state->i2c_cec_addr); + return 0; +} + +/* Power up/down adv7511 */ +static int adv7511_s_power(struct v4l2_subdev *sd, int on) +{ + struct adv7511_state *state = get_adv7511_state(sd); + const int retries = 20; + int i; + + v4l2_dbg(1, debug, sd, "%s: power %s\n", __func__, on ? "on" : "off"); + + state->power_on = on; + + if (!on) { + /* Power down */ + adv7511_wr_and_or(sd, 0x41, 0xbf, 0x40); + return true; + } + + /* Power up */ + /* The adv7511 does not always come up immediately. + Retry multiple times. */ + for (i = 0; i < retries; i++) { + adv7511_wr_and_or(sd, 0x41, 0xbf, 0x0); + if ((adv7511_rd(sd, 0x41) & 0x40) == 0) + break; + adv7511_wr_and_or(sd, 0x41, 0xbf, 0x40); + msleep(10); + } + if (i == retries) { + v4l2_dbg(1, debug, sd, "%s: failed to powerup the adv7511!\n", __func__); + adv7511_s_power(sd, 0); + return false; + } + if (i > 1) + v4l2_dbg(1, debug, sd, "%s: needed %d retries to powerup the adv7511\n", __func__, i); + + /* Reserved registers that must be set */ + adv7511_wr(sd, 0x98, 0x03); + adv7511_wr_and_or(sd, 0x9a, 0xfe, 0x70); + adv7511_wr(sd, 0x9c, 0x30); + adv7511_wr_and_or(sd, 0x9d, 0xfc, 0x01); + adv7511_wr(sd, 0xa2, 0xa4); + adv7511_wr(sd, 0xa3, 0xa4); + adv7511_wr(sd, 0xe0, 0xd0); + adv7511_wr(sd, 0xf9, 0x00); + + adv7511_wr(sd, 0x43, state->i2c_edid_addr); + + /* Set number of attempts to read the EDID */ + adv7511_wr(sd, 0xc9, 0xf); + return true; +} + +/* Enable interrupts */ +static void adv7511_set_isr(struct v4l2_subdev *sd, bool enable) +{ + uint8_t irqs = MASK_ADV7511_HPD_INT | MASK_ADV7511_MSEN_INT; + uint8_t irqs_rd; + int retries = 100; + + v4l2_dbg(2, debug, sd, "%s: %s\n", __func__, enable ? "enable" : "disable"); + + /* The datasheet says that the EDID ready interrupt should be + disabled if there is no hotplug. */ + if (!enable) + irqs = 0; + else if (adv7511_have_hotplug(sd)) + irqs |= MASK_ADV7511_EDID_RDY_INT; + + /* + * This i2c write can fail (approx. 1 in 1000 writes). But it + * is essential that this register is correct, so retry it + * multiple times. + * + * Note that the i2c write does not report an error, but the readback + * clearly shows the wrong value. + */ + do { + adv7511_wr(sd, 0x94, irqs); + irqs_rd = adv7511_rd(sd, 0x94); + } while (retries-- && irqs_rd != irqs); + + if (irqs_rd == irqs) + return; + v4l2_err(sd, "Could not set interrupts: hw failure?\n"); +} + +/* Interrupt handler */ +static int adv7511_isr(struct v4l2_subdev *sd, u32 status, bool *handled) +{ + uint8_t irq_status; + + /* disable interrupts to prevent a race condition */ + adv7511_set_isr(sd, false); + irq_status = adv7511_rd(sd, 0x96); + /* clear detected interrupts */ + adv7511_wr(sd, 0x96, irq_status); + + v4l2_dbg(1, debug, sd, "%s: irq 0x%x\n", __func__, irq_status); + + if (irq_status & (MASK_ADV7511_HPD_INT | MASK_ADV7511_MSEN_INT)) + adv7511_check_monitor_present_status(sd); + if (irq_status & MASK_ADV7511_EDID_RDY_INT) + adv7511_check_edid_status(sd); + + /* enable interrupts */ + adv7511_set_isr(sd, true); + + if (handled) + *handled = true; + return 0; +} + +static int adv7511_get_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + if (edid->pad != 0) + return -EINVAL; + if ((edid->blocks == 0) || (edid->blocks > 256)) + return -EINVAL; + if (!edid->edid) + return -EINVAL; + if (!state->edid.segments) { + v4l2_dbg(1, debug, sd, "EDID segment 0 not found\n"); + return -ENODATA; + } + if (edid->start_block >= state->edid.segments * 2) + return -E2BIG; + if ((edid->blocks + edid->start_block) >= state->edid.segments * 2) + edid->blocks = state->edid.segments * 2 - edid->start_block; + + memcpy(edid->edid, &state->edid.data[edid->start_block * 128], + 128 * edid->blocks); + return 0; +} + +static const struct v4l2_subdev_pad_ops adv7511_pad_ops = { + .get_edid = adv7511_get_edid, +}; + +static const struct v4l2_subdev_core_ops adv7511_core_ops = { + .log_status = adv7511_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = adv7511_g_register, + .s_register = adv7511_s_register, +#endif + .s_power = adv7511_s_power, + .interrupt_service_routine = adv7511_isr, +}; + +/* ------------------------------ VIDEO OPS ------------------------------ */ + +/* Enable/disable adv7511 output */ +static int adv7511_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + v4l2_dbg(1, debug, sd, "%s: %sable\n", __func__, (enable ? "en" : "dis")); + adv7511_wr_and_or(sd, 0xa1, ~0x3c, (enable ? 0 : 0x3c)); + if (enable) { + adv7511_check_monitor_present_status(sd); + } else { + adv7511_s_power(sd, 0); + state->have_monitor = false; + } + return 0; +} + +static int adv7511_s_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + /* quick sanity check */ + if (!v4l2_valid_dv_timings(timings, &adv7511_timings_cap, NULL, NULL)) + return -EINVAL; + + /* Fill the optional fields .standards and .flags in struct v4l2_dv_timings + if the format is one of the CEA or DMT timings. */ + v4l2_find_dv_timings_cap(timings, &adv7511_timings_cap, 0, NULL, NULL); + + timings->bt.flags &= ~V4L2_DV_FL_REDUCED_FPS; + + /* save timings */ + state->dv_timings = *timings; + + /* update quantization range based on new dv_timings */ + adv7511_set_rgb_quantization_mode(sd, state->rgb_quantization_range_ctrl); + + /* update AVI infoframe */ + adv7511_set_IT_content_AVI_InfoFrame(sd); + + return 0; +} + +static int adv7511_g_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (!timings) + return -EINVAL; + + *timings = state->dv_timings; + + return 0; +} + +static int adv7511_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + return v4l2_enum_dv_timings_cap(timings, &adv7511_timings_cap, NULL, NULL); +} + +static int adv7511_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + *cap = adv7511_timings_cap; + return 0; +} + +static const struct v4l2_subdev_video_ops adv7511_video_ops = { + .s_stream = adv7511_s_stream, + .s_dv_timings = adv7511_s_dv_timings, + .g_dv_timings = adv7511_g_dv_timings, + .enum_dv_timings = adv7511_enum_dv_timings, + .dv_timings_cap = adv7511_dv_timings_cap, +}; + +/* ------------------------------ AUDIO OPS ------------------------------ */ +static int adv7511_s_audio_stream(struct v4l2_subdev *sd, int enable) +{ + v4l2_dbg(1, debug, sd, "%s: %sable\n", __func__, (enable ? "en" : "dis")); + + if (enable) + adv7511_wr_and_or(sd, 0x4b, 0x3f, 0x80); + else + adv7511_wr_and_or(sd, 0x4b, 0x3f, 0x40); + + return 0; +} + +static int adv7511_s_clock_freq(struct v4l2_subdev *sd, u32 freq) +{ + u32 N; + + switch (freq) { + case 32000: N = 4096; break; + case 44100: N = 6272; break; + case 48000: N = 6144; break; + case 88200: N = 12544; break; + case 96000: N = 12288; break; + case 176400: N = 25088; break; + case 192000: N = 24576; break; + default: + return -EINVAL; + } + + /* Set N (used with CTS to regenerate the audio clock) */ + adv7511_wr(sd, 0x01, (N >> 16) & 0xf); + adv7511_wr(sd, 0x02, (N >> 8) & 0xff); + adv7511_wr(sd, 0x03, N & 0xff); + + return 0; +} + +static int adv7511_s_i2s_clock_freq(struct v4l2_subdev *sd, u32 freq) +{ + u32 i2s_sf; + + switch (freq) { + case 32000: i2s_sf = 0x30; break; + case 44100: i2s_sf = 0x00; break; + case 48000: i2s_sf = 0x20; break; + case 88200: i2s_sf = 0x80; break; + case 96000: i2s_sf = 0xa0; break; + case 176400: i2s_sf = 0xc0; break; + case 192000: i2s_sf = 0xe0; break; + default: + return -EINVAL; + } + + /* Set sampling frequency for I2S audio to 48 kHz */ + adv7511_wr_and_or(sd, 0x15, 0xf, i2s_sf); + + return 0; +} + +static int adv7511_s_routing(struct v4l2_subdev *sd, u32 input, u32 output, u32 config) +{ + /* Only 2 channels in use for application */ + adv7511_wr_and_or(sd, 0x73, 0xf8, 0x1); + /* Speaker mapping */ + adv7511_wr(sd, 0x76, 0x00); + + /* 16 bit audio word length */ + adv7511_wr_and_or(sd, 0x14, 0xf0, 0x02); + + return 0; +} + +static const struct v4l2_subdev_audio_ops adv7511_audio_ops = { + .s_stream = adv7511_s_audio_stream, + .s_clock_freq = adv7511_s_clock_freq, + .s_i2s_clock_freq = adv7511_s_i2s_clock_freq, + .s_routing = adv7511_s_routing, +}; + +/* --------------------- SUBDEV OPS --------------------------------------- */ + +static const struct v4l2_subdev_ops adv7511_ops = { + .core = &adv7511_core_ops, + .pad = &adv7511_pad_ops, + .video = &adv7511_video_ops, + .audio = &adv7511_audio_ops, +}; + +/* ----------------------------------------------------------------------- */ +static void adv7511_dbg_dump_edid(int lvl, int debug, struct v4l2_subdev *sd, int segment, uint8_t *buf) +{ + if (debug >= lvl) { + int i, j; + v4l2_dbg(lvl, debug, sd, "edid segment %d\n", segment); + for (i = 0; i < 256; i += 16) { + u8 b[128]; + u8 *bp = b; + if (i == 128) + v4l2_dbg(lvl, debug, sd, "\n"); + for (j = i; j < i + 16; j++) { + sprintf(bp, "0x%02x, ", buf[j]); + bp += 6; + } + bp[0] = '\0'; + v4l2_dbg(lvl, debug, sd, "%s\n", b); + } + } +} + +static void adv7511_edid_handler(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct adv7511_state *state = container_of(dwork, struct adv7511_state, edid_handler); + struct v4l2_subdev *sd = &state->sd; + struct adv7511_edid_detect ed; + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (adv7511_check_edid_status(sd)) { + /* Return if we received the EDID. */ + return; + } + + if (adv7511_have_hotplug(sd)) { + /* We must retry reading the EDID several times, it is possible + * that initially the EDID couldn't be read due to i2c errors + * (DVI connectors are particularly prone to this problem). */ + if (state->edid.read_retries) { + state->edid.read_retries--; + v4l2_dbg(1, debug, sd, "%s: edid read failed\n", __func__); + state->have_monitor = false; + adv7511_s_power(sd, false); + adv7511_s_power(sd, true); + queue_delayed_work(state->work_queue, &state->edid_handler, EDID_DELAY); + return; + } + } + + /* We failed to read the EDID, so send an event for this. */ + ed.present = false; + ed.segment = adv7511_rd(sd, 0xc4); + v4l2_subdev_notify(sd, ADV7511_EDID_DETECT, (void *)&ed); + v4l2_dbg(1, debug, sd, "%s: no edid found\n", __func__); +} + +static void adv7511_audio_setup(struct v4l2_subdev *sd) +{ + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + adv7511_s_i2s_clock_freq(sd, 48000); + adv7511_s_clock_freq(sd, 48000); + adv7511_s_routing(sd, 0, 0, 0); +} + +/* Configure hdmi transmitter. */ +static void adv7511_setup(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + /* Input format: RGB 4:4:4 */ + adv7511_wr_and_or(sd, 0x15, 0xf0, 0x0); + /* Output format: RGB 4:4:4 */ + adv7511_wr_and_or(sd, 0x16, 0x7f, 0x0); + /* 1st order interpolation 4:2:2 -> 4:4:4 up conversion, Aspect ratio: 16:9 */ + adv7511_wr_and_or(sd, 0x17, 0xf9, 0x06); + /* Disable pixel repetition */ + adv7511_wr_and_or(sd, 0x3b, 0x9f, 0x0); + /* Disable CSC */ + adv7511_wr_and_or(sd, 0x18, 0x7f, 0x0); + /* Output format: RGB 4:4:4, Active Format Information is valid, + * underscanned */ + adv7511_wr_and_or(sd, 0x55, 0x9c, 0x12); + /* AVI Info frame packet enable, Audio Info frame disable */ + adv7511_wr_and_or(sd, 0x44, 0xe7, 0x10); + /* Colorimetry, Active format aspect ratio: same as picure. */ + adv7511_wr(sd, 0x56, 0xa8); + /* No encryption */ + adv7511_wr_and_or(sd, 0xaf, 0xed, 0x0); + + /* Positive clk edge capture for input video clock */ + adv7511_wr_and_or(sd, 0xba, 0x1f, 0x60); + + adv7511_audio_setup(sd); + + v4l2_ctrl_handler_setup(&state->hdl); +} + +static void adv7511_notify_monitor_detect(struct v4l2_subdev *sd) +{ + struct adv7511_monitor_detect mdt; + struct adv7511_state *state = get_adv7511_state(sd); + + mdt.present = state->have_monitor; + v4l2_subdev_notify(sd, ADV7511_MONITOR_DETECT, (void *)&mdt); +} + +static void adv7511_check_monitor_present_status(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + /* read hotplug and rx-sense state */ + uint8_t status = adv7511_rd(sd, 0x42); + + v4l2_dbg(1, debug, sd, "%s: status: 0x%x%s%s\n", + __func__, + status, + status & MASK_ADV7511_HPD_DETECT ? ", hotplug" : "", + status & MASK_ADV7511_MSEN_DETECT ? ", rx-sense" : ""); + + /* update read only ctrls */ + v4l2_ctrl_s_ctrl(state->hotplug_ctrl, adv7511_have_hotplug(sd) ? 0x1 : 0x0); + v4l2_ctrl_s_ctrl(state->rx_sense_ctrl, adv7511_have_rx_sense(sd) ? 0x1 : 0x0); + v4l2_ctrl_s_ctrl(state->have_edid0_ctrl, state->edid.segments ? 0x1 : 0x0); + + if ((status & MASK_ADV7511_HPD_DETECT) && ((status & MASK_ADV7511_MSEN_DETECT) || state->edid.segments)) { + v4l2_dbg(1, debug, sd, "%s: hotplug and (rx-sense or edid)\n", __func__); + if (!state->have_monitor) { + v4l2_dbg(1, debug, sd, "%s: monitor detected\n", __func__); + state->have_monitor = true; + adv7511_set_isr(sd, true); + if (!adv7511_s_power(sd, true)) { + v4l2_dbg(1, debug, sd, "%s: monitor detected, powerup failed\n", __func__); + return; + } + adv7511_setup(sd); + adv7511_notify_monitor_detect(sd); + state->edid.read_retries = EDID_MAX_RETRIES; + queue_delayed_work(state->work_queue, &state->edid_handler, EDID_DELAY); + } + } else if (status & MASK_ADV7511_HPD_DETECT) { + v4l2_dbg(1, debug, sd, "%s: hotplug detected\n", __func__); + state->edid.read_retries = EDID_MAX_RETRIES; + queue_delayed_work(state->work_queue, &state->edid_handler, EDID_DELAY); + } else if (!(status & MASK_ADV7511_HPD_DETECT)) { + v4l2_dbg(1, debug, sd, "%s: hotplug not detected\n", __func__); + if (state->have_monitor) { + v4l2_dbg(1, debug, sd, "%s: monitor not detected\n", __func__); + state->have_monitor = false; + adv7511_notify_monitor_detect(sd); + } + adv7511_s_power(sd, false); + memset(&state->edid, 0, sizeof(struct adv7511_state_edid)); + } +} + +static bool edid_block_verify_crc(uint8_t *edid_block) +{ + int i; + uint8_t sum = 0; + + for (i = 0; i < 128; i++) + sum += *(edid_block + i); + return (sum == 0); +} + +static bool edid_segment_verify_crc(struct v4l2_subdev *sd, u32 segment) +{ + struct adv7511_state *state = get_adv7511_state(sd); + u32 blocks = state->edid.blocks; + uint8_t *data = state->edid.data; + + if (edid_block_verify_crc(&data[segment * 256])) { + if ((segment + 1) * 2 <= blocks) + return edid_block_verify_crc(&data[segment * 256 + 128]); + return true; + } + return false; +} + +static bool adv7511_check_edid_status(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + uint8_t edidRdy = adv7511_rd(sd, 0xc5); + + v4l2_dbg(1, debug, sd, "%s: edid ready (retries: %d)\n", + __func__, EDID_MAX_RETRIES - state->edid.read_retries); + + if (state->edid.complete) + return true; + + if (edidRdy & MASK_ADV7511_EDID_RDY) { + int segment = adv7511_rd(sd, 0xc4); + struct adv7511_edid_detect ed; + + if (segment >= EDID_MAX_SEGM) { + v4l2_err(sd, "edid segment number too big\n"); + return false; + } + v4l2_dbg(1, debug, sd, "%s: got segment %d\n", __func__, segment); + adv7511_edid_rd(sd, 256, &state->edid.data[segment * 256]); + adv7511_dbg_dump_edid(2, debug, sd, segment, &state->edid.data[segment * 256]); + if (segment == 0) { + state->edid.blocks = state->edid.data[0x7e] + 1; + v4l2_dbg(1, debug, sd, "%s: %d blocks in total\n", __func__, state->edid.blocks); + } + if (!edid_segment_verify_crc(sd, segment)) { + /* edid crc error, force reread of edid segment */ + v4l2_dbg(1, debug, sd, "%s: edid crc error\n", __func__); + state->have_monitor = false; + adv7511_s_power(sd, false); + adv7511_s_power(sd, true); + return false; + } + /* one more segment read ok */ + state->edid.segments = segment + 1; + if (((state->edid.data[0x7e] >> 1) + 1) > state->edid.segments) { + /* Request next EDID segment */ + v4l2_dbg(1, debug, sd, "%s: request segment %d\n", __func__, state->edid.segments); + adv7511_wr(sd, 0xc9, 0xf); + adv7511_wr(sd, 0xc4, state->edid.segments); + state->edid.read_retries = EDID_MAX_RETRIES; + queue_delayed_work(state->work_queue, &state->edid_handler, EDID_DELAY); + return false; + } + + v4l2_dbg(1, debug, sd, "%s: edid complete with %d segment(s)\n", __func__, state->edid.segments); + state->edid.complete = true; + + /* report when we have all segments + but report only for segment 0 + */ + ed.present = true; + ed.segment = 0; + state->edid_detect_counter++; + v4l2_ctrl_s_ctrl(state->have_edid0_ctrl, state->edid.segments ? 0x1 : 0x0); + v4l2_subdev_notify(sd, ADV7511_EDID_DETECT, (void *)&ed); + return ed.present; + } + + return false; +} + +/* ----------------------------------------------------------------------- */ +/* Setup ADV7511 */ +static void adv7511_init_setup(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + struct adv7511_state_edid *edid = &state->edid; + + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + /* clear all interrupts */ + adv7511_wr(sd, 0x96, 0xff); + memset(edid, 0, sizeof(struct adv7511_state_edid)); + state->have_monitor = false; + adv7511_set_isr(sd, false); + adv7511_s_stream(sd, false); + adv7511_s_audio_stream(sd, false); +} + +static int adv7511_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct adv7511_state *state; + struct adv7511_platform_data *pdata = client->dev.platform_data; + struct v4l2_ctrl_handler *hdl; + struct v4l2_subdev *sd; + u8 chip_id[2]; + int err = -EIO; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + state = devm_kzalloc(&client->dev, sizeof(struct adv7511_state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + /* Platform data */ + if (!pdata) { + v4l_err(client, "No platform data!\n"); + return -ENODEV; + } + memcpy(&state->pdata, pdata, sizeof(state->pdata)); + + sd = &state->sd; + + v4l2_dbg(1, debug, sd, "detecting adv7511 client on address 0x%x\n", + client->addr << 1); + + v4l2_i2c_subdev_init(sd, client, &adv7511_ops); + + hdl = &state->hdl; + v4l2_ctrl_handler_init(hdl, 10); + /* add in ascending ID order */ + state->hdmi_mode_ctrl = v4l2_ctrl_new_std_menu(hdl, &adv7511_ctrl_ops, + V4L2_CID_DV_TX_MODE, V4L2_DV_TX_MODE_HDMI, + 0, V4L2_DV_TX_MODE_DVI_D); + state->hotplug_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_TX_HOTPLUG, 0, 1, 0, 0); + state->rx_sense_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_TX_RXSENSE, 0, 1, 0, 0); + state->have_edid0_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_TX_EDID_PRESENT, 0, 1, 0, 0); + state->rgb_quantization_range_ctrl = + v4l2_ctrl_new_std_menu(hdl, &adv7511_ctrl_ops, + V4L2_CID_DV_TX_RGB_RANGE, V4L2_DV_RGB_RANGE_FULL, + 0, V4L2_DV_RGB_RANGE_AUTO); + sd->ctrl_handler = hdl; + if (hdl->error) { + err = hdl->error; + goto err_hdl; + } + state->hdmi_mode_ctrl->is_private = true; + state->hotplug_ctrl->is_private = true; + state->rx_sense_ctrl->is_private = true; + state->have_edid0_ctrl->is_private = true; + state->rgb_quantization_range_ctrl->is_private = true; + + state->pad.flags = MEDIA_PAD_FL_SINK; + err = media_entity_init(&sd->entity, 1, &state->pad, 0); + if (err) + goto err_hdl; + + /* EDID and CEC i2c addr */ + state->i2c_edid_addr = state->pdata.i2c_edid << 1; + state->i2c_cec_addr = state->pdata.i2c_cec << 1; + + state->chip_revision = adv7511_rd(sd, 0x0); + chip_id[0] = adv7511_rd(sd, 0xf5); + chip_id[1] = adv7511_rd(sd, 0xf6); + if (chip_id[0] != 0x75 || chip_id[1] != 0x11) { + v4l2_err(sd, "chip_id != 0x7511, read 0x%02x%02x\n", chip_id[0], chip_id[1]); + err = -EIO; + goto err_entity; + } + + state->i2c_edid = i2c_new_dummy(client->adapter, state->i2c_edid_addr >> 1); + if (state->i2c_edid == NULL) { + v4l2_err(sd, "failed to register edid i2c client\n"); + goto err_entity; + } + + adv7511_wr(sd, 0xe2, 0x01); /* power down cec section */ + state->work_queue = create_singlethread_workqueue(sd->name); + if (state->work_queue == NULL) { + v4l2_err(sd, "could not create workqueue\n"); + goto err_unreg_cec; + } + + INIT_DELAYED_WORK(&state->edid_handler, adv7511_edid_handler); + + adv7511_init_setup(sd); + adv7511_set_isr(sd, true); + adv7511_check_monitor_present_status(sd); + + v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + return 0; + +err_unreg_cec: + i2c_unregister_device(state->i2c_edid); +err_entity: + media_entity_cleanup(&sd->entity); +err_hdl: + v4l2_ctrl_handler_free(&state->hdl); + return err; +} + +/* ----------------------------------------------------------------------- */ + +static int adv7511_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct adv7511_state *state = get_adv7511_state(sd); + + state->chip_revision = -1; + + v4l2_dbg(1, debug, sd, "%s removed @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + + adv7511_init_setup(sd); + cancel_delayed_work(&state->edid_handler); + i2c_unregister_device(state->i2c_edid); + destroy_workqueue(state->work_queue); + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_device_id adv7511_id[] = { + { "adv7511", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adv7511_id); + +static struct i2c_driver adv7511_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "adv7511", + }, + .probe = adv7511_probe, + .remove = adv7511_remove, + .id_table = adv7511_id, +}; + +module_i2c_driver(adv7511_driver); diff --git a/drivers/media/i2c/adv7604.c b/drivers/media/i2c/adv7604.c index 1d675b58fd71..fbfdd2fc2a36 100644 --- a/drivers/media/i2c/adv7604.c +++ b/drivers/media/i2c/adv7604.c @@ -38,6 +38,7 @@ #include <linux/v4l2-dv-timings.h> #include <media/v4l2-device.h> #include <media/v4l2-ctrls.h> +#include <media/v4l2-dv-timings.h> #include <media/adv7604.h> static int debug; @@ -76,6 +77,7 @@ struct adv7604_state { struct delayed_work delayed_work_enable_hotplug; bool connector_hdmi; bool restart_stdi_once; + u32 prev_input_status; /* i2c clients */ struct i2c_client *i2c_avlink; @@ -260,22 +262,22 @@ static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) static inline unsigned hblanking(const struct v4l2_bt_timings *t) { - return t->hfrontporch + t->hsync + t->hbackporch; + return V4L2_DV_BT_BLANKING_WIDTH(t); } static inline unsigned htotal(const struct v4l2_bt_timings *t) { - return t->width + t->hfrontporch + t->hsync + t->hbackporch; + return V4L2_DV_BT_FRAME_WIDTH(t); } static inline unsigned vblanking(const struct v4l2_bt_timings *t) { - return t->vfrontporch + t->vsync + t->vbackporch; + return V4L2_DV_BT_BLANKING_HEIGHT(t); } static inline unsigned vtotal(const struct v4l2_bt_timings *t) { - return t->height + t->vfrontporch + t->vsync + t->vbackporch; + return V4L2_DV_BT_FRAME_HEIGHT(t); } /* ----------------------------------------------------------------------- */ @@ -761,7 +763,7 @@ static int find_and_set_predefined_video_timings(struct v4l2_subdev *sd, int i; for (i = 0; predef_vid_timings[i].timings.bt.width; i++) { - if (!v4l_match_dv_timings(timings, &predef_vid_timings[i].timings, + if (!v4l2_match_dv_timings(timings, &predef_vid_timings[i].timings, DIGITAL_INPUT ? 250000 : 1000000)) continue; io_write(sd, 0x00, predef_vid_timings[i].vid_std); /* video std */ @@ -990,6 +992,11 @@ static inline bool no_lock_tmds(struct v4l2_subdev *sd) return (io_read(sd, 0x6a) & 0xe0) != 0xe0; } +static inline bool is_hdmi(struct v4l2_subdev *sd) +{ + return hdmi_read(sd, 0x05) & 0x80; +} + static inline bool no_lock_sspd(struct v4l2_subdev *sd) { /* TODO channel 2 */ @@ -1044,38 +1051,6 @@ static int adv7604_g_input_status(struct v4l2_subdev *sd, u32 *status) /* ----------------------------------------------------------------------- */ -static void adv7604_print_timings(struct v4l2_subdev *sd, - struct v4l2_dv_timings *timings, const char *txt, bool detailed) -{ - struct v4l2_bt_timings *bt = &timings->bt; - u32 htot, vtot; - - if (timings->type != V4L2_DV_BT_656_1120) - return; - - htot = htotal(bt); - vtot = vtotal(bt); - - v4l2_info(sd, "%s %dx%d%s%d (%dx%d)", - txt, bt->width, bt->height, bt->interlaced ? "i" : "p", - (htot * vtot) > 0 ? ((u32)bt->pixelclock / - (htot * vtot)) : 0, - htot, vtot); - - if (detailed) { - v4l2_info(sd, " horizontal: fp = %d, %ssync = %d, bp = %d\n", - bt->hfrontporch, - (bt->polarities & V4L2_DV_HSYNC_POS_POL) ? "+" : "-", - bt->hsync, bt->hbackporch); - v4l2_info(sd, " vertical: fp = %d, %ssync = %d, bp = %d\n", - bt->vfrontporch, - (bt->polarities & V4L2_DV_VSYNC_POS_POL) ? "+" : "-", - bt->vsync, bt->vbackporch); - v4l2_info(sd, " pixelclock: %lld, flags: 0x%x, standards: 0x%x\n", - bt->pixelclock, bt->flags, bt->standards); - } -} - struct stdi_readback { u16 bl, lcf, lcvs; u8 hs_pol, vs_pol; @@ -1187,7 +1162,7 @@ static int adv7604_dv_timings_cap(struct v4l2_subdev *sd, cap->type = V4L2_DV_BT_656_1120; cap->bt.max_width = 1920; cap->bt.max_height = 1200; - cap->bt.min_pixelclock = 27000000; + cap->bt.min_pixelclock = 25000000; if (DIGITAL_INPUT) cap->bt.max_pixelclock = 225000000; else @@ -1208,7 +1183,7 @@ static void adv7604_fill_optional_dv_timings_fields(struct v4l2_subdev *sd, int i; for (i = 0; adv7604_timings[i].bt.width; i++) { - if (v4l_match_dv_timings(timings, &adv7604_timings[i], + if (v4l2_match_dv_timings(timings, &adv7604_timings[i], DIGITAL_INPUT ? 250000 : 1000000)) { *timings = adv7604_timings[i]; break; @@ -1242,12 +1217,21 @@ static int adv7604_query_dv_timings(struct v4l2_subdev *sd, V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE; if (DIGITAL_INPUT) { + uint32_t freq; + timings->type = V4L2_DV_BT_656_1120; bt->width = (hdmi_read(sd, 0x07) & 0x0f) * 256 + hdmi_read(sd, 0x08); bt->height = (hdmi_read(sd, 0x09) & 0x0f) * 256 + hdmi_read(sd, 0x0a); - bt->pixelclock = (hdmi_read(sd, 0x06) * 1000000) + + freq = (hdmi_read(sd, 0x06) * 1000000) + ((hdmi_read(sd, 0x3b) & 0x30) >> 4) * 250000; + if (is_hdmi(sd)) { + /* adjust for deep color mode */ + unsigned bits_per_channel = ((hdmi_read(sd, 0x0b) & 0x60) >> 4) + 8; + + freq = freq * 8 / bits_per_channel; + } + bt->pixelclock = freq; bt->hfrontporch = (hdmi_read(sd, 0x20) & 0x03) * 256 + hdmi_read(sd, 0x21); bt->hsync = (hdmi_read(sd, 0x22) & 0x03) * 256 + @@ -1329,8 +1313,8 @@ found: } if (debug > 1) - adv7604_print_timings(sd, timings, - "adv7604_query_dv_timings:", true); + v4l2_print_dv_timings(sd->name, "adv7604_query_dv_timings: ", + timings, true); return 0; } @@ -1372,8 +1356,8 @@ static int adv7604_s_dv_timings(struct v4l2_subdev *sd, if (debug > 1) - adv7604_print_timings(sd, timings, - "adv7604_s_dv_timings:", true); + v4l2_print_dv_timings(sd->name, "adv7604_s_dv_timings: ", + timings, true); return 0; } @@ -1534,6 +1518,7 @@ static int adv7604_isr(struct v4l2_subdev *sd, u32 status, bool *handled) { struct adv7604_state *state = to_state(sd); u8 fmt_change, fmt_change_digital, tx_5v; + u32 input_status; /* format change */ fmt_change = io_read(sd, 0x43) & 0x98; @@ -1544,9 +1529,18 @@ static int adv7604_isr(struct v4l2_subdev *sd, u32 status, bool *handled) io_write(sd, 0x6c, fmt_change_digital); if (fmt_change || fmt_change_digital) { v4l2_dbg(1, debug, sd, - "%s: ADV7604_FMT_CHANGE, fmt_change = 0x%x, fmt_change_digital = 0x%x\n", + "%s: fmt_change = 0x%x, fmt_change_digital = 0x%x\n", __func__, fmt_change, fmt_change_digital); - v4l2_subdev_notify(sd, ADV7604_FMT_CHANGE, NULL); + + adv7604_g_input_status(sd, &input_status); + if (input_status != state->prev_input_status) { + v4l2_dbg(1, debug, sd, + "%s: input_status = 0x%x, prev_input_status = 0x%x\n", + __func__, input_status, state->prev_input_status); + state->prev_input_status = input_status; + v4l2_subdev_notify(sd, ADV7604_FMT_CHANGE, NULL); + } + if (handled) *handled = true; } @@ -1625,7 +1619,7 @@ static void print_avi_infoframe(struct v4l2_subdev *sd) u8 avi_len; u8 avi_ver; - if (!(hdmi_read(sd, 0x05) & 0x80)) { + if (!is_hdmi(sd)) { v4l2_info(sd, "receive DVI-D signal (AVI infoframe not supported)\n"); return; } @@ -1686,6 +1680,12 @@ static int adv7604_log_status(struct v4l2_subdev *sd) "RGB limited range (16-235)", "RGB full range (0-255)", }; + char *deep_color_mode_txt[4] = { + "8-bits per channel", + "10-bits per channel", + "12-bits per channel", + "16-bits per channel (not supported)" + }; v4l2_info(sd, "-----Chip status-----\n"); v4l2_info(sd, "Chip power: %s\n", no_power(sd) ? "off" : "on"); @@ -1723,8 +1723,13 @@ static int adv7604_log_status(struct v4l2_subdev *sd) if (adv7604_query_dv_timings(sd, &timings)) v4l2_info(sd, "No video detected\n"); else - adv7604_print_timings(sd, &timings, "Detected format:", true); - adv7604_print_timings(sd, &state->timings, "Configured format:", true); + v4l2_print_dv_timings(sd->name, "Detected format: ", + &timings, true); + v4l2_print_dv_timings(sd->name, "Configured format: ", + &state->timings, true); + + if (no_signal(sd)) + return 0; v4l2_info(sd, "-----Color space-----\n"); v4l2_info(sd, "RGB quantization range ctrl: %s\n", @@ -1735,15 +1740,40 @@ static int adv7604_log_status(struct v4l2_subdev *sd) (reg_io_0x02 & 0x02) ? "RGB" : "YCbCr", (reg_io_0x02 & 0x04) ? "(16-235)" : "(0-255)", ((reg_io_0x02 & 0x04) ^ (reg_io_0x02 & 0x01)) ? - "enabled" : "disabled"); + "enabled" : "disabled"); v4l2_info(sd, "Color space conversion: %s\n", csc_coeff_sel_rb[cp_read(sd, 0xfc) >> 4]); - /* Digital video */ - if (DIGITAL_INPUT) { - v4l2_info(sd, "-----HDMI status-----\n"); - v4l2_info(sd, "HDCP encrypted content: %s\n", - hdmi_read(sd, 0x05) & 0x40 ? "true" : "false"); + if (!DIGITAL_INPUT) + return 0; + + v4l2_info(sd, "-----%s status-----\n", is_hdmi(sd) ? "HDMI" : "DVI-D"); + v4l2_info(sd, "HDCP encrypted content: %s\n", (hdmi_read(sd, 0x05) & 0x40) ? "true" : "false"); + v4l2_info(sd, "HDCP keys read: %s%s\n", + (hdmi_read(sd, 0x04) & 0x20) ? "yes" : "no", + (hdmi_read(sd, 0x04) & 0x10) ? "ERROR" : ""); + if (!is_hdmi(sd)) { + bool audio_pll_locked = hdmi_read(sd, 0x04) & 0x01; + bool audio_sample_packet_detect = hdmi_read(sd, 0x18) & 0x01; + bool audio_mute = io_read(sd, 0x65) & 0x40; + + v4l2_info(sd, "Audio: pll %s, samples %s, %s\n", + audio_pll_locked ? "locked" : "not locked", + audio_sample_packet_detect ? "detected" : "not detected", + audio_mute ? "muted" : "enabled"); + if (audio_pll_locked && audio_sample_packet_detect) { + v4l2_info(sd, "Audio format: %s\n", + (hdmi_read(sd, 0x07) & 0x20) ? "multi-channel" : "stereo"); + } + v4l2_info(sd, "Audio CTS: %u\n", (hdmi_read(sd, 0x5b) << 12) + + (hdmi_read(sd, 0x5c) << 8) + + (hdmi_read(sd, 0x5d) & 0xf0)); + v4l2_info(sd, "Audio N: %u\n", ((hdmi_read(sd, 0x5d) & 0x0f) << 16) + + (hdmi_read(sd, 0x5e) << 8) + + hdmi_read(sd, 0x5f)); + v4l2_info(sd, "AV Mute: %s\n", (hdmi_read(sd, 0x04) & 0x40) ? "on" : "off"); + + v4l2_info(sd, "Deep color mode: %s\n", deep_color_mode_txt[(hdmi_read(sd, 0x0b) & 0x60) >> 5]); print_avi_infoframe(sd); } @@ -1952,6 +1982,10 @@ static int adv7604_probe(struct i2c_client *client, return -ENOMEM; } + /* initialize variables */ + state->restart_stdi_once = true; + state->prev_input_status = ~0; + /* platform data */ if (!pdata) { v4l_err(client, "No platform data!\n"); @@ -1987,29 +2021,30 @@ static int adv7604_probe(struct i2c_client *client, /* private controls */ state->detect_tx_5v_ctrl = v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_DV_RX_POWER_PRESENT, 0, 1, 0, 0); - state->detect_tx_5v_ctrl->is_private = true; state->rgb_quantization_range_ctrl = v4l2_ctrl_new_std_menu(hdl, &adv7604_ctrl_ops, V4L2_CID_DV_RX_RGB_RANGE, V4L2_DV_RGB_RANGE_FULL, 0, V4L2_DV_RGB_RANGE_AUTO); - state->rgb_quantization_range_ctrl->is_private = true; /* custom controls */ state->analog_sampling_phase_ctrl = v4l2_ctrl_new_custom(hdl, &adv7604_ctrl_analog_sampling_phase, NULL); - state->analog_sampling_phase_ctrl->is_private = true; state->free_run_color_manual_ctrl = v4l2_ctrl_new_custom(hdl, &adv7604_ctrl_free_run_color_manual, NULL); - state->free_run_color_manual_ctrl->is_private = true; state->free_run_color_ctrl = v4l2_ctrl_new_custom(hdl, &adv7604_ctrl_free_run_color, NULL); - state->free_run_color_ctrl->is_private = true; sd->ctrl_handler = hdl; if (hdl->error) { err = hdl->error; goto err_hdl; } + state->detect_tx_5v_ctrl->is_private = true; + state->rgb_quantization_range_ctrl->is_private = true; + state->analog_sampling_phase_ctrl->is_private = true; + state->free_run_color_manual_ctrl->is_private = true; + state->free_run_color_ctrl->is_private = true; + if (adv7604_s_detect_tx_5v_ctrl(sd)) { err = -ENODEV; goto err_hdl; @@ -2035,7 +2070,6 @@ static int adv7604_probe(struct i2c_client *client, v4l2_err(sd, "failed to create all i2c clients\n"); goto err_i2c; } - state->restart_stdi_once = true; /* work queues */ state->work_queues = create_singlethread_workqueue(client->name); diff --git a/drivers/media/i2c/adv7842.c b/drivers/media/i2c/adv7842.c new file mode 100644 index 000000000000..d1748901337c --- /dev/null +++ b/drivers/media/i2c/adv7842.c @@ -0,0 +1,2946 @@ +/* + * adv7842 - Analog Devices ADV7842 video decoder driver + * + * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +/* + * References (c = chapter, p = page): + * REF_01 - Analog devices, ADV7842, Register Settings Recommendations, + * Revision 2.5, June 2010 + * REF_02 - Analog devices, Register map documentation, Documentation of + * the register maps, Software manual, Rev. F, June 2010 + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/videodev2.h> +#include <linux/workqueue.h> +#include <linux/v4l2-dv-timings.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-dv-timings.h> +#include <media/adv7842.h> + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +MODULE_DESCRIPTION("Analog Devices ADV7842 video decoder driver"); +MODULE_AUTHOR("Hans Verkuil <hans.verkuil@cisco.com>"); +MODULE_AUTHOR("Martin Bugge <marbugge@cisco.com>"); +MODULE_LICENSE("GPL"); + +/* ADV7842 system clock frequency */ +#define ADV7842_fsc (28636360) + +/* +********************************************************************** +* +* Arrays with configuration parameters for the ADV7842 +* +********************************************************************** +*/ + +struct adv7842_state { + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler hdl; + enum adv7842_mode mode; + struct v4l2_dv_timings timings; + enum adv7842_vid_std_select vid_std_select; + v4l2_std_id norm; + struct { + u8 edid[256]; + u32 present; + } hdmi_edid; + struct { + u8 edid[256]; + u32 present; + } vga_edid; + struct v4l2_fract aspect_ratio; + u32 rgb_quantization_range; + bool is_cea_format; + struct workqueue_struct *work_queues; + struct delayed_work delayed_work_enable_hotplug; + bool connector_hdmi; + bool hdmi_port_a; + + /* i2c clients */ + struct i2c_client *i2c_sdp_io; + struct i2c_client *i2c_sdp; + struct i2c_client *i2c_cp; + struct i2c_client *i2c_vdp; + struct i2c_client *i2c_afe; + struct i2c_client *i2c_hdmi; + struct i2c_client *i2c_repeater; + struct i2c_client *i2c_edid; + struct i2c_client *i2c_infoframe; + struct i2c_client *i2c_cec; + struct i2c_client *i2c_avlink; + + /* controls */ + struct v4l2_ctrl *detect_tx_5v_ctrl; + struct v4l2_ctrl *analog_sampling_phase_ctrl; + struct v4l2_ctrl *free_run_color_ctrl_manual; + struct v4l2_ctrl *free_run_color_ctrl; + struct v4l2_ctrl *rgb_quantization_range_ctrl; +}; + +/* Unsupported timings. This device cannot support 720p30. */ +static const struct v4l2_dv_timings adv7842_timings_exceptions[] = { + V4L2_DV_BT_CEA_1280X720P30, + { } +}; + +static bool adv7842_check_dv_timings(const struct v4l2_dv_timings *t, void *hdl) +{ + int i; + + for (i = 0; adv7842_timings_exceptions[i].bt.width; i++) + if (v4l2_match_dv_timings(t, adv7842_timings_exceptions + i, 0)) + return false; + return true; +} + +struct adv7842_video_standards { + struct v4l2_dv_timings timings; + u8 vid_std; + u8 v_freq; +}; + +/* sorted by number of lines */ +static const struct adv7842_video_standards adv7842_prim_mode_comp[] = { + /* { V4L2_DV_BT_CEA_720X480P59_94, 0x0a, 0x00 }, TODO flickering */ + { V4L2_DV_BT_CEA_720X576P50, 0x0b, 0x00 }, + { V4L2_DV_BT_CEA_1280X720P50, 0x19, 0x01 }, + { V4L2_DV_BT_CEA_1280X720P60, 0x19, 0x00 }, + { V4L2_DV_BT_CEA_1920X1080P24, 0x1e, 0x04 }, + { V4L2_DV_BT_CEA_1920X1080P25, 0x1e, 0x03 }, + { V4L2_DV_BT_CEA_1920X1080P30, 0x1e, 0x02 }, + { V4L2_DV_BT_CEA_1920X1080P50, 0x1e, 0x01 }, + { V4L2_DV_BT_CEA_1920X1080P60, 0x1e, 0x00 }, + /* TODO add 1920x1080P60_RB (CVT timing) */ + { }, +}; + +/* sorted by number of lines */ +static const struct adv7842_video_standards adv7842_prim_mode_gr[] = { + { V4L2_DV_BT_DMT_640X480P60, 0x08, 0x00 }, + { V4L2_DV_BT_DMT_640X480P72, 0x09, 0x00 }, + { V4L2_DV_BT_DMT_640X480P75, 0x0a, 0x00 }, + { V4L2_DV_BT_DMT_640X480P85, 0x0b, 0x00 }, + { V4L2_DV_BT_DMT_800X600P56, 0x00, 0x00 }, + { V4L2_DV_BT_DMT_800X600P60, 0x01, 0x00 }, + { V4L2_DV_BT_DMT_800X600P72, 0x02, 0x00 }, + { V4L2_DV_BT_DMT_800X600P75, 0x03, 0x00 }, + { V4L2_DV_BT_DMT_800X600P85, 0x04, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P60, 0x0c, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P70, 0x0d, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P75, 0x0e, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P85, 0x0f, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P60, 0x05, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P75, 0x06, 0x00 }, + { V4L2_DV_BT_DMT_1360X768P60, 0x12, 0x00 }, + { V4L2_DV_BT_DMT_1366X768P60, 0x13, 0x00 }, + { V4L2_DV_BT_DMT_1400X1050P60, 0x14, 0x00 }, + { V4L2_DV_BT_DMT_1400X1050P75, 0x15, 0x00 }, + { V4L2_DV_BT_DMT_1600X1200P60, 0x16, 0x00 }, /* TODO not tested */ + /* TODO add 1600X1200P60_RB (not a DMT timing) */ + { V4L2_DV_BT_DMT_1680X1050P60, 0x18, 0x00 }, + { V4L2_DV_BT_DMT_1920X1200P60_RB, 0x19, 0x00 }, /* TODO not tested */ + { }, +}; + +/* sorted by number of lines */ +static const struct adv7842_video_standards adv7842_prim_mode_hdmi_comp[] = { + { V4L2_DV_BT_CEA_720X480P59_94, 0x0a, 0x00 }, + { V4L2_DV_BT_CEA_720X576P50, 0x0b, 0x00 }, + { V4L2_DV_BT_CEA_1280X720P50, 0x13, 0x01 }, + { V4L2_DV_BT_CEA_1280X720P60, 0x13, 0x00 }, + { V4L2_DV_BT_CEA_1920X1080P24, 0x1e, 0x04 }, + { V4L2_DV_BT_CEA_1920X1080P25, 0x1e, 0x03 }, + { V4L2_DV_BT_CEA_1920X1080P30, 0x1e, 0x02 }, + { V4L2_DV_BT_CEA_1920X1080P50, 0x1e, 0x01 }, + { V4L2_DV_BT_CEA_1920X1080P60, 0x1e, 0x00 }, + { }, +}; + +/* sorted by number of lines */ +static const struct adv7842_video_standards adv7842_prim_mode_hdmi_gr[] = { + { V4L2_DV_BT_DMT_640X480P60, 0x08, 0x00 }, + { V4L2_DV_BT_DMT_640X480P72, 0x09, 0x00 }, + { V4L2_DV_BT_DMT_640X480P75, 0x0a, 0x00 }, + { V4L2_DV_BT_DMT_640X480P85, 0x0b, 0x00 }, + { V4L2_DV_BT_DMT_800X600P56, 0x00, 0x00 }, + { V4L2_DV_BT_DMT_800X600P60, 0x01, 0x00 }, + { V4L2_DV_BT_DMT_800X600P72, 0x02, 0x00 }, + { V4L2_DV_BT_DMT_800X600P75, 0x03, 0x00 }, + { V4L2_DV_BT_DMT_800X600P85, 0x04, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P60, 0x0c, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P70, 0x0d, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P75, 0x0e, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P85, 0x0f, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P60, 0x05, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P75, 0x06, 0x00 }, + { }, +}; + +/* ----------------------------------------------------------------------- */ + +static inline struct adv7842_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7842_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct adv7842_state, hdl)->sd; +} + +static inline unsigned hblanking(const struct v4l2_bt_timings *t) +{ + return V4L2_DV_BT_BLANKING_WIDTH(t); +} + +static inline unsigned htotal(const struct v4l2_bt_timings *t) +{ + return V4L2_DV_BT_FRAME_WIDTH(t); +} + +static inline unsigned vblanking(const struct v4l2_bt_timings *t) +{ + return V4L2_DV_BT_BLANKING_HEIGHT(t); +} + +static inline unsigned vtotal(const struct v4l2_bt_timings *t) +{ + return V4L2_DV_BT_FRAME_HEIGHT(t); +} + + +/* ----------------------------------------------------------------------- */ + +static s32 adv_smbus_read_byte_data_check(struct i2c_client *client, + u8 command, bool check) +{ + union i2c_smbus_data data; + + if (!i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_READ, command, + I2C_SMBUS_BYTE_DATA, &data)) + return data.byte; + if (check) + v4l_err(client, "error reading %02x, %02x\n", + client->addr, command); + return -EIO; +} + +static s32 adv_smbus_read_byte_data(struct i2c_client *client, u8 command) +{ + int i; + + for (i = 0; i < 3; i++) { + int ret = adv_smbus_read_byte_data_check(client, command, true); + + if (ret >= 0) { + if (i) + v4l_err(client, "read ok after %d retries\n", i); + return ret; + } + } + v4l_err(client, "read failed\n"); + return -EIO; +} + +static s32 adv_smbus_write_byte_data(struct i2c_client *client, + u8 command, u8 value) +{ + union i2c_smbus_data data; + int err; + int i; + + data.byte = value; + for (i = 0; i < 3; i++) { + err = i2c_smbus_xfer(client->adapter, client->addr, + client->flags, + I2C_SMBUS_WRITE, command, + I2C_SMBUS_BYTE_DATA, &data); + if (!err) + break; + } + if (err < 0) + v4l_err(client, "error writing %02x, %02x, %02x\n", + client->addr, command, value); + return err; +} + +static void adv_smbus_write_byte_no_check(struct i2c_client *client, + u8 command, u8 value) +{ + union i2c_smbus_data data; + data.byte = value; + + i2c_smbus_xfer(client->adapter, client->addr, + client->flags, + I2C_SMBUS_WRITE, command, + I2C_SMBUS_BYTE_DATA, &data); +} + +static s32 adv_smbus_write_i2c_block_data(struct i2c_client *client, + u8 command, unsigned length, const u8 *values) +{ + union i2c_smbus_data data; + + if (length > I2C_SMBUS_BLOCK_MAX) + length = I2C_SMBUS_BLOCK_MAX; + data.block[0] = length; + memcpy(data.block + 1, values, length); + return i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_WRITE, command, + I2C_SMBUS_I2C_BLOCK_DATA, &data); +} + +/* ----------------------------------------------------------------------- */ + +static inline int io_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return adv_smbus_read_byte_data(client, reg); +} + +static inline int io_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return adv_smbus_write_byte_data(client, reg, val); +} + +static inline int io_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return io_write(sd, reg, (io_read(sd, reg) & mask) | val); +} + +static inline int avlink_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_avlink, reg); +} + +static inline int avlink_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_avlink, reg, val); +} + +static inline int cec_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_cec, reg); +} + +static inline int cec_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_cec, reg, val); +} + +static inline int cec_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return cec_write(sd, reg, (cec_read(sd, reg) & mask) | val); +} + +static inline int infoframe_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_infoframe, reg); +} + +static inline int infoframe_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_infoframe, reg, val); +} + +static inline int sdp_io_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_sdp_io, reg); +} + +static inline int sdp_io_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_sdp_io, reg, val); +} + +static inline int sdp_io_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return sdp_io_write(sd, reg, (sdp_io_read(sd, reg) & mask) | val); +} + +static inline int sdp_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_sdp, reg); +} + +static inline int sdp_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_sdp, reg, val); +} + +static inline int sdp_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return sdp_write(sd, reg, (sdp_read(sd, reg) & mask) | val); +} + +static inline int afe_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_afe, reg); +} + +static inline int afe_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_afe, reg, val); +} + +static inline int afe_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return afe_write(sd, reg, (afe_read(sd, reg) & mask) | val); +} + +static inline int rep_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_repeater, reg); +} + +static inline int rep_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_repeater, reg, val); +} + +static inline int rep_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return rep_write(sd, reg, (rep_read(sd, reg) & mask) | val); +} + +static inline int edid_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_edid, reg); +} + +static inline int edid_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_edid, reg, val); +} + +static inline int hdmi_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_hdmi, reg); +} + +static inline int hdmi_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_hdmi, reg, val); +} + +static inline int cp_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_cp, reg); +} + +static inline int cp_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_cp, reg, val); +} + +static inline int cp_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return cp_write(sd, reg, (cp_read(sd, reg) & mask) | val); +} + +static inline int vdp_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_vdp, reg); +} + +static inline int vdp_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_vdp, reg, val); +} + +static void main_reset(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + adv_smbus_write_byte_no_check(client, 0xff, 0x80); + + mdelay(2); +} + +/* ----------------------------------------------------------------------- */ + +static inline bool is_digital_input(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + + return state->mode == ADV7842_MODE_HDMI; +} + +static const struct v4l2_dv_timings_cap adv7842_timings_cap_analog = { + .type = V4L2_DV_BT_656_1120, + .bt = { + .max_width = 1920, + .max_height = 1200, + .min_pixelclock = 25000000, + .max_pixelclock = 170000000, + .standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + .capabilities = V4L2_DV_BT_CAP_PROGRESSIVE | + V4L2_DV_BT_CAP_REDUCED_BLANKING | V4L2_DV_BT_CAP_CUSTOM, + }, +}; + +static const struct v4l2_dv_timings_cap adv7842_timings_cap_digital = { + .type = V4L2_DV_BT_656_1120, + .bt = { + .max_width = 1920, + .max_height = 1200, + .min_pixelclock = 25000000, + .max_pixelclock = 225000000, + .standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + .capabilities = V4L2_DV_BT_CAP_PROGRESSIVE | + V4L2_DV_BT_CAP_REDUCED_BLANKING | V4L2_DV_BT_CAP_CUSTOM, + }, +}; + +static inline const struct v4l2_dv_timings_cap * +adv7842_get_dv_timings_cap(struct v4l2_subdev *sd) +{ + return is_digital_input(sd) ? &adv7842_timings_cap_digital : + &adv7842_timings_cap_analog; +} + +/* ----------------------------------------------------------------------- */ + +static void adv7842_delayed_work_enable_hotplug(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct adv7842_state *state = container_of(dwork, + struct adv7842_state, delayed_work_enable_hotplug); + struct v4l2_subdev *sd = &state->sd; + int present = state->hdmi_edid.present; + u8 mask = 0; + + v4l2_dbg(2, debug, sd, "%s: enable hotplug on ports: 0x%x\n", + __func__, present); + + if (present & 0x1) + mask |= 0x20; /* port A */ + if (present & 0x2) + mask |= 0x10; /* port B */ + io_write_and_or(sd, 0x20, 0xcf, mask); +} + +static int edid_write_vga_segment(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct adv7842_state *state = to_state(sd); + const u8 *val = state->vga_edid.edid; + int err = 0; + int i; + + v4l2_dbg(2, debug, sd, "%s: write EDID on VGA port\n", __func__); + + /* HPA disable on port A and B */ + io_write_and_or(sd, 0x20, 0xcf, 0x00); + + /* Disable I2C access to internal EDID ram from VGA DDC port */ + rep_write_and_or(sd, 0x7f, 0x7f, 0x00); + + /* edid segment pointer '1' for VGA port */ + rep_write_and_or(sd, 0x77, 0xef, 0x10); + + for (i = 0; !err && i < 256; i += I2C_SMBUS_BLOCK_MAX) + err = adv_smbus_write_i2c_block_data(state->i2c_edid, i, + I2C_SMBUS_BLOCK_MAX, val + i); + if (err) + return err; + + /* Calculates the checksums and enables I2C access + * to internal EDID ram from VGA DDC port. + */ + rep_write_and_or(sd, 0x7f, 0x7f, 0x80); + + for (i = 0; i < 1000; i++) { + if (rep_read(sd, 0x79) & 0x20) + break; + mdelay(1); + } + if (i == 1000) { + v4l_err(client, "error enabling edid on VGA port\n"); + return -EIO; + } + + /* enable hotplug after 200 ms */ + queue_delayed_work(state->work_queues, + &state->delayed_work_enable_hotplug, HZ / 5); + + return 0; +} + +static int edid_spa_location(const u8 *edid) +{ + u8 d; + + /* + * TODO, improve and update for other CEA extensions + * currently only for 1 segment (256 bytes), + * i.e. 1 extension block and CEA revision 3. + */ + if ((edid[0x7e] != 1) || + (edid[0x80] != 0x02) || + (edid[0x81] != 0x03)) { + return -EINVAL; + } + /* + * search Vendor Specific Data Block (tag 3) + */ + d = edid[0x82] & 0x7f; + if (d > 4) { + int i = 0x84; + int end = 0x80 + d; + do { + u8 tag = edid[i]>>5; + u8 len = edid[i] & 0x1f; + + if ((tag == 3) && (len >= 5)) + return i + 4; + i += len + 1; + } while (i < end); + } + return -EINVAL; +} + +static int edid_write_hdmi_segment(struct v4l2_subdev *sd, u8 port) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct adv7842_state *state = to_state(sd); + const u8 *val = state->hdmi_edid.edid; + u8 cur_mask = rep_read(sd, 0x77) & 0x0c; + u8 mask = port == 0 ? 0x4 : 0x8; + int spa_loc = edid_spa_location(val); + int err = 0; + int i; + + v4l2_dbg(2, debug, sd, "%s: write EDID on port %d (spa at 0x%x)\n", + __func__, port, spa_loc); + + /* HPA disable on port A and B */ + io_write_and_or(sd, 0x20, 0xcf, 0x00); + + /* Disable I2C access to internal EDID ram from HDMI DDC ports */ + rep_write_and_or(sd, 0x77, 0xf3, 0x00); + + /* edid segment pointer '0' for HDMI ports */ + rep_write_and_or(sd, 0x77, 0xef, 0x00); + + for (i = 0; !err && i < 256; i += I2C_SMBUS_BLOCK_MAX) + err = adv_smbus_write_i2c_block_data(state->i2c_edid, i, + I2C_SMBUS_BLOCK_MAX, val + i); + if (err) + return err; + + if (spa_loc > 0) { + if (port == 0) { + /* port A SPA */ + rep_write(sd, 0x72, val[spa_loc]); + rep_write(sd, 0x73, val[spa_loc + 1]); + } else { + /* port B SPA */ + rep_write(sd, 0x74, val[spa_loc]); + rep_write(sd, 0x75, val[spa_loc + 1]); + } + rep_write(sd, 0x76, spa_loc); + } else { + /* default register values for SPA */ + if (port == 0) { + /* port A SPA */ + rep_write(sd, 0x72, 0); + rep_write(sd, 0x73, 0); + } else { + /* port B SPA */ + rep_write(sd, 0x74, 0); + rep_write(sd, 0x75, 0); + } + rep_write(sd, 0x76, 0xc0); + } + rep_write_and_or(sd, 0x77, 0xbf, 0x00); + + /* Calculates the checksums and enables I2C access to internal + * EDID ram from HDMI DDC ports + */ + rep_write_and_or(sd, 0x77, 0xf3, mask | cur_mask); + + for (i = 0; i < 1000; i++) { + if (rep_read(sd, 0x7d) & mask) + break; + mdelay(1); + } + if (i == 1000) { + v4l_err(client, "error enabling edid on port %d\n", port); + return -EIO; + } + + /* enable hotplug after 200 ms */ + queue_delayed_work(state->work_queues, + &state->delayed_work_enable_hotplug, HZ / 5); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static void adv7842_inv_register(struct v4l2_subdev *sd) +{ + v4l2_info(sd, "0x000-0x0ff: IO Map\n"); + v4l2_info(sd, "0x100-0x1ff: AVLink Map\n"); + v4l2_info(sd, "0x200-0x2ff: CEC Map\n"); + v4l2_info(sd, "0x300-0x3ff: InfoFrame Map\n"); + v4l2_info(sd, "0x400-0x4ff: SDP_IO Map\n"); + v4l2_info(sd, "0x500-0x5ff: SDP Map\n"); + v4l2_info(sd, "0x600-0x6ff: AFE Map\n"); + v4l2_info(sd, "0x700-0x7ff: Repeater Map\n"); + v4l2_info(sd, "0x800-0x8ff: EDID Map\n"); + v4l2_info(sd, "0x900-0x9ff: HDMI Map\n"); + v4l2_info(sd, "0xa00-0xaff: CP Map\n"); + v4l2_info(sd, "0xb00-0xbff: VDP Map\n"); +} + +static int adv7842_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + reg->size = 1; + switch (reg->reg >> 8) { + case 0: + reg->val = io_read(sd, reg->reg & 0xff); + break; + case 1: + reg->val = avlink_read(sd, reg->reg & 0xff); + break; + case 2: + reg->val = cec_read(sd, reg->reg & 0xff); + break; + case 3: + reg->val = infoframe_read(sd, reg->reg & 0xff); + break; + case 4: + reg->val = sdp_io_read(sd, reg->reg & 0xff); + break; + case 5: + reg->val = sdp_read(sd, reg->reg & 0xff); + break; + case 6: + reg->val = afe_read(sd, reg->reg & 0xff); + break; + case 7: + reg->val = rep_read(sd, reg->reg & 0xff); + break; + case 8: + reg->val = edid_read(sd, reg->reg & 0xff); + break; + case 9: + reg->val = hdmi_read(sd, reg->reg & 0xff); + break; + case 0xa: + reg->val = cp_read(sd, reg->reg & 0xff); + break; + case 0xb: + reg->val = vdp_read(sd, reg->reg & 0xff); + break; + default: + v4l2_info(sd, "Register %03llx not supported\n", reg->reg); + adv7842_inv_register(sd); + break; + } + return 0; +} + +static int adv7842_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + u8 val = reg->val & 0xff; + + switch (reg->reg >> 8) { + case 0: + io_write(sd, reg->reg & 0xff, val); + break; + case 1: + avlink_write(sd, reg->reg & 0xff, val); + break; + case 2: + cec_write(sd, reg->reg & 0xff, val); + break; + case 3: + infoframe_write(sd, reg->reg & 0xff, val); + break; + case 4: + sdp_io_write(sd, reg->reg & 0xff, val); + break; + case 5: + sdp_write(sd, reg->reg & 0xff, val); + break; + case 6: + afe_write(sd, reg->reg & 0xff, val); + break; + case 7: + rep_write(sd, reg->reg & 0xff, val); + break; + case 8: + edid_write(sd, reg->reg & 0xff, val); + break; + case 9: + hdmi_write(sd, reg->reg & 0xff, val); + break; + case 0xa: + cp_write(sd, reg->reg & 0xff, val); + break; + case 0xb: + vdp_write(sd, reg->reg & 0xff, val); + break; + default: + v4l2_info(sd, "Register %03llx not supported\n", reg->reg); + adv7842_inv_register(sd); + break; + } + return 0; +} +#endif + +static int adv7842_s_detect_tx_5v_ctrl(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + int prev = v4l2_ctrl_g_ctrl(state->detect_tx_5v_ctrl); + u8 reg_io_6f = io_read(sd, 0x6f); + int val = 0; + + if (reg_io_6f & 0x02) + val |= 1; /* port A */ + if (reg_io_6f & 0x01) + val |= 2; /* port B */ + + v4l2_dbg(1, debug, sd, "%s: 0x%x -> 0x%x\n", __func__, prev, val); + + if (val != prev) + return v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl, val); + return 0; +} + +static int find_and_set_predefined_video_timings(struct v4l2_subdev *sd, + u8 prim_mode, + const struct adv7842_video_standards *predef_vid_timings, + const struct v4l2_dv_timings *timings) +{ + int i; + + for (i = 0; predef_vid_timings[i].timings.bt.width; i++) { + if (!v4l2_match_dv_timings(timings, &predef_vid_timings[i].timings, + is_digital_input(sd) ? 250000 : 1000000)) + continue; + /* video std */ + io_write(sd, 0x00, predef_vid_timings[i].vid_std); + /* v_freq and prim mode */ + io_write(sd, 0x01, (predef_vid_timings[i].v_freq << 4) + prim_mode); + return 0; + } + + return -1; +} + +static int configure_predefined_video_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv7842_state *state = to_state(sd); + int err; + + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + /* reset to default values */ + io_write(sd, 0x16, 0x43); + io_write(sd, 0x17, 0x5a); + /* disable embedded syncs for auto graphics mode */ + cp_write_and_or(sd, 0x81, 0xef, 0x00); + cp_write(sd, 0x26, 0x00); + cp_write(sd, 0x27, 0x00); + cp_write(sd, 0x28, 0x00); + cp_write(sd, 0x29, 0x00); + cp_write(sd, 0x8f, 0x00); + cp_write(sd, 0x90, 0x00); + cp_write(sd, 0xa5, 0x00); + cp_write(sd, 0xa6, 0x00); + cp_write(sd, 0xa7, 0x00); + cp_write(sd, 0xab, 0x00); + cp_write(sd, 0xac, 0x00); + + switch (state->mode) { + case ADV7842_MODE_COMP: + case ADV7842_MODE_RGB: + err = find_and_set_predefined_video_timings(sd, + 0x01, adv7842_prim_mode_comp, timings); + if (err) + err = find_and_set_predefined_video_timings(sd, + 0x02, adv7842_prim_mode_gr, timings); + break; + case ADV7842_MODE_HDMI: + err = find_and_set_predefined_video_timings(sd, + 0x05, adv7842_prim_mode_hdmi_comp, timings); + if (err) + err = find_and_set_predefined_video_timings(sd, + 0x06, adv7842_prim_mode_hdmi_gr, timings); + break; + default: + v4l2_dbg(2, debug, sd, "%s: Unknown mode %d\n", + __func__, state->mode); + err = -1; + break; + } + + + return err; +} + +static void configure_custom_video_timings(struct v4l2_subdev *sd, + const struct v4l2_bt_timings *bt) +{ + struct adv7842_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + u32 width = htotal(bt); + u32 height = vtotal(bt); + u16 cp_start_sav = bt->hsync + bt->hbackporch - 4; + u16 cp_start_eav = width - bt->hfrontporch; + u16 cp_start_vbi = height - bt->vfrontporch + 1; + u16 cp_end_vbi = bt->vsync + bt->vbackporch + 1; + u16 ch1_fr_ll = (((u32)bt->pixelclock / 100) > 0) ? + ((width * (ADV7842_fsc / 100)) / ((u32)bt->pixelclock / 100)) : 0; + const u8 pll[2] = { + 0xc0 | ((width >> 8) & 0x1f), + width & 0xff + }; + + v4l2_dbg(2, debug, sd, "%s\n", __func__); + + switch (state->mode) { + case ADV7842_MODE_COMP: + case ADV7842_MODE_RGB: + /* auto graphics */ + io_write(sd, 0x00, 0x07); /* video std */ + io_write(sd, 0x01, 0x02); /* prim mode */ + /* enable embedded syncs for auto graphics mode */ + cp_write_and_or(sd, 0x81, 0xef, 0x10); + + /* Should only be set in auto-graphics mode [REF_02, p. 91-92] */ + /* setup PLL_DIV_MAN_EN and PLL_DIV_RATIO */ + /* IO-map reg. 0x16 and 0x17 should be written in sequence */ + if (adv_smbus_write_i2c_block_data(client, 0x16, 2, pll)) { + v4l2_err(sd, "writing to reg 0x16 and 0x17 failed\n"); + break; + } + + /* active video - horizontal timing */ + cp_write(sd, 0x26, (cp_start_sav >> 8) & 0xf); + cp_write(sd, 0x27, (cp_start_sav & 0xff)); + cp_write(sd, 0x28, (cp_start_eav >> 8) & 0xf); + cp_write(sd, 0x29, (cp_start_eav & 0xff)); + + /* active video - vertical timing */ + cp_write(sd, 0xa5, (cp_start_vbi >> 4) & 0xff); + cp_write(sd, 0xa6, ((cp_start_vbi & 0xf) << 4) | + ((cp_end_vbi >> 8) & 0xf)); + cp_write(sd, 0xa7, cp_end_vbi & 0xff); + break; + case ADV7842_MODE_HDMI: + /* set default prim_mode/vid_std for HDMI + accoring to [REF_03, c. 4.2] */ + io_write(sd, 0x00, 0x02); /* video std */ + io_write(sd, 0x01, 0x06); /* prim mode */ + break; + default: + v4l2_dbg(2, debug, sd, "%s: Unknown mode %d\n", + __func__, state->mode); + break; + } + + cp_write(sd, 0x8f, (ch1_fr_ll >> 8) & 0x7); + cp_write(sd, 0x90, ch1_fr_ll & 0xff); + cp_write(sd, 0xab, (height >> 4) & 0xff); + cp_write(sd, 0xac, (height & 0x0f) << 4); +} + +static void set_rgb_quantization_range(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + + switch (state->rgb_quantization_range) { + case V4L2_DV_RGB_RANGE_AUTO: + /* automatic */ + if (is_digital_input(sd) && !(hdmi_read(sd, 0x05) & 0x80)) { + /* receiving DVI-D signal */ + + /* ADV7842 selects RGB limited range regardless of + input format (CE/IT) in automatic mode */ + if (state->timings.bt.standards & V4L2_DV_BT_STD_CEA861) { + /* RGB limited range (16-235) */ + io_write_and_or(sd, 0x02, 0x0f, 0x00); + + } else { + /* RGB full range (0-255) */ + io_write_and_or(sd, 0x02, 0x0f, 0x10); + } + } else { + /* receiving HDMI or analog signal, set automode */ + io_write_and_or(sd, 0x02, 0x0f, 0xf0); + } + break; + case V4L2_DV_RGB_RANGE_LIMITED: + /* RGB limited range (16-235) */ + io_write_and_or(sd, 0x02, 0x0f, 0x00); + break; + case V4L2_DV_RGB_RANGE_FULL: + /* RGB full range (0-255) */ + io_write_and_or(sd, 0x02, 0x0f, 0x10); + break; + } +} + +static int adv7842_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct adv7842_state *state = to_state(sd); + + /* TODO SDP ctrls + contrast/brightness/hue/free run is acting a bit strange, + not sure if sdp csc is correct. + */ + switch (ctrl->id) { + /* standard ctrls */ + case V4L2_CID_BRIGHTNESS: + cp_write(sd, 0x3c, ctrl->val); + sdp_write(sd, 0x14, ctrl->val); + /* ignore lsb sdp 0x17[3:2] */ + return 0; + case V4L2_CID_CONTRAST: + cp_write(sd, 0x3a, ctrl->val); + sdp_write(sd, 0x13, ctrl->val); + /* ignore lsb sdp 0x17[1:0] */ + return 0; + case V4L2_CID_SATURATION: + cp_write(sd, 0x3b, ctrl->val); + sdp_write(sd, 0x15, ctrl->val); + /* ignore lsb sdp 0x17[5:4] */ + return 0; + case V4L2_CID_HUE: + cp_write(sd, 0x3d, ctrl->val); + sdp_write(sd, 0x16, ctrl->val); + /* ignore lsb sdp 0x17[7:6] */ + return 0; + /* custom ctrls */ + case V4L2_CID_ADV_RX_ANALOG_SAMPLING_PHASE: + afe_write(sd, 0xc8, ctrl->val); + return 0; + case V4L2_CID_ADV_RX_FREE_RUN_COLOR_MANUAL: + cp_write_and_or(sd, 0xbf, ~0x04, (ctrl->val << 2)); + sdp_write_and_or(sd, 0xdd, ~0x04, (ctrl->val << 2)); + return 0; + case V4L2_CID_ADV_RX_FREE_RUN_COLOR: { + u8 R = (ctrl->val & 0xff0000) >> 16; + u8 G = (ctrl->val & 0x00ff00) >> 8; + u8 B = (ctrl->val & 0x0000ff); + /* RGB -> YUV, numerical approximation */ + int Y = 66 * R + 129 * G + 25 * B; + int U = -38 * R - 74 * G + 112 * B; + int V = 112 * R - 94 * G - 18 * B; + + /* Scale down to 8 bits with rounding */ + Y = (Y + 128) >> 8; + U = (U + 128) >> 8; + V = (V + 128) >> 8; + /* make U,V positive */ + Y += 16; + U += 128; + V += 128; + + v4l2_dbg(1, debug, sd, "R %x, G %x, B %x\n", R, G, B); + v4l2_dbg(1, debug, sd, "Y %x, U %x, V %x\n", Y, U, V); + + /* CP */ + cp_write(sd, 0xc1, R); + cp_write(sd, 0xc0, G); + cp_write(sd, 0xc2, B); + /* SDP */ + sdp_write(sd, 0xde, Y); + sdp_write(sd, 0xdf, (V & 0xf0) | ((U >> 4) & 0x0f)); + return 0; + } + case V4L2_CID_DV_RX_RGB_RANGE: + state->rgb_quantization_range = ctrl->val; + set_rgb_quantization_range(sd); + return 0; + } + return -EINVAL; +} + +static inline bool no_power(struct v4l2_subdev *sd) +{ + return io_read(sd, 0x0c) & 0x24; +} + +static inline bool no_cp_signal(struct v4l2_subdev *sd) +{ + return ((cp_read(sd, 0xb5) & 0xd0) != 0xd0) || !(cp_read(sd, 0xb1) & 0x80); +} + +static inline bool is_hdmi(struct v4l2_subdev *sd) +{ + return hdmi_read(sd, 0x05) & 0x80; +} + +static int adv7842_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct adv7842_state *state = to_state(sd); + + *status = 0; + + if (io_read(sd, 0x0c) & 0x24) + *status |= V4L2_IN_ST_NO_POWER; + + if (state->mode == ADV7842_MODE_SDP) { + /* status from SDP block */ + if (!(sdp_read(sd, 0x5A) & 0x01)) + *status |= V4L2_IN_ST_NO_SIGNAL; + + v4l2_dbg(1, debug, sd, "%s: SDP status = 0x%x\n", + __func__, *status); + return 0; + } + /* status from CP block */ + if ((cp_read(sd, 0xb5) & 0xd0) != 0xd0 || + !(cp_read(sd, 0xb1) & 0x80)) + /* TODO channel 2 */ + *status |= V4L2_IN_ST_NO_SIGNAL; + + if (is_digital_input(sd) && ((io_read(sd, 0x74) & 0x03) != 0x03)) + *status |= V4L2_IN_ST_NO_SIGNAL; + + v4l2_dbg(1, debug, sd, "%s: CP status = 0x%x\n", + __func__, *status); + + return 0; +} + +struct stdi_readback { + u16 bl, lcf, lcvs; + u8 hs_pol, vs_pol; + bool interlaced; +}; + +static int stdi2dv_timings(struct v4l2_subdev *sd, + struct stdi_readback *stdi, + struct v4l2_dv_timings *timings) +{ + struct adv7842_state *state = to_state(sd); + u32 hfreq = (ADV7842_fsc * 8) / stdi->bl; + u32 pix_clk; + int i; + + for (i = 0; v4l2_dv_timings_presets[i].bt.width; i++) { + const struct v4l2_bt_timings *bt = &v4l2_dv_timings_presets[i].bt; + + if (!v4l2_valid_dv_timings(&v4l2_dv_timings_presets[i], + adv7842_get_dv_timings_cap(sd), + adv7842_check_dv_timings, NULL)) + continue; + if (vtotal(bt) != stdi->lcf + 1) + continue; + if (bt->vsync != stdi->lcvs) + continue; + + pix_clk = hfreq * htotal(bt); + + if ((pix_clk < bt->pixelclock + 1000000) && + (pix_clk > bt->pixelclock - 1000000)) { + *timings = v4l2_dv_timings_presets[i]; + return 0; + } + } + + if (v4l2_detect_cvt(stdi->lcf + 1, hfreq, stdi->lcvs, + (stdi->hs_pol == '+' ? V4L2_DV_HSYNC_POS_POL : 0) | + (stdi->vs_pol == '+' ? V4L2_DV_VSYNC_POS_POL : 0), + timings)) + return 0; + if (v4l2_detect_gtf(stdi->lcf + 1, hfreq, stdi->lcvs, + (stdi->hs_pol == '+' ? V4L2_DV_HSYNC_POS_POL : 0) | + (stdi->vs_pol == '+' ? V4L2_DV_VSYNC_POS_POL : 0), + state->aspect_ratio, timings)) + return 0; + + v4l2_dbg(2, debug, sd, + "%s: No format candidate found for lcvs = %d, lcf=%d, bl = %d, %chsync, %cvsync\n", + __func__, stdi->lcvs, stdi->lcf, stdi->bl, + stdi->hs_pol, stdi->vs_pol); + return -1; +} + +static int read_stdi(struct v4l2_subdev *sd, struct stdi_readback *stdi) +{ + u32 status; + + adv7842_g_input_status(sd, &status); + if (status & V4L2_IN_ST_NO_SIGNAL) { + v4l2_dbg(2, debug, sd, "%s: no signal\n", __func__); + return -ENOLINK; + } + + stdi->bl = ((cp_read(sd, 0xb1) & 0x3f) << 8) | cp_read(sd, 0xb2); + stdi->lcf = ((cp_read(sd, 0xb3) & 0x7) << 8) | cp_read(sd, 0xb4); + stdi->lcvs = cp_read(sd, 0xb3) >> 3; + + if ((cp_read(sd, 0xb5) & 0x80) && ((cp_read(sd, 0xb5) & 0x03) == 0x01)) { + stdi->hs_pol = ((cp_read(sd, 0xb5) & 0x10) ? + ((cp_read(sd, 0xb5) & 0x08) ? '+' : '-') : 'x'); + stdi->vs_pol = ((cp_read(sd, 0xb5) & 0x40) ? + ((cp_read(sd, 0xb5) & 0x20) ? '+' : '-') : 'x'); + } else { + stdi->hs_pol = 'x'; + stdi->vs_pol = 'x'; + } + stdi->interlaced = (cp_read(sd, 0xb1) & 0x40) ? true : false; + + if (stdi->lcf < 239 || stdi->bl < 8 || stdi->bl == 0x3fff) { + v4l2_dbg(2, debug, sd, "%s: invalid signal\n", __func__); + return -ENOLINK; + } + + v4l2_dbg(2, debug, sd, + "%s: lcf (frame height - 1) = %d, bl = %d, lcvs (vsync) = %d, %chsync, %cvsync, %s\n", + __func__, stdi->lcf, stdi->bl, stdi->lcvs, + stdi->hs_pol, stdi->vs_pol, + stdi->interlaced ? "interlaced" : "progressive"); + + return 0; +} + +static int adv7842_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + return v4l2_enum_dv_timings_cap(timings, + adv7842_get_dv_timings_cap(sd), adv7842_check_dv_timings, NULL); +} + +static int adv7842_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + *cap = *adv7842_get_dv_timings_cap(sd); + return 0; +} + +/* Fill the optional fields .standards and .flags in struct v4l2_dv_timings + if the format is listed in adv7604_timings[] */ +static void adv7842_fill_optional_dv_timings_fields(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + v4l2_find_dv_timings_cap(timings, adv7842_get_dv_timings_cap(sd), + is_digital_input(sd) ? 250000 : 1000000, + adv7842_check_dv_timings, NULL); +} + +static int adv7842_query_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv7842_state *state = to_state(sd); + struct v4l2_bt_timings *bt = &timings->bt; + struct stdi_readback stdi = { 0 }; + + /* SDP block */ + if (state->mode == ADV7842_MODE_SDP) + return -ENODATA; + + /* read STDI */ + if (read_stdi(sd, &stdi)) { + v4l2_dbg(1, debug, sd, "%s: no valid signal\n", __func__); + return -ENOLINK; + } + bt->interlaced = stdi.interlaced ? + V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE; + bt->polarities = ((hdmi_read(sd, 0x05) & 0x10) ? V4L2_DV_VSYNC_POS_POL : 0) | + ((hdmi_read(sd, 0x05) & 0x20) ? V4L2_DV_HSYNC_POS_POL : 0); + bt->vsync = stdi.lcvs; + + if (is_digital_input(sd)) { + bool lock = hdmi_read(sd, 0x04) & 0x02; + bool interlaced = hdmi_read(sd, 0x0b) & 0x20; + unsigned w = (hdmi_read(sd, 0x07) & 0x1f) * 256 + hdmi_read(sd, 0x08); + unsigned h = (hdmi_read(sd, 0x09) & 0x1f) * 256 + hdmi_read(sd, 0x0a); + unsigned w_total = (hdmi_read(sd, 0x1e) & 0x3f) * 256 + + hdmi_read(sd, 0x1f); + unsigned h_total = ((hdmi_read(sd, 0x26) & 0x3f) * 256 + + hdmi_read(sd, 0x27)) / 2; + unsigned freq = (((hdmi_read(sd, 0x51) << 1) + + (hdmi_read(sd, 0x52) >> 7)) * 1000000) + + ((hdmi_read(sd, 0x52) & 0x7f) * 1000000) / 128; + int i; + + if (is_hdmi(sd)) { + /* adjust for deep color mode */ + freq = freq * 8 / (((hdmi_read(sd, 0x0b) & 0xc0)>>6) * 2 + 8); + } + + /* No lock? */ + if (!lock) { + v4l2_dbg(1, debug, sd, "%s: no lock on TMDS signal\n", __func__); + return -ENOLCK; + } + /* Interlaced? */ + if (interlaced) { + v4l2_dbg(1, debug, sd, "%s: interlaced video not supported\n", __func__); + return -ERANGE; + } + + for (i = 0; v4l2_dv_timings_presets[i].bt.width; i++) { + const struct v4l2_bt_timings *bt = &v4l2_dv_timings_presets[i].bt; + + if (!v4l2_valid_dv_timings(&v4l2_dv_timings_presets[i], + adv7842_get_dv_timings_cap(sd), + adv7842_check_dv_timings, NULL)) + continue; + if (w_total != htotal(bt) || h_total != vtotal(bt)) + continue; + + if (w != bt->width || h != bt->height) + continue; + + if (abs(freq - bt->pixelclock) > 1000000) + continue; + *timings = v4l2_dv_timings_presets[i]; + return 0; + } + + timings->type = V4L2_DV_BT_656_1120; + + bt->width = w; + bt->height = h; + bt->interlaced = (hdmi_read(sd, 0x0b) & 0x20) ? + V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE; + bt->polarities = ((hdmi_read(sd, 0x05) & 0x10) ? + V4L2_DV_VSYNC_POS_POL : 0) | ((hdmi_read(sd, 0x05) & 0x20) ? + V4L2_DV_HSYNC_POS_POL : 0); + bt->pixelclock = (((hdmi_read(sd, 0x51) << 1) + + (hdmi_read(sd, 0x52) >> 7)) * 1000000) + + ((hdmi_read(sd, 0x52) & 0x7f) * 1000000) / 128; + bt->hfrontporch = (hdmi_read(sd, 0x20) & 0x1f) * 256 + + hdmi_read(sd, 0x21); + bt->hsync = (hdmi_read(sd, 0x22) & 0x1f) * 256 + + hdmi_read(sd, 0x23); + bt->hbackporch = (hdmi_read(sd, 0x24) & 0x1f) * 256 + + hdmi_read(sd, 0x25); + bt->vfrontporch = ((hdmi_read(sd, 0x2a) & 0x3f) * 256 + + hdmi_read(sd, 0x2b)) / 2; + bt->il_vfrontporch = ((hdmi_read(sd, 0x2c) & 0x3f) * 256 + + hdmi_read(sd, 0x2d)) / 2; + bt->vsync = ((hdmi_read(sd, 0x2e) & 0x3f) * 256 + + hdmi_read(sd, 0x2f)) / 2; + bt->il_vsync = ((hdmi_read(sd, 0x30) & 0x3f) * 256 + + hdmi_read(sd, 0x31)) / 2; + bt->vbackporch = ((hdmi_read(sd, 0x32) & 0x3f) * 256 + + hdmi_read(sd, 0x33)) / 2; + bt->il_vbackporch = ((hdmi_read(sd, 0x34) & 0x3f) * 256 + + hdmi_read(sd, 0x35)) / 2; + + bt->standards = 0; + bt->flags = 0; + } else { + /* Interlaced? */ + if (stdi.interlaced) { + v4l2_dbg(1, debug, sd, "%s: interlaced video not supported\n", __func__); + return -ERANGE; + } + + if (stdi2dv_timings(sd, &stdi, timings)) { + v4l2_dbg(1, debug, sd, "%s: format not supported\n", __func__); + return -ERANGE; + } + } + + if (debug > 1) + v4l2_print_dv_timings(sd->name, "adv7842_query_dv_timings: ", + timings, true); + return 0; +} + +static int adv7842_s_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv7842_state *state = to_state(sd); + struct v4l2_bt_timings *bt; + int err; + + if (state->mode == ADV7842_MODE_SDP) + return -ENODATA; + + bt = &timings->bt; + + if (!v4l2_valid_dv_timings(timings, adv7842_get_dv_timings_cap(sd), + adv7842_check_dv_timings, NULL)) + return -ERANGE; + + adv7842_fill_optional_dv_timings_fields(sd, timings); + + state->timings = *timings; + + cp_write(sd, 0x91, bt->interlaced ? 0x50 : 0x10); + + /* Use prim_mode and vid_std when available */ + err = configure_predefined_video_timings(sd, timings); + if (err) { + /* custom settings when the video format + does not have prim_mode/vid_std */ + configure_custom_video_timings(sd, bt); + } + + set_rgb_quantization_range(sd); + + + if (debug > 1) + v4l2_print_dv_timings(sd->name, "adv7842_s_dv_timings: ", + timings, true); + return 0; +} + +static int adv7842_g_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv7842_state *state = to_state(sd); + + if (state->mode == ADV7842_MODE_SDP) + return -ENODATA; + *timings = state->timings; + return 0; +} + +static void enable_input(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + switch (state->mode) { + case ADV7842_MODE_SDP: + case ADV7842_MODE_COMP: + case ADV7842_MODE_RGB: + /* enable */ + io_write(sd, 0x15, 0xb0); /* Disable Tristate of Pins (no audio) */ + break; + case ADV7842_MODE_HDMI: + /* enable */ + hdmi_write(sd, 0x1a, 0x0a); /* Unmute audio */ + hdmi_write(sd, 0x01, 0x00); /* Enable HDMI clock terminators */ + io_write(sd, 0x15, 0xa0); /* Disable Tristate of Pins */ + break; + default: + v4l2_dbg(2, debug, sd, "%s: Unknown mode %d\n", + __func__, state->mode); + break; + } +} + +static void disable_input(struct v4l2_subdev *sd) +{ + /* disable */ + io_write(sd, 0x15, 0xbe); /* Tristate all outputs from video core */ + hdmi_write(sd, 0x1a, 0x1a); /* Mute audio */ + hdmi_write(sd, 0x01, 0x78); /* Disable HDMI clock terminators */ +} + +static void sdp_csc_coeff(struct v4l2_subdev *sd, + const struct adv7842_sdp_csc_coeff *c) +{ + /* csc auto/manual */ + sdp_io_write_and_or(sd, 0xe0, 0xbf, c->manual ? 0x00 : 0x40); + + if (!c->manual) + return; + + /* csc scaling */ + sdp_io_write_and_or(sd, 0xe0, 0x7f, c->scaling == 2 ? 0x80 : 0x00); + + /* A coeff */ + sdp_io_write_and_or(sd, 0xe0, 0xe0, c->A1 >> 8); + sdp_io_write(sd, 0xe1, c->A1); + sdp_io_write_and_or(sd, 0xe2, 0xe0, c->A2 >> 8); + sdp_io_write(sd, 0xe3, c->A2); + sdp_io_write_and_or(sd, 0xe4, 0xe0, c->A3 >> 8); + sdp_io_write(sd, 0xe5, c->A3); + + /* A scale */ + sdp_io_write_and_or(sd, 0xe6, 0x80, c->A4 >> 8); + sdp_io_write(sd, 0xe7, c->A4); + + /* B coeff */ + sdp_io_write_and_or(sd, 0xe8, 0xe0, c->B1 >> 8); + sdp_io_write(sd, 0xe9, c->B1); + sdp_io_write_and_or(sd, 0xea, 0xe0, c->B2 >> 8); + sdp_io_write(sd, 0xeb, c->B2); + sdp_io_write_and_or(sd, 0xec, 0xe0, c->B3 >> 8); + sdp_io_write(sd, 0xed, c->B3); + + /* B scale */ + sdp_io_write_and_or(sd, 0xee, 0x80, c->B4 >> 8); + sdp_io_write(sd, 0xef, c->B4); + + /* C coeff */ + sdp_io_write_and_or(sd, 0xf0, 0xe0, c->C1 >> 8); + sdp_io_write(sd, 0xf1, c->C1); + sdp_io_write_and_or(sd, 0xf2, 0xe0, c->C2 >> 8); + sdp_io_write(sd, 0xf3, c->C2); + sdp_io_write_and_or(sd, 0xf4, 0xe0, c->C3 >> 8); + sdp_io_write(sd, 0xf5, c->C3); + + /* C scale */ + sdp_io_write_and_or(sd, 0xf6, 0x80, c->C4 >> 8); + sdp_io_write(sd, 0xf7, c->C4); +} + +static void select_input(struct v4l2_subdev *sd, + enum adv7842_vid_std_select vid_std_select) +{ + struct adv7842_state *state = to_state(sd); + + switch (state->mode) { + case ADV7842_MODE_SDP: + io_write(sd, 0x00, vid_std_select); /* video std: CVBS or YC mode */ + io_write(sd, 0x01, 0); /* prim mode */ + /* enable embedded syncs for auto graphics mode */ + cp_write_and_or(sd, 0x81, 0xef, 0x10); + + afe_write(sd, 0x00, 0x00); /* power up ADC */ + afe_write(sd, 0xc8, 0x00); /* phase control */ + + io_write(sd, 0x19, 0x83); /* LLC DLL phase */ + io_write(sd, 0x33, 0x40); /* LLC DLL enable */ + + io_write(sd, 0xdd, 0x90); /* Manual 2x output clock */ + /* script says register 0xde, which don't exist in manual */ + + /* Manual analog input muxing mode, CVBS (6.4)*/ + afe_write_and_or(sd, 0x02, 0x7f, 0x80); + if (vid_std_select == ADV7842_SDP_VID_STD_CVBS_SD_4x1) { + afe_write(sd, 0x03, 0xa0); /* ADC0 to AIN10 (CVBS), ADC1 N/C*/ + afe_write(sd, 0x04, 0x00); /* ADC2 N/C,ADC3 N/C*/ + } else { + afe_write(sd, 0x03, 0xa0); /* ADC0 to AIN10 (CVBS), ADC1 N/C*/ + afe_write(sd, 0x04, 0xc0); /* ADC2 to AIN12, ADC3 N/C*/ + } + afe_write(sd, 0x0c, 0x1f); /* ADI recommend write */ + afe_write(sd, 0x12, 0x63); /* ADI recommend write */ + + sdp_io_write(sd, 0xb2, 0x60); /* Disable AV codes */ + sdp_io_write(sd, 0xc8, 0xe3); /* Disable Ancillary data */ + + /* SDP recommended settings */ + sdp_write(sd, 0x00, 0x3F); /* Autodetect PAL NTSC (not SECAM) */ + sdp_write(sd, 0x01, 0x00); /* Pedestal Off */ + + sdp_write(sd, 0x03, 0xE4); /* Manual VCR Gain Luma 0x40B */ + sdp_write(sd, 0x04, 0x0B); /* Manual Luma setting */ + sdp_write(sd, 0x05, 0xC3); /* Manual Chroma setting 0x3FE */ + sdp_write(sd, 0x06, 0xFE); /* Manual Chroma setting */ + sdp_write(sd, 0x12, 0x0D); /* Frame TBC,I_P, 3D comb enabled */ + sdp_write(sd, 0xA7, 0x00); /* ADI Recommended Write */ + sdp_io_write(sd, 0xB0, 0x00); /* Disable H and v blanking */ + + /* deinterlacer enabled and 3D comb */ + sdp_write_and_or(sd, 0x12, 0xf6, 0x09); + + sdp_write(sd, 0xdd, 0x08); /* free run auto */ + + break; + + case ADV7842_MODE_COMP: + case ADV7842_MODE_RGB: + /* Automatic analog input muxing mode */ + afe_write_and_or(sd, 0x02, 0x7f, 0x00); + /* set mode and select free run resolution */ + io_write(sd, 0x00, vid_std_select); /* video std */ + io_write(sd, 0x01, 0x02); /* prim mode */ + cp_write_and_or(sd, 0x81, 0xef, 0x10); /* enable embedded syncs + for auto graphics mode */ + + afe_write(sd, 0x00, 0x00); /* power up ADC */ + afe_write(sd, 0xc8, 0x00); /* phase control */ + + /* set ADI recommended settings for digitizer */ + /* "ADV7842 Register Settings Recommendations + * (rev. 1.8, November 2010)" p. 9. */ + afe_write(sd, 0x0c, 0x1f); /* ADC Range improvement */ + afe_write(sd, 0x12, 0x63); /* ADC Range improvement */ + + /* set to default gain for RGB */ + cp_write(sd, 0x73, 0x10); + cp_write(sd, 0x74, 0x04); + cp_write(sd, 0x75, 0x01); + cp_write(sd, 0x76, 0x00); + + cp_write(sd, 0x3e, 0x04); /* CP core pre-gain control */ + cp_write(sd, 0xc3, 0x39); /* CP coast control. Graphics mode */ + cp_write(sd, 0x40, 0x5c); /* CP core pre-gain control. Graphics mode */ + break; + + case ADV7842_MODE_HDMI: + /* Automatic analog input muxing mode */ + afe_write_and_or(sd, 0x02, 0x7f, 0x00); + /* set mode and select free run resolution */ + if (state->hdmi_port_a) + hdmi_write(sd, 0x00, 0x02); /* select port A */ + else + hdmi_write(sd, 0x00, 0x03); /* select port B */ + io_write(sd, 0x00, vid_std_select); /* video std */ + io_write(sd, 0x01, 5); /* prim mode */ + cp_write_and_or(sd, 0x81, 0xef, 0x00); /* disable embedded syncs + for auto graphics mode */ + + /* set ADI recommended settings for HDMI: */ + /* "ADV7842 Register Settings Recommendations + * (rev. 1.8, November 2010)" p. 3. */ + hdmi_write(sd, 0xc0, 0x00); + hdmi_write(sd, 0x0d, 0x34); /* ADI recommended write */ + hdmi_write(sd, 0x3d, 0x10); /* ADI recommended write */ + hdmi_write(sd, 0x44, 0x85); /* TMDS PLL optimization */ + hdmi_write(sd, 0x46, 0x1f); /* ADI recommended write */ + hdmi_write(sd, 0x57, 0xb6); /* TMDS PLL optimization */ + hdmi_write(sd, 0x58, 0x03); /* TMDS PLL optimization */ + hdmi_write(sd, 0x60, 0x88); /* TMDS PLL optimization */ + hdmi_write(sd, 0x61, 0x88); /* TMDS PLL optimization */ + hdmi_write(sd, 0x6c, 0x18); /* Disable ISRC clearing bit, + Improve robustness */ + hdmi_write(sd, 0x75, 0x10); /* DDC drive strength */ + hdmi_write(sd, 0x85, 0x1f); /* equaliser */ + hdmi_write(sd, 0x87, 0x70); /* ADI recommended write */ + hdmi_write(sd, 0x89, 0x04); /* equaliser */ + hdmi_write(sd, 0x8a, 0x1e); /* equaliser */ + hdmi_write(sd, 0x93, 0x04); /* equaliser */ + hdmi_write(sd, 0x94, 0x1e); /* equaliser */ + hdmi_write(sd, 0x99, 0xa1); /* ADI recommended write */ + hdmi_write(sd, 0x9b, 0x09); /* ADI recommended write */ + hdmi_write(sd, 0x9d, 0x02); /* equaliser */ + + afe_write(sd, 0x00, 0xff); /* power down ADC */ + afe_write(sd, 0xc8, 0x40); /* phase control */ + + /* set to default gain for HDMI */ + cp_write(sd, 0x73, 0x10); + cp_write(sd, 0x74, 0x04); + cp_write(sd, 0x75, 0x01); + cp_write(sd, 0x76, 0x00); + + /* reset ADI recommended settings for digitizer */ + /* "ADV7842 Register Settings Recommendations + * (rev. 2.5, June 2010)" p. 17. */ + afe_write(sd, 0x12, 0xfb); /* ADC noise shaping filter controls */ + afe_write(sd, 0x0c, 0x0d); /* CP core gain controls */ + cp_write(sd, 0x3e, 0x80); /* CP core pre-gain control, + enable color control */ + /* CP coast control */ + cp_write(sd, 0xc3, 0x33); /* Component mode */ + + /* color space conversion, autodetect color space */ + io_write_and_or(sd, 0x02, 0x0f, 0xf0); + break; + + default: + v4l2_dbg(2, debug, sd, "%s: Unknown mode %d\n", + __func__, state->mode); + break; + } +} + +static int adv7842_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct adv7842_state *state = to_state(sd); + + v4l2_dbg(2, debug, sd, "%s: input %d\n", __func__, input); + + switch (input) { + case ADV7842_SELECT_HDMI_PORT_A: + /* TODO select HDMI_COMP or HDMI_GR */ + state->mode = ADV7842_MODE_HDMI; + state->vid_std_select = ADV7842_HDMI_COMP_VID_STD_HD_1250P; + state->hdmi_port_a = true; + break; + case ADV7842_SELECT_HDMI_PORT_B: + /* TODO select HDMI_COMP or HDMI_GR */ + state->mode = ADV7842_MODE_HDMI; + state->vid_std_select = ADV7842_HDMI_COMP_VID_STD_HD_1250P; + state->hdmi_port_a = false; + break; + case ADV7842_SELECT_VGA_COMP: + v4l2_info(sd, "%s: VGA component: todo\n", __func__); + case ADV7842_SELECT_VGA_RGB: + state->mode = ADV7842_MODE_RGB; + state->vid_std_select = ADV7842_RGB_VID_STD_AUTO_GRAPH_MODE; + break; + case ADV7842_SELECT_SDP_CVBS: + state->mode = ADV7842_MODE_SDP; + state->vid_std_select = ADV7842_SDP_VID_STD_CVBS_SD_4x1; + break; + case ADV7842_SELECT_SDP_YC: + state->mode = ADV7842_MODE_SDP; + state->vid_std_select = ADV7842_SDP_VID_STD_YC_SD4_x1; + break; + default: + return -EINVAL; + } + + disable_input(sd); + select_input(sd, state->vid_std_select); + enable_input(sd); + + v4l2_subdev_notify(sd, ADV7842_FMT_CHANGE, NULL); + + return 0; +} + +static int adv7842_enum_mbus_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + if (index) + return -EINVAL; + /* Good enough for now */ + *code = V4L2_MBUS_FMT_FIXED; + return 0; +} + +static int adv7842_g_mbus_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt) +{ + struct adv7842_state *state = to_state(sd); + + fmt->width = state->timings.bt.width; + fmt->height = state->timings.bt.height; + fmt->code = V4L2_MBUS_FMT_FIXED; + fmt->field = V4L2_FIELD_NONE; + + if (state->mode == ADV7842_MODE_SDP) { + /* SPD block */ + if (!(sdp_read(sd, 0x5A) & 0x01)) + return -EINVAL; + fmt->width = 720; + /* valid signal */ + if (state->norm & V4L2_STD_525_60) + fmt->height = 480; + else + fmt->height = 576; + fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + return 0; + } + + if (state->timings.bt.standards & V4L2_DV_BT_STD_CEA861) { + fmt->colorspace = (state->timings.bt.height <= 576) ? + V4L2_COLORSPACE_SMPTE170M : V4L2_COLORSPACE_REC709; + } + return 0; +} + +static void adv7842_irq_enable(struct v4l2_subdev *sd, bool enable) +{ + if (enable) { + /* Enable SSPD, STDI and CP locked/unlocked interrupts */ + io_write(sd, 0x46, 0x9c); + /* ESDP_50HZ_DET interrupt */ + io_write(sd, 0x5a, 0x10); + /* Enable CABLE_DET_A/B_ST (+5v) interrupt */ + io_write(sd, 0x73, 0x03); + /* Enable V_LOCKED and DE_REGEN_LCK interrupts */ + io_write(sd, 0x78, 0x03); + /* Enable SDP Standard Detection Change and SDP Video Detected */ + io_write(sd, 0xa0, 0x09); + } else { + io_write(sd, 0x46, 0x0); + io_write(sd, 0x5a, 0x0); + io_write(sd, 0x73, 0x0); + io_write(sd, 0x78, 0x0); + io_write(sd, 0xa0, 0x0); + } +} + +static int adv7842_isr(struct v4l2_subdev *sd, u32 status, bool *handled) +{ + struct adv7842_state *state = to_state(sd); + u8 fmt_change_cp, fmt_change_digital, fmt_change_sdp; + u8 irq_status[5]; + u8 irq_cfg = io_read(sd, 0x40); + + /* disable irq-pin output */ + io_write(sd, 0x40, irq_cfg | 0x3); + + /* read status */ + irq_status[0] = io_read(sd, 0x43); + irq_status[1] = io_read(sd, 0x57); + irq_status[2] = io_read(sd, 0x70); + irq_status[3] = io_read(sd, 0x75); + irq_status[4] = io_read(sd, 0x9d); + + /* and clear */ + if (irq_status[0]) + io_write(sd, 0x44, irq_status[0]); + if (irq_status[1]) + io_write(sd, 0x58, irq_status[1]); + if (irq_status[2]) + io_write(sd, 0x71, irq_status[2]); + if (irq_status[3]) + io_write(sd, 0x76, irq_status[3]); + if (irq_status[4]) + io_write(sd, 0x9e, irq_status[4]); + + v4l2_dbg(1, debug, sd, "%s: irq %x, %x, %x, %x, %x\n", __func__, + irq_status[0], irq_status[1], irq_status[2], + irq_status[3], irq_status[4]); + + /* format change CP */ + fmt_change_cp = irq_status[0] & 0x9c; + + /* format change SDP */ + if (state->mode == ADV7842_MODE_SDP) + fmt_change_sdp = (irq_status[1] & 0x30) | (irq_status[4] & 0x09); + else + fmt_change_sdp = 0; + + /* digital format CP */ + if (is_digital_input(sd)) + fmt_change_digital = irq_status[3] & 0x03; + else + fmt_change_digital = 0; + + /* notify */ + if (fmt_change_cp || fmt_change_digital || fmt_change_sdp) { + v4l2_dbg(1, debug, sd, + "%s: fmt_change_cp = 0x%x, fmt_change_digital = 0x%x, fmt_change_sdp = 0x%x\n", + __func__, fmt_change_cp, fmt_change_digital, + fmt_change_sdp); + v4l2_subdev_notify(sd, ADV7842_FMT_CHANGE, NULL); + } + + /* 5v cable detect */ + if (irq_status[2]) + adv7842_s_detect_tx_5v_ctrl(sd); + + if (handled) + *handled = true; + + /* re-enable irq-pin output */ + io_write(sd, 0x40, irq_cfg); + + return 0; +} + +static int adv7842_set_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *e) +{ + struct adv7842_state *state = to_state(sd); + int err = 0; + + if (e->pad > 2) + return -EINVAL; + if (e->start_block != 0) + return -EINVAL; + if (e->blocks > 2) + return -E2BIG; + if (!e->edid) + return -EINVAL; + + /* todo, per edid */ + state->aspect_ratio = v4l2_calc_aspect_ratio(e->edid[0x15], + e->edid[0x16]); + + if (e->pad == 2) { + memset(&state->vga_edid.edid, 0, 256); + state->vga_edid.present = e->blocks ? 0x1 : 0x0; + memcpy(&state->vga_edid.edid, e->edid, 128 * e->blocks); + err = edid_write_vga_segment(sd); + } else { + u32 mask = 0x1<<e->pad; + memset(&state->hdmi_edid.edid, 0, 256); + if (e->blocks) + state->hdmi_edid.present |= mask; + else + state->hdmi_edid.present &= ~mask; + memcpy(&state->hdmi_edid.edid, e->edid, 128*e->blocks); + err = edid_write_hdmi_segment(sd, e->pad); + } + if (err < 0) + v4l2_err(sd, "error %d writing edid on port %d\n", err, e->pad); + return err; +} + +/*********** avi info frame CEA-861-E **************/ +/* TODO move to common library */ + +struct avi_info_frame { + uint8_t f17; + uint8_t y10; + uint8_t a0; + uint8_t b10; + uint8_t s10; + uint8_t c10; + uint8_t m10; + uint8_t r3210; + uint8_t itc; + uint8_t ec210; + uint8_t q10; + uint8_t sc10; + uint8_t f47; + uint8_t vic; + uint8_t yq10; + uint8_t cn10; + uint8_t pr3210; + uint16_t etb; + uint16_t sbb; + uint16_t elb; + uint16_t srb; +}; + +static const char *y10_txt[4] = { + "RGB", + "YCbCr 4:2:2", + "YCbCr 4:4:4", + "Future", +}; + +static const char *c10_txt[4] = { + "No Data", + "SMPTE 170M", + "ITU-R 709", + "Extended Colorimetry information valied", +}; + +static const char *itc_txt[2] = { + "No Data", + "IT content", +}; + +static const char *ec210_txt[8] = { + "xvYCC601", + "xvYCC709", + "sYCC601", + "AdobeYCC601", + "AdobeRGB", + "5 reserved", + "6 reserved", + "7 reserved", +}; + +static const char *q10_txt[4] = { + "Default", + "Limited Range", + "Full Range", + "Reserved", +}; + +static void parse_avi_infoframe(struct v4l2_subdev *sd, uint8_t *buf, + struct avi_info_frame *avi) +{ + avi->f17 = (buf[1] >> 7) & 0x1; + avi->y10 = (buf[1] >> 5) & 0x3; + avi->a0 = (buf[1] >> 4) & 0x1; + avi->b10 = (buf[1] >> 2) & 0x3; + avi->s10 = buf[1] & 0x3; + avi->c10 = (buf[2] >> 6) & 0x3; + avi->m10 = (buf[2] >> 4) & 0x3; + avi->r3210 = buf[2] & 0xf; + avi->itc = (buf[3] >> 7) & 0x1; + avi->ec210 = (buf[3] >> 4) & 0x7; + avi->q10 = (buf[3] >> 2) & 0x3; + avi->sc10 = buf[3] & 0x3; + avi->f47 = (buf[4] >> 7) & 0x1; + avi->vic = buf[4] & 0x7f; + avi->yq10 = (buf[5] >> 6) & 0x3; + avi->cn10 = (buf[5] >> 4) & 0x3; + avi->pr3210 = buf[5] & 0xf; + avi->etb = buf[6] + 256*buf[7]; + avi->sbb = buf[8] + 256*buf[9]; + avi->elb = buf[10] + 256*buf[11]; + avi->srb = buf[12] + 256*buf[13]; +} + +static void print_avi_infoframe(struct v4l2_subdev *sd) +{ + int i; + uint8_t buf[14]; + uint8_t avi_inf_len; + struct avi_info_frame avi; + + if (!(hdmi_read(sd, 0x05) & 0x80)) { + v4l2_info(sd, "receive DVI-D signal (AVI infoframe not supported)\n"); + return; + } + if (!(io_read(sd, 0x60) & 0x01)) { + v4l2_info(sd, "AVI infoframe not received\n"); + return; + } + + if (io_read(sd, 0x88) & 0x10) { + /* Note: the ADV7842 calculated incorrect checksums for InfoFrames + with a length of 14 or 15. See the ADV7842 Register Settings + Recommendations document for more details. */ + v4l2_info(sd, "AVI infoframe checksum error\n"); + return; + } + + avi_inf_len = infoframe_read(sd, 0xe2); + v4l2_info(sd, "AVI infoframe version %d (%d byte)\n", + infoframe_read(sd, 0xe1), avi_inf_len); + + if (infoframe_read(sd, 0xe1) != 0x02) + return; + + for (i = 0; i < 14; i++) + buf[i] = infoframe_read(sd, i); + + v4l2_info(sd, "\t%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], + buf[8], buf[9], buf[10], buf[11], buf[12], buf[13]); + + parse_avi_infoframe(sd, buf, &avi); + + if (avi.vic) + v4l2_info(sd, "\tVIC: %d\n", avi.vic); + if (avi.itc) + v4l2_info(sd, "\t%s\n", itc_txt[avi.itc]); + + if (avi.y10) + v4l2_info(sd, "\t%s %s\n", y10_txt[avi.y10], !avi.c10 ? "" : + (avi.c10 == 0x3 ? ec210_txt[avi.ec210] : c10_txt[avi.c10])); + else + v4l2_info(sd, "\t%s %s\n", y10_txt[avi.y10], q10_txt[avi.q10]); +} + +static const char * const prim_mode_txt[] = { + "SDP", + "Component", + "Graphics", + "Reserved", + "CVBS & HDMI AUDIO", + "HDMI-Comp", + "HDMI-GR", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", +}; + +static int adv7842_sdp_log_status(struct v4l2_subdev *sd) +{ + /* SDP (Standard definition processor) block */ + uint8_t sdp_signal_detected = sdp_read(sd, 0x5A) & 0x01; + + v4l2_info(sd, "Chip powered %s\n", no_power(sd) ? "off" : "on"); + v4l2_info(sd, "Prim-mode = 0x%x, video std = 0x%x\n", + io_read(sd, 0x01) & 0x0f, io_read(sd, 0x00) & 0x3f); + + v4l2_info(sd, "SDP: free run: %s\n", + (sdp_read(sd, 0x56) & 0x01) ? "on" : "off"); + v4l2_info(sd, "SDP: %s\n", sdp_signal_detected ? + "valid SD/PR signal detected" : "invalid/no signal"); + if (sdp_signal_detected) { + static const char * const sdp_std_txt[] = { + "NTSC-M/J", + "1?", + "NTSC-443", + "60HzSECAM", + "PAL-M", + "5?", + "PAL-60", + "7?", "8?", "9?", "a?", "b?", + "PAL-CombN", + "d?", + "PAL-BGHID", + "SECAM" + }; + v4l2_info(sd, "SDP: standard %s\n", + sdp_std_txt[sdp_read(sd, 0x52) & 0x0f]); + v4l2_info(sd, "SDP: %s\n", + (sdp_read(sd, 0x59) & 0x08) ? "50Hz" : "60Hz"); + v4l2_info(sd, "SDP: %s\n", + (sdp_read(sd, 0x57) & 0x08) ? "Interlaced" : "Progressive"); + v4l2_info(sd, "SDP: deinterlacer %s\n", + (sdp_read(sd, 0x12) & 0x08) ? "enabled" : "disabled"); + v4l2_info(sd, "SDP: csc %s mode\n", + (sdp_io_read(sd, 0xe0) & 0x40) ? "auto" : "manual"); + } + return 0; +} + +static int adv7842_cp_log_status(struct v4l2_subdev *sd) +{ + /* CP block */ + struct adv7842_state *state = to_state(sd); + struct v4l2_dv_timings timings; + uint8_t reg_io_0x02 = io_read(sd, 0x02); + uint8_t reg_io_0x21 = io_read(sd, 0x21); + uint8_t reg_rep_0x77 = rep_read(sd, 0x77); + uint8_t reg_rep_0x7d = rep_read(sd, 0x7d); + bool audio_pll_locked = hdmi_read(sd, 0x04) & 0x01; + bool audio_sample_packet_detect = hdmi_read(sd, 0x18) & 0x01; + bool audio_mute = io_read(sd, 0x65) & 0x40; + + static const char * const csc_coeff_sel_rb[16] = { + "bypassed", "YPbPr601 -> RGB", "reserved", "YPbPr709 -> RGB", + "reserved", "RGB -> YPbPr601", "reserved", "RGB -> YPbPr709", + "reserved", "YPbPr709 -> YPbPr601", "YPbPr601 -> YPbPr709", + "reserved", "reserved", "reserved", "reserved", "manual" + }; + static const char * const input_color_space_txt[16] = { + "RGB limited range (16-235)", "RGB full range (0-255)", + "YCbCr Bt.601 (16-235)", "YCbCr Bt.709 (16-235)", + "XvYCC Bt.601", "XvYCC Bt.709", + "YCbCr Bt.601 (0-255)", "YCbCr Bt.709 (0-255)", + "invalid", "invalid", "invalid", "invalid", "invalid", + "invalid", "invalid", "automatic" + }; + static const char * const rgb_quantization_range_txt[] = { + "Automatic", + "RGB limited range (16-235)", + "RGB full range (0-255)", + }; + static const char * const deep_color_mode_txt[4] = { + "8-bits per channel", + "10-bits per channel", + "12-bits per channel", + "16-bits per channel (not supported)" + }; + + v4l2_info(sd, "-----Chip status-----\n"); + v4l2_info(sd, "Chip power: %s\n", no_power(sd) ? "off" : "on"); + v4l2_info(sd, "Connector type: %s\n", state->connector_hdmi ? + "HDMI" : (is_digital_input(sd) ? "DVI-D" : "DVI-A")); + v4l2_info(sd, "HDMI/DVI-D port selected: %s\n", + state->hdmi_port_a ? "A" : "B"); + v4l2_info(sd, "EDID A %s, B %s\n", + ((reg_rep_0x7d & 0x04) && (reg_rep_0x77 & 0x04)) ? + "enabled" : "disabled", + ((reg_rep_0x7d & 0x08) && (reg_rep_0x77 & 0x08)) ? + "enabled" : "disabled"); + v4l2_info(sd, "HPD A %s, B %s\n", + reg_io_0x21 & 0x02 ? "enabled" : "disabled", + reg_io_0x21 & 0x01 ? "enabled" : "disabled"); + v4l2_info(sd, "CEC %s\n", !!(cec_read(sd, 0x2a) & 0x01) ? + "enabled" : "disabled"); + + v4l2_info(sd, "-----Signal status-----\n"); + if (state->hdmi_port_a) { + v4l2_info(sd, "Cable detected (+5V power): %s\n", + io_read(sd, 0x6f) & 0x02 ? "true" : "false"); + v4l2_info(sd, "TMDS signal detected: %s\n", + (io_read(sd, 0x6a) & 0x02) ? "true" : "false"); + v4l2_info(sd, "TMDS signal locked: %s\n", + (io_read(sd, 0x6a) & 0x20) ? "true" : "false"); + } else { + v4l2_info(sd, "Cable detected (+5V power):%s\n", + io_read(sd, 0x6f) & 0x01 ? "true" : "false"); + v4l2_info(sd, "TMDS signal detected: %s\n", + (io_read(sd, 0x6a) & 0x01) ? "true" : "false"); + v4l2_info(sd, "TMDS signal locked: %s\n", + (io_read(sd, 0x6a) & 0x10) ? "true" : "false"); + } + v4l2_info(sd, "CP free run: %s\n", + (!!(cp_read(sd, 0xff) & 0x10) ? "on" : "off")); + v4l2_info(sd, "Prim-mode = 0x%x, video std = 0x%x, v_freq = 0x%x\n", + io_read(sd, 0x01) & 0x0f, io_read(sd, 0x00) & 0x3f, + (io_read(sd, 0x01) & 0x70) >> 4); + + v4l2_info(sd, "-----Video Timings-----\n"); + if (no_cp_signal(sd)) { + v4l2_info(sd, "STDI: not locked\n"); + } else { + uint32_t bl = ((cp_read(sd, 0xb1) & 0x3f) << 8) | cp_read(sd, 0xb2); + uint32_t lcf = ((cp_read(sd, 0xb3) & 0x7) << 8) | cp_read(sd, 0xb4); + uint32_t lcvs = cp_read(sd, 0xb3) >> 3; + uint32_t fcl = ((cp_read(sd, 0xb8) & 0x1f) << 8) | cp_read(sd, 0xb9); + char hs_pol = ((cp_read(sd, 0xb5) & 0x10) ? + ((cp_read(sd, 0xb5) & 0x08) ? '+' : '-') : 'x'); + char vs_pol = ((cp_read(sd, 0xb5) & 0x40) ? + ((cp_read(sd, 0xb5) & 0x20) ? '+' : '-') : 'x'); + v4l2_info(sd, + "STDI: lcf (frame height - 1) = %d, bl = %d, lcvs (vsync) = %d, fcl = %d, %s, %chsync, %cvsync\n", + lcf, bl, lcvs, fcl, + (cp_read(sd, 0xb1) & 0x40) ? + "interlaced" : "progressive", + hs_pol, vs_pol); + } + if (adv7842_query_dv_timings(sd, &timings)) + v4l2_info(sd, "No video detected\n"); + else + v4l2_print_dv_timings(sd->name, "Detected format: ", + &timings, true); + v4l2_print_dv_timings(sd->name, "Configured format: ", + &state->timings, true); + + if (no_cp_signal(sd)) + return 0; + + v4l2_info(sd, "-----Color space-----\n"); + v4l2_info(sd, "RGB quantization range ctrl: %s\n", + rgb_quantization_range_txt[state->rgb_quantization_range]); + v4l2_info(sd, "Input color space: %s\n", + input_color_space_txt[reg_io_0x02 >> 4]); + v4l2_info(sd, "Output color space: %s %s, saturator %s\n", + (reg_io_0x02 & 0x02) ? "RGB" : "YCbCr", + (reg_io_0x02 & 0x04) ? "(16-235)" : "(0-255)", + ((reg_io_0x02 & 0x04) ^ (reg_io_0x02 & 0x01)) ? + "enabled" : "disabled"); + v4l2_info(sd, "Color space conversion: %s\n", + csc_coeff_sel_rb[cp_read(sd, 0xf4) >> 4]); + + if (!is_digital_input(sd)) + return 0; + + v4l2_info(sd, "-----%s status-----\n", is_hdmi(sd) ? "HDMI" : "DVI-D"); + v4l2_info(sd, "HDCP encrypted content: %s\n", + (hdmi_read(sd, 0x05) & 0x40) ? "true" : "false"); + v4l2_info(sd, "HDCP keys read: %s%s\n", + (hdmi_read(sd, 0x04) & 0x20) ? "yes" : "no", + (hdmi_read(sd, 0x04) & 0x10) ? "ERROR" : ""); + if (!is_hdmi(sd)) + return 0; + + v4l2_info(sd, "Audio: pll %s, samples %s, %s\n", + audio_pll_locked ? "locked" : "not locked", + audio_sample_packet_detect ? "detected" : "not detected", + audio_mute ? "muted" : "enabled"); + if (audio_pll_locked && audio_sample_packet_detect) { + v4l2_info(sd, "Audio format: %s\n", + (hdmi_read(sd, 0x07) & 0x40) ? "multi-channel" : "stereo"); + } + v4l2_info(sd, "Audio CTS: %u\n", (hdmi_read(sd, 0x5b) << 12) + + (hdmi_read(sd, 0x5c) << 8) + + (hdmi_read(sd, 0x5d) & 0xf0)); + v4l2_info(sd, "Audio N: %u\n", ((hdmi_read(sd, 0x5d) & 0x0f) << 16) + + (hdmi_read(sd, 0x5e) << 8) + + hdmi_read(sd, 0x5f)); + v4l2_info(sd, "AV Mute: %s\n", + (hdmi_read(sd, 0x04) & 0x40) ? "on" : "off"); + v4l2_info(sd, "Deep color mode: %s\n", + deep_color_mode_txt[hdmi_read(sd, 0x0b) >> 6]); + + print_avi_infoframe(sd); + return 0; +} + +static int adv7842_log_status(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + + if (state->mode == ADV7842_MODE_SDP) + return adv7842_sdp_log_status(sd); + return adv7842_cp_log_status(sd); +} + +static int adv7842_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct adv7842_state *state = to_state(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (state->mode != ADV7842_MODE_SDP) + return -ENODATA; + + if (!(sdp_read(sd, 0x5A) & 0x01)) { + *std = 0; + v4l2_dbg(1, debug, sd, "%s: no valid signal\n", __func__); + return 0; + } + + switch (sdp_read(sd, 0x52) & 0x0f) { + case 0: + /* NTSC-M/J */ + *std &= V4L2_STD_NTSC; + break; + case 2: + /* NTSC-443 */ + *std &= V4L2_STD_NTSC_443; + break; + case 3: + /* 60HzSECAM */ + *std &= V4L2_STD_SECAM; + break; + case 4: + /* PAL-M */ + *std &= V4L2_STD_PAL_M; + break; + case 6: + /* PAL-60 */ + *std &= V4L2_STD_PAL_60; + break; + case 0xc: + /* PAL-CombN */ + *std &= V4L2_STD_PAL_Nc; + break; + case 0xe: + /* PAL-BGHID */ + *std &= V4L2_STD_PAL; + break; + case 0xf: + /* SECAM */ + *std &= V4L2_STD_SECAM; + break; + default: + *std &= V4L2_STD_ALL; + break; + } + return 0; +} + +static int adv7842_s_std(struct v4l2_subdev *sd, v4l2_std_id norm) +{ + struct adv7842_state *state = to_state(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (state->mode != ADV7842_MODE_SDP) + return -ENODATA; + + if (norm & V4L2_STD_ALL) { + state->norm = norm; + return 0; + } + return -EINVAL; +} + +static int adv7842_g_std(struct v4l2_subdev *sd, v4l2_std_id *norm) +{ + struct adv7842_state *state = to_state(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (state->mode != ADV7842_MODE_SDP) + return -ENODATA; + + *norm = state->norm; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int adv7842_core_init(struct v4l2_subdev *sd, + const struct adv7842_platform_data *pdata) +{ + hdmi_write(sd, 0x48, + (pdata->disable_pwrdnb ? 0x80 : 0) | + (pdata->disable_cable_det_rst ? 0x40 : 0)); + + disable_input(sd); + + /* power */ + io_write(sd, 0x0c, 0x42); /* Power up part and power down VDP */ + io_write(sd, 0x15, 0x80); /* Power up pads */ + + /* video format */ + io_write(sd, 0x02, + pdata->inp_color_space << 4 | + pdata->alt_gamma << 3 | + pdata->op_656_range << 2 | + pdata->rgb_out << 1 | + pdata->alt_data_sat << 0); + io_write(sd, 0x03, pdata->op_format_sel); + io_write_and_or(sd, 0x04, 0x1f, pdata->op_ch_sel << 5); + io_write_and_or(sd, 0x05, 0xf0, pdata->blank_data << 3 | + pdata->insert_av_codes << 2 | + pdata->replicate_av_codes << 1 | + pdata->invert_cbcr << 0); + + /* Drive strength */ + io_write_and_or(sd, 0x14, 0xc0, pdata->drive_strength.data<<4 | + pdata->drive_strength.clock<<2 | + pdata->drive_strength.sync); + + /* HDMI free run */ + cp_write(sd, 0xba, (pdata->hdmi_free_run_mode << 1) | 0x01); + + /* TODO from platform data */ + cp_write(sd, 0x69, 0x14); /* Enable CP CSC */ + io_write(sd, 0x06, 0xa6); /* positive VS and HS and DE */ + cp_write(sd, 0xf3, 0xdc); /* Low threshold to enter/exit free run mode */ + afe_write(sd, 0xb5, 0x01); /* Setting MCLK to 256Fs */ + + afe_write(sd, 0x02, pdata->ain_sel); /* Select analog input muxing mode */ + io_write_and_or(sd, 0x30, ~(1 << 4), pdata->output_bus_lsb_to_msb << 4); + + sdp_csc_coeff(sd, &pdata->sdp_csc_coeff); + + if (pdata->sdp_io_sync.adjust) { + const struct adv7842_sdp_io_sync_adjustment *s = &pdata->sdp_io_sync; + sdp_io_write(sd, 0x94, (s->hs_beg>>8) & 0xf); + sdp_io_write(sd, 0x95, s->hs_beg & 0xff); + sdp_io_write(sd, 0x96, (s->hs_width>>8) & 0xf); + sdp_io_write(sd, 0x97, s->hs_width & 0xff); + sdp_io_write(sd, 0x98, (s->de_beg>>8) & 0xf); + sdp_io_write(sd, 0x99, s->de_beg & 0xff); + sdp_io_write(sd, 0x9a, (s->de_end>>8) & 0xf); + sdp_io_write(sd, 0x9b, s->de_end & 0xff); + } + + /* todo, improve settings for sdram */ + if (pdata->sd_ram_size >= 128) { + sdp_write(sd, 0x12, 0x0d); /* Frame TBC,3D comb enabled */ + if (pdata->sd_ram_ddr) { + /* SDP setup for the AD eval board */ + sdp_io_write(sd, 0x6f, 0x00); /* DDR mode */ + sdp_io_write(sd, 0x75, 0x0a); /* 128 MB memory size */ + sdp_io_write(sd, 0x7a, 0xa5); /* Timing Adjustment */ + sdp_io_write(sd, 0x7b, 0x8f); /* Timing Adjustment */ + sdp_io_write(sd, 0x60, 0x01); /* SDRAM reset */ + } else { + sdp_io_write(sd, 0x75, 0x0a); /* 64 MB memory size ?*/ + sdp_io_write(sd, 0x74, 0x00); /* must be zero for sdr sdram */ + sdp_io_write(sd, 0x79, 0x33); /* CAS latency to 3, + depends on memory */ + sdp_io_write(sd, 0x6f, 0x01); /* SDR mode */ + sdp_io_write(sd, 0x7a, 0xa5); /* Timing Adjustment */ + sdp_io_write(sd, 0x7b, 0x8f); /* Timing Adjustment */ + sdp_io_write(sd, 0x60, 0x01); /* SDRAM reset */ + } + } else { + /* + * Manual UG-214, rev 0 is bit confusing on this bit + * but a '1' disables any signal if the Ram is active. + */ + sdp_io_write(sd, 0x29, 0x10); /* Tristate memory interface */ + } + + select_input(sd, pdata->vid_std_select); + + enable_input(sd); + + /* disable I2C access to internal EDID ram from HDMI DDC ports */ + rep_write_and_or(sd, 0x77, 0xf3, 0x00); + + hdmi_write(sd, 0x69, 0xa3); /* HPA manual */ + /* HPA disable on port A and B */ + io_write_and_or(sd, 0x20, 0xcf, 0x00); + + /* LLC */ + /* Set phase to 16. TODO: get this from platform_data */ + io_write(sd, 0x19, 0x90); + io_write(sd, 0x33, 0x40); + + /* interrupts */ + io_write(sd, 0x40, 0xe2); /* Configure INT1 */ + + adv7842_irq_enable(sd, true); + + return v4l2_ctrl_handler_setup(sd->ctrl_handler); +} + +/* ----------------------------------------------------------------------- */ + +static int adv7842_ddr_ram_test(struct v4l2_subdev *sd) +{ + /* + * From ADV784x external Memory test.pdf + * + * Reset must just been performed before running test. + * Recommended to reset after test. + */ + int i; + int pass = 0; + int fail = 0; + int complete = 0; + + io_write(sd, 0x00, 0x01); /* Program SDP 4x1 */ + io_write(sd, 0x01, 0x00); /* Program SDP mode */ + afe_write(sd, 0x80, 0x92); /* SDP Recommeneded Write */ + afe_write(sd, 0x9B, 0x01); /* SDP Recommeneded Write ADV7844ES1 */ + afe_write(sd, 0x9C, 0x60); /* SDP Recommeneded Write ADV7844ES1 */ + afe_write(sd, 0x9E, 0x02); /* SDP Recommeneded Write ADV7844ES1 */ + afe_write(sd, 0xA0, 0x0B); /* SDP Recommeneded Write ADV7844ES1 */ + afe_write(sd, 0xC3, 0x02); /* Memory BIST Initialisation */ + io_write(sd, 0x0C, 0x40); /* Power up ADV7844 */ + io_write(sd, 0x15, 0xBA); /* Enable outputs */ + sdp_write(sd, 0x12, 0x00); /* Disable 3D comb, Frame TBC & 3DNR */ + io_write(sd, 0xFF, 0x04); /* Reset memory controller */ + + mdelay(5); + + sdp_write(sd, 0x12, 0x00); /* Disable 3D Comb, Frame TBC & 3DNR */ + sdp_io_write(sd, 0x2A, 0x01); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x7c, 0x19); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x80, 0x87); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x81, 0x4a); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x82, 0x2c); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x83, 0x0e); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x84, 0x94); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x85, 0x62); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x7d, 0x00); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x7e, 0x1a); /* Memory BIST Initialisation */ + + mdelay(5); + + sdp_io_write(sd, 0xd9, 0xd5); /* Enable BIST Test */ + sdp_write(sd, 0x12, 0x05); /* Enable FRAME TBC & 3D COMB */ + + mdelay(20); + + for (i = 0; i < 10; i++) { + u8 result = sdp_io_read(sd, 0xdb); + if (result & 0x10) { + complete++; + if (result & 0x20) + fail++; + else + pass++; + } + mdelay(20); + } + + v4l2_dbg(1, debug, sd, + "Ram Test: completed %d of %d: pass %d, fail %d\n", + complete, i, pass, fail); + + if (!complete || fail) + return -EIO; + return 0; +} + +static void adv7842_rewrite_i2c_addresses(struct v4l2_subdev *sd, + struct adv7842_platform_data *pdata) +{ + io_write(sd, 0xf1, pdata->i2c_sdp << 1); + io_write(sd, 0xf2, pdata->i2c_sdp_io << 1); + io_write(sd, 0xf3, pdata->i2c_avlink << 1); + io_write(sd, 0xf4, pdata->i2c_cec << 1); + io_write(sd, 0xf5, pdata->i2c_infoframe << 1); + + io_write(sd, 0xf8, pdata->i2c_afe << 1); + io_write(sd, 0xf9, pdata->i2c_repeater << 1); + io_write(sd, 0xfa, pdata->i2c_edid << 1); + io_write(sd, 0xfb, pdata->i2c_hdmi << 1); + + io_write(sd, 0xfd, pdata->i2c_cp << 1); + io_write(sd, 0xfe, pdata->i2c_vdp << 1); +} + +static int adv7842_command_ram_test(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct adv7842_state *state = to_state(sd); + struct adv7842_platform_data *pdata = client->dev.platform_data; + int ret = 0; + + if (!pdata) + return -ENODEV; + + if (!pdata->sd_ram_size || !pdata->sd_ram_ddr) { + v4l2_info(sd, "no sdram or no ddr sdram\n"); + return -EINVAL; + } + + main_reset(sd); + + adv7842_rewrite_i2c_addresses(sd, pdata); + + /* run ram test */ + ret = adv7842_ddr_ram_test(sd); + + main_reset(sd); + + adv7842_rewrite_i2c_addresses(sd, pdata); + + /* and re-init chip and state */ + adv7842_core_init(sd, pdata); + + disable_input(sd); + + select_input(sd, state->vid_std_select); + + enable_input(sd); + + adv7842_s_dv_timings(sd, &state->timings); + + edid_write_vga_segment(sd); + edid_write_hdmi_segment(sd, 0); + edid_write_hdmi_segment(sd, 1); + + return ret; +} + +static long adv7842_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ + switch (cmd) { + case ADV7842_CMD_RAM_TEST: + return adv7842_command_ram_test(sd); + } + return -ENOTTY; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops adv7842_ctrl_ops = { + .s_ctrl = adv7842_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops adv7842_core_ops = { + .log_status = adv7842_log_status, + .g_std = adv7842_g_std, + .s_std = adv7842_s_std, + .ioctl = adv7842_ioctl, + .interrupt_service_routine = adv7842_isr, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = adv7842_g_register, + .s_register = adv7842_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops adv7842_video_ops = { + .s_routing = adv7842_s_routing, + .querystd = adv7842_querystd, + .g_input_status = adv7842_g_input_status, + .s_dv_timings = adv7842_s_dv_timings, + .g_dv_timings = adv7842_g_dv_timings, + .query_dv_timings = adv7842_query_dv_timings, + .enum_dv_timings = adv7842_enum_dv_timings, + .dv_timings_cap = adv7842_dv_timings_cap, + .enum_mbus_fmt = adv7842_enum_mbus_fmt, + .g_mbus_fmt = adv7842_g_mbus_fmt, + .try_mbus_fmt = adv7842_g_mbus_fmt, + .s_mbus_fmt = adv7842_g_mbus_fmt, +}; + +static const struct v4l2_subdev_pad_ops adv7842_pad_ops = { + .set_edid = adv7842_set_edid, +}; + +static const struct v4l2_subdev_ops adv7842_ops = { + .core = &adv7842_core_ops, + .video = &adv7842_video_ops, + .pad = &adv7842_pad_ops, +}; + +/* -------------------------- custom ctrls ---------------------------------- */ + +static const struct v4l2_ctrl_config adv7842_ctrl_analog_sampling_phase = { + .ops = &adv7842_ctrl_ops, + .id = V4L2_CID_ADV_RX_ANALOG_SAMPLING_PHASE, + .name = "Analog Sampling Phase", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 0x1f, + .step = 1, + .def = 0, +}; + +static const struct v4l2_ctrl_config adv7842_ctrl_free_run_color_manual = { + .ops = &adv7842_ctrl_ops, + .id = V4L2_CID_ADV_RX_FREE_RUN_COLOR_MANUAL, + .name = "Free Running Color, Manual", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .max = 1, + .step = 1, + .def = 1, +}; + +static const struct v4l2_ctrl_config adv7842_ctrl_free_run_color = { + .ops = &adv7842_ctrl_ops, + .id = V4L2_CID_ADV_RX_FREE_RUN_COLOR, + .name = "Free Running Color", + .type = V4L2_CTRL_TYPE_INTEGER, + .max = 0xffffff, + .step = 0x1, +}; + + +static void adv7842_unregister_clients(struct adv7842_state *state) +{ + if (state->i2c_avlink) + i2c_unregister_device(state->i2c_avlink); + if (state->i2c_cec) + i2c_unregister_device(state->i2c_cec); + if (state->i2c_infoframe) + i2c_unregister_device(state->i2c_infoframe); + if (state->i2c_sdp_io) + i2c_unregister_device(state->i2c_sdp_io); + if (state->i2c_sdp) + i2c_unregister_device(state->i2c_sdp); + if (state->i2c_afe) + i2c_unregister_device(state->i2c_afe); + if (state->i2c_repeater) + i2c_unregister_device(state->i2c_repeater); + if (state->i2c_edid) + i2c_unregister_device(state->i2c_edid); + if (state->i2c_hdmi) + i2c_unregister_device(state->i2c_hdmi); + if (state->i2c_cp) + i2c_unregister_device(state->i2c_cp); + if (state->i2c_vdp) + i2c_unregister_device(state->i2c_vdp); +} + +static struct i2c_client *adv7842_dummy_client(struct v4l2_subdev *sd, + u8 addr, u8 io_reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + io_write(sd, io_reg, addr << 1); + return i2c_new_dummy(client->adapter, io_read(sd, io_reg) >> 1); +} + +static int adv7842_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adv7842_state *state; + struct adv7842_platform_data *pdata = client->dev.platform_data; + struct v4l2_ctrl_handler *hdl; + struct v4l2_subdev *sd; + u16 rev; + int err; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_dbg(1, debug, client, "detecting adv7842 client on address 0x%x\n", + client->addr << 1); + + if (!pdata) { + v4l_err(client, "No platform data!\n"); + return -ENODEV; + } + + state = devm_kzalloc(&client->dev, sizeof(struct adv7842_state), GFP_KERNEL); + if (!state) { + v4l_err(client, "Could not allocate adv7842_state memory!\n"); + return -ENOMEM; + } + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &adv7842_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + state->connector_hdmi = pdata->connector_hdmi; + state->mode = pdata->mode; + + state->hdmi_port_a = true; + + /* i2c access to adv7842? */ + rev = adv_smbus_read_byte_data_check(client, 0xea, false) << 8 | + adv_smbus_read_byte_data_check(client, 0xeb, false); + if (rev != 0x2012) { + v4l2_info(sd, "got rev=0x%04x on first read attempt\n", rev); + rev = adv_smbus_read_byte_data_check(client, 0xea, false) << 8 | + adv_smbus_read_byte_data_check(client, 0xeb, false); + } + if (rev != 0x2012) { + v4l2_info(sd, "not an adv7842 on address 0x%x (rev=0x%04x)\n", + client->addr << 1, rev); + return -ENODEV; + } + + if (pdata->chip_reset) + main_reset(sd); + + /* control handlers */ + hdl = &state->hdl; + v4l2_ctrl_handler_init(hdl, 6); + + /* add in ascending ID order */ + v4l2_ctrl_new_std(hdl, &adv7842_ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(hdl, &adv7842_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &adv7842_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &adv7842_ctrl_ops, + V4L2_CID_HUE, 0, 128, 1, 0); + + /* custom controls */ + state->detect_tx_5v_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_RX_POWER_PRESENT, 0, 3, 0, 0); + state->analog_sampling_phase_ctrl = v4l2_ctrl_new_custom(hdl, + &adv7842_ctrl_analog_sampling_phase, NULL); + state->free_run_color_ctrl_manual = v4l2_ctrl_new_custom(hdl, + &adv7842_ctrl_free_run_color_manual, NULL); + state->free_run_color_ctrl = v4l2_ctrl_new_custom(hdl, + &adv7842_ctrl_free_run_color, NULL); + state->rgb_quantization_range_ctrl = + v4l2_ctrl_new_std_menu(hdl, &adv7842_ctrl_ops, + V4L2_CID_DV_RX_RGB_RANGE, V4L2_DV_RGB_RANGE_FULL, + 0, V4L2_DV_RGB_RANGE_AUTO); + sd->ctrl_handler = hdl; + if (hdl->error) { + err = hdl->error; + goto err_hdl; + } + state->detect_tx_5v_ctrl->is_private = true; + state->rgb_quantization_range_ctrl->is_private = true; + state->analog_sampling_phase_ctrl->is_private = true; + state->free_run_color_ctrl_manual->is_private = true; + state->free_run_color_ctrl->is_private = true; + + if (adv7842_s_detect_tx_5v_ctrl(sd)) { + err = -ENODEV; + goto err_hdl; + } + + state->i2c_avlink = adv7842_dummy_client(sd, pdata->i2c_avlink, 0xf3); + state->i2c_cec = adv7842_dummy_client(sd, pdata->i2c_cec, 0xf4); + state->i2c_infoframe = adv7842_dummy_client(sd, pdata->i2c_infoframe, 0xf5); + state->i2c_sdp_io = adv7842_dummy_client(sd, pdata->i2c_sdp_io, 0xf2); + state->i2c_sdp = adv7842_dummy_client(sd, pdata->i2c_sdp, 0xf1); + state->i2c_afe = adv7842_dummy_client(sd, pdata->i2c_afe, 0xf8); + state->i2c_repeater = adv7842_dummy_client(sd, pdata->i2c_repeater, 0xf9); + state->i2c_edid = adv7842_dummy_client(sd, pdata->i2c_edid, 0xfa); + state->i2c_hdmi = adv7842_dummy_client(sd, pdata->i2c_hdmi, 0xfb); + state->i2c_cp = adv7842_dummy_client(sd, pdata->i2c_cp, 0xfd); + state->i2c_vdp = adv7842_dummy_client(sd, pdata->i2c_vdp, 0xfe); + if (!state->i2c_avlink || !state->i2c_cec || !state->i2c_infoframe || + !state->i2c_sdp_io || !state->i2c_sdp || !state->i2c_afe || + !state->i2c_repeater || !state->i2c_edid || !state->i2c_hdmi || + !state->i2c_cp || !state->i2c_vdp) { + err = -ENOMEM; + v4l2_err(sd, "failed to create all i2c clients\n"); + goto err_i2c; + } + + /* work queues */ + state->work_queues = create_singlethread_workqueue(client->name); + if (!state->work_queues) { + v4l2_err(sd, "Could not create work queue\n"); + err = -ENOMEM; + goto err_i2c; + } + + INIT_DELAYED_WORK(&state->delayed_work_enable_hotplug, + adv7842_delayed_work_enable_hotplug); + + state->pad.flags = MEDIA_PAD_FL_SOURCE; + err = media_entity_init(&sd->entity, 1, &state->pad, 0); + if (err) + goto err_work_queues; + + err = adv7842_core_init(sd, pdata); + if (err) + goto err_entity; + + v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + return 0; + +err_entity: + media_entity_cleanup(&sd->entity); +err_work_queues: + cancel_delayed_work(&state->delayed_work_enable_hotplug); + destroy_workqueue(state->work_queues); +err_i2c: + adv7842_unregister_clients(state); +err_hdl: + v4l2_ctrl_handler_free(hdl); + return err; +} + +/* ----------------------------------------------------------------------- */ + +static int adv7842_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct adv7842_state *state = to_state(sd); + + adv7842_irq_enable(sd, false); + + cancel_delayed_work(&state->delayed_work_enable_hotplug); + destroy_workqueue(state->work_queues); + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + adv7842_unregister_clients(to_state(sd)); + v4l2_ctrl_handler_free(sd->ctrl_handler); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_device_id adv7842_id[] = { + { "adv7842", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adv7842_id); + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver adv7842_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "adv7842", + }, + .probe = adv7842_probe, + .remove = adv7842_remove, + .id_table = adv7842_id, +}; + +module_i2c_driver(adv7842_driver); diff --git a/drivers/media/i2c/ml86v7667.c b/drivers/media/i2c/ml86v7667.c index a9857022f71d..a9110d8bbbcd 100644 --- a/drivers/media/i2c/ml86v7667.c +++ b/drivers/media/i2c/ml86v7667.c @@ -209,7 +209,8 @@ static int ml86v7667_mbus_fmt(struct v4l2_subdev *sd, fmt->code = V4L2_MBUS_FMT_YUYV8_2X8; fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; - fmt->field = V4L2_FIELD_INTERLACED; + /* The top field is always transferred first by the chip */ + fmt->field = V4L2_FIELD_INTERLACED_TB; fmt->width = 720; fmt->height = priv->std & V4L2_STD_525_60 ? 480 : 576; diff --git a/drivers/media/i2c/mt9v032.c b/drivers/media/i2c/mt9v032.c index 60c6f6739560..2c50effaa334 100644 --- a/drivers/media/i2c/mt9v032.c +++ b/drivers/media/i2c/mt9v032.c @@ -12,6 +12,7 @@ * published by the Free Software Foundation. */ +#include <linux/clk.h> #include <linux/delay.h> #include <linux/i2c.h> #include <linux/log2.h> @@ -135,6 +136,8 @@ struct mt9v032 { struct mutex power_lock; int power_count; + struct clk *clk; + struct mt9v032_platform_data *pdata; u32 sysclk; @@ -219,10 +222,9 @@ static int mt9v032_power_on(struct mt9v032 *mt9v032) struct i2c_client *client = v4l2_get_subdevdata(&mt9v032->subdev); int ret; - if (mt9v032->pdata->set_clock) { - mt9v032->pdata->set_clock(&mt9v032->subdev, mt9v032->sysclk); - udelay(1); - } + clk_set_rate(mt9v032->clk, mt9v032->sysclk); + clk_prepare_enable(mt9v032->clk); + udelay(1); /* Reset the chip and stop data read out */ ret = mt9v032_write(client, MT9V032_RESET, 1); @@ -238,8 +240,7 @@ static int mt9v032_power_on(struct mt9v032 *mt9v032) static void mt9v032_power_off(struct mt9v032 *mt9v032) { - if (mt9v032->pdata->set_clock) - mt9v032->pdata->set_clock(&mt9v032->subdev, 0); + clk_disable_unprepare(mt9v032->clk); } static int __mt9v032_set_power(struct mt9v032 *mt9v032, bool on) @@ -748,6 +749,10 @@ static int mt9v032_probe(struct i2c_client *client, if (!mt9v032) return -ENOMEM; + mt9v032->clk = devm_clk_get(&client->dev, NULL); + if (IS_ERR(mt9v032->clk)) + return PTR_ERR(mt9v032->clk); + mutex_init(&mt9v032->power_lock); mt9v032->pdata = pdata; diff --git a/drivers/media/i2c/ov9650.c b/drivers/media/i2c/ov9650.c index 1dbb8118a285..4da90c621f7e 100644 --- a/drivers/media/i2c/ov9650.c +++ b/drivers/media/i2c/ov9650.c @@ -1083,7 +1083,7 @@ static int ov965x_enum_frame_sizes(struct v4l2_subdev *sd, { int i = ARRAY_SIZE(ov965x_formats); - if (fse->index > ARRAY_SIZE(ov965x_framesizes)) + if (fse->index >= ARRAY_SIZE(ov965x_framesizes)) return -EINVAL; while (--i) diff --git a/drivers/media/i2c/s5c73m3/s5c73m3-core.c b/drivers/media/i2c/s5c73m3/s5c73m3-core.c index 825ea86d982d..b76ec0e7e685 100644 --- a/drivers/media/i2c/s5c73m3/s5c73m3-core.c +++ b/drivers/media/i2c/s5c73m3/s5c73m3-core.c @@ -1111,6 +1111,11 @@ static int s5c73m3_oif_set_fmt(struct v4l2_subdev *sd, if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { mf = v4l2_subdev_get_try_format(fh, fmt->pad); *mf = fmt->format; + if (fmt->pad == OIF_ISP_PAD) { + mf = v4l2_subdev_get_try_format(fh, OIF_SOURCE_PAD); + mf->width = fmt->format.width; + mf->height = fmt->format.height; + } } else { switch (fmt->pad) { case OIF_ISP_PAD: diff --git a/drivers/media/i2c/s5k6aa.c b/drivers/media/i2c/s5k6aa.c index 789c02a6ca1a..629a5cdadd3a 100644 --- a/drivers/media/i2c/s5k6aa.c +++ b/drivers/media/i2c/s5k6aa.c @@ -1003,7 +1003,7 @@ static int s5k6aa_enum_frame_interval(struct v4l2_subdev *sd, const struct s5k6aa_interval *fi; int ret = 0; - if (fie->index > ARRAY_SIZE(s5k6aa_intervals)) + if (fie->index >= ARRAY_SIZE(s5k6aa_intervals)) return -EINVAL; v4l_bound_align_image(&fie->width, S5K6AA_WIN_WIDTH_MIN, diff --git a/drivers/media/i2c/saa7115.c b/drivers/media/i2c/saa7115.c index 7fd766ec64c8..637d02634527 100644 --- a/drivers/media/i2c/saa7115.c +++ b/drivers/media/i2c/saa7115.c @@ -225,19 +225,63 @@ static const unsigned char saa7111_init[] = { 0x00, 0x00 }; -/* SAA7113/GM7113C init codes - * It's important that R_14... R_17 == 0x00 - * for the gm7113c chip to deliver stable video +/* + * This table has one illegal value, and some values that are not + * correct according to the datasheet initialization table. + * + * If you need a table with legal/default values tell the driver in + * i2c_board_info.platform_data, and you will get the gm7113c_init + * table instead. */ + +/* SAA7113 Init codes */ static const unsigned char saa7113_init[] = { R_01_INC_DELAY, 0x08, R_02_INPUT_CNTL_1, 0xc2, R_03_INPUT_CNTL_2, 0x30, R_04_INPUT_CNTL_3, 0x00, R_05_INPUT_CNTL_4, 0x00, - R_06_H_SYNC_START, 0x89, + R_06_H_SYNC_START, 0x89, /* Illegal value -119, + * min. value = -108 (0x94) */ + R_07_H_SYNC_STOP, 0x0d, + R_08_SYNC_CNTL, 0x88, /* Not datasheet default. + * HTC = VTR mode, should be 0x98 */ + R_09_LUMA_CNTL, 0x01, + R_0A_LUMA_BRIGHT_CNTL, 0x80, + R_0B_LUMA_CONTRAST_CNTL, 0x47, + R_0C_CHROMA_SAT_CNTL, 0x40, + R_0D_CHROMA_HUE_CNTL, 0x00, + R_0E_CHROMA_CNTL_1, 0x01, + R_0F_CHROMA_GAIN_CNTL, 0x2a, + R_10_CHROMA_CNTL_2, 0x08, /* Not datsheet default. + * VRLN enabled, should be 0x00 */ + R_11_MODE_DELAY_CNTL, 0x0c, + R_12_RT_SIGNAL_CNTL, 0x07, /* Not datasheet default, + * should be 0x01 */ + R_13_RT_X_PORT_OUT_CNTL, 0x00, + R_14_ANAL_ADC_COMPAT_CNTL, 0x00, + R_15_VGATE_START_FID_CHG, 0x00, + R_16_VGATE_STOP, 0x00, + R_17_MISC_VGATE_CONF_AND_MSB, 0x00, + + 0x00, 0x00 +}; + +/* + * GM7113C is a clone of the SAA7113 chip + * This init table is copied out of the saa7113 datasheet. + * In R_08 we enable "Automatic Field Detection" [AUFD], + * this is disabled when saa711x_set_v4lstd is called. + */ +static const unsigned char gm7113c_init[] = { + R_01_INC_DELAY, 0x08, + R_02_INPUT_CNTL_1, 0xc0, + R_03_INPUT_CNTL_2, 0x33, + R_04_INPUT_CNTL_3, 0x00, + R_05_INPUT_CNTL_4, 0x00, + R_06_H_SYNC_START, 0xe9, R_07_H_SYNC_STOP, 0x0d, - R_08_SYNC_CNTL, 0x88, + R_08_SYNC_CNTL, 0x98, R_09_LUMA_CNTL, 0x01, R_0A_LUMA_BRIGHT_CNTL, 0x80, R_0B_LUMA_CONTRAST_CNTL, 0x47, @@ -245,9 +289,9 @@ static const unsigned char saa7113_init[] = { R_0D_CHROMA_HUE_CNTL, 0x00, R_0E_CHROMA_CNTL_1, 0x01, R_0F_CHROMA_GAIN_CNTL, 0x2a, - R_10_CHROMA_CNTL_2, 0x08, + R_10_CHROMA_CNTL_2, 0x00, R_11_MODE_DELAY_CNTL, 0x0c, - R_12_RT_SIGNAL_CNTL, 0x07, + R_12_RT_SIGNAL_CNTL, 0x01, R_13_RT_X_PORT_OUT_CNTL, 0x00, R_14_ANAL_ADC_COMPAT_CNTL, 0x00, R_15_VGATE_START_FID_CHG, 0x00, @@ -462,24 +506,6 @@ static const unsigned char saa7115_cfg_50hz_video[] = { /* ============== SAA7715 VIDEO templates (end) ======= */ -/* ============== GM7113C VIDEO templates ============= */ -static const unsigned char gm7113c_cfg_60hz_video[] = { - R_08_SYNC_CNTL, 0x68, /* 0xBO: auto detection, 0x68 = NTSC */ - R_0E_CHROMA_CNTL_1, 0x07, /* video autodetection is on */ - - 0x00, 0x00 -}; - -static const unsigned char gm7113c_cfg_50hz_video[] = { - R_08_SYNC_CNTL, 0x28, /* 0x28 = PAL */ - R_0E_CHROMA_CNTL_1, 0x07, - - 0x00, 0x00 -}; - -/* ============== GM7113C VIDEO templates (end) ======= */ - - static const unsigned char saa7115_cfg_vbi_on[] = { R_80_GLOBAL_CNTL_1, 0x00, /* reset tasks */ R_88_POWER_SAVE_ADC_PORT_CNTL, 0xd0, /* reset scaler */ @@ -964,17 +990,24 @@ static void saa711x_set_v4lstd(struct v4l2_subdev *sd, v4l2_std_id std) // This works for NTSC-M, SECAM-L and the 50Hz PAL variants. if (std & V4L2_STD_525_60) { v4l2_dbg(1, debug, sd, "decoder set standard 60 Hz\n"); - if (state->ident == GM7113C) - saa711x_writeregs(sd, gm7113c_cfg_60hz_video); - else + if (state->ident == GM7113C) { + u8 reg = saa711x_read(sd, R_08_SYNC_CNTL); + reg &= ~(SAA7113_R_08_FSEL | SAA7113_R_08_AUFD); + reg |= SAA7113_R_08_FSEL; + saa711x_write(sd, R_08_SYNC_CNTL, reg); + } else { saa711x_writeregs(sd, saa7115_cfg_60hz_video); + } saa711x_set_size(sd, 720, 480); } else { v4l2_dbg(1, debug, sd, "decoder set standard 50 Hz\n"); - if (state->ident == GM7113C) - saa711x_writeregs(sd, gm7113c_cfg_50hz_video); - else + if (state->ident == GM7113C) { + u8 reg = saa711x_read(sd, R_08_SYNC_CNTL); + reg &= ~(SAA7113_R_08_FSEL | SAA7113_R_08_AUFD); + saa711x_write(sd, R_08_SYNC_CNTL, reg); + } else { saa711x_writeregs(sd, saa7115_cfg_50hz_video); + } saa711x_set_size(sd, 720, 576); } @@ -1596,6 +1629,65 @@ static const struct v4l2_subdev_ops saa711x_ops = { /* ----------------------------------------------------------------------- */ +static void saa711x_write_platform_data(struct saa711x_state *state, + struct saa7115_platform_data *data) +{ + struct v4l2_subdev *sd = &state->sd; + u8 work; + + if (state->ident != GM7113C && + state->ident != SAA7113) + return; + + if (data->saa7113_r08_htc) { + work = saa711x_read(sd, R_08_SYNC_CNTL); + work &= ~SAA7113_R_08_HTC_MASK; + work |= ((*data->saa7113_r08_htc) << SAA7113_R_08_HTC_OFFSET); + saa711x_write(sd, R_08_SYNC_CNTL, work); + } + + if (data->saa7113_r10_vrln) { + work = saa711x_read(sd, R_10_CHROMA_CNTL_2); + work &= ~SAA7113_R_10_VRLN_MASK; + if (*data->saa7113_r10_vrln) + work |= (1 << SAA7113_R_10_VRLN_OFFSET); + saa711x_write(sd, R_10_CHROMA_CNTL_2, work); + } + + if (data->saa7113_r10_ofts) { + work = saa711x_read(sd, R_10_CHROMA_CNTL_2); + work &= ~SAA7113_R_10_OFTS_MASK; + work |= (*data->saa7113_r10_ofts << SAA7113_R_10_OFTS_OFFSET); + saa711x_write(sd, R_10_CHROMA_CNTL_2, work); + } + + if (data->saa7113_r12_rts0) { + work = saa711x_read(sd, R_12_RT_SIGNAL_CNTL); + work &= ~SAA7113_R_12_RTS0_MASK; + work |= (*data->saa7113_r12_rts0 << SAA7113_R_12_RTS0_OFFSET); + + /* According to the datasheet, + * SAA7113_RTS_DOT_IN should only be used on RTS1 */ + WARN_ON(*data->saa7113_r12_rts0 == SAA7113_RTS_DOT_IN); + saa711x_write(sd, R_12_RT_SIGNAL_CNTL, work); + } + + if (data->saa7113_r12_rts1) { + work = saa711x_read(sd, R_12_RT_SIGNAL_CNTL); + work &= ~SAA7113_R_12_RTS1_MASK; + work |= (*data->saa7113_r12_rts1 << SAA7113_R_12_RTS1_OFFSET); + saa711x_write(sd, R_12_RT_SIGNAL_CNTL, work); + } + + if (data->saa7113_r13_adlsb) { + work = saa711x_read(sd, R_13_RT_X_PORT_OUT_CNTL); + work &= ~SAA7113_R_13_ADLSB_MASK; + if (*data->saa7113_r13_adlsb) + work |= (1 << SAA7113_R_13_ADLSB_OFFSET); + saa711x_write(sd, R_13_RT_X_PORT_OUT_CNTL, work); + } +} + /** * saa711x_detect_chip - Detects the saa711x (or clone) variant * @client: I2C client structure. @@ -1704,6 +1796,7 @@ static int saa711x_probe(struct i2c_client *client, struct saa711x_state *state; struct v4l2_subdev *sd; struct v4l2_ctrl_handler *hdl; + struct saa7115_platform_data *pdata; int ident; char name[CHIP_VER_SIZE + 1]; @@ -1767,21 +1860,31 @@ static int saa711x_probe(struct i2c_client *client, /* init to 60hz/48khz */ state->crystal_freq = SAA7115_FREQ_24_576_MHZ; + pdata = client->dev.platform_data; switch (state->ident) { case SAA7111: case SAA7111A: saa711x_writeregs(sd, saa7111_init); break; case GM7113C: + saa711x_writeregs(sd, gm7113c_init); + break; case SAA7113: - saa711x_writeregs(sd, saa7113_init); + if (pdata && pdata->saa7113_force_gm7113c_init) + saa711x_writeregs(sd, gm7113c_init); + else + saa711x_writeregs(sd, saa7113_init); break; default: state->crystal_freq = SAA7115_FREQ_32_11_MHZ; saa711x_writeregs(sd, saa7115_init_auto_input); } - if (state->ident > SAA7111A) + if (state->ident > SAA7111A && state->ident != GM7113C) saa711x_writeregs(sd, saa7115_init_misc); + + if (pdata) + saa711x_write_platform_data(state, pdata); + saa711x_set_v4lstd(sd, V4L2_STD_NTSC); v4l2_ctrl_handler_setup(hdl); diff --git a/drivers/media/i2c/saa711x_regs.h b/drivers/media/i2c/saa711x_regs.h index 4e5f2eb0a2c1..730ca90b30ac 100644 --- a/drivers/media/i2c/saa711x_regs.h +++ b/drivers/media/i2c/saa711x_regs.h @@ -201,6 +201,25 @@ #define R_FB_PULSE_C_POS_MSB 0xfb #define R_FF_S_PLL_MAX_PHASE_ERR_THRESH_NUM_LINES 0xff +/* SAA7113 bit-masks */ +#define SAA7113_R_08_HTC_OFFSET 3 +#define SAA7113_R_08_HTC_MASK (0x3 << SAA7113_R_08_HTC_OFFSET) +#define SAA7113_R_08_FSEL 0x40 +#define SAA7113_R_08_AUFD 0x80 + +#define SAA7113_R_10_VRLN_OFFSET 3 +#define SAA7113_R_10_VRLN_MASK (0x1 << SAA7113_R_10_VRLN_OFFSET) +#define SAA7113_R_10_OFTS_OFFSET 6 +#define SAA7113_R_10_OFTS_MASK (0x3 << SAA7113_R_10_OFTS_OFFSET) + +#define SAA7113_R_12_RTS0_OFFSET 0 +#define SAA7113_R_12_RTS0_MASK (0xf << SAA7113_R_12_RTS0_OFFSET) +#define SAA7113_R_12_RTS1_OFFSET 4 +#define SAA7113_R_12_RTS1_MASK (0xf << SAA7113_R_12_RTS1_OFFSET) + +#define SAA7113_R_13_ADLSB_OFFSET 7 +#define SAA7113_R_13_ADLSB_MASK (0x1 << SAA7113_R_13_ADLSB_OFFSET) + #if 0 /* Those structs will be used in the future for debug purposes */ struct saa711x_reg_descr { diff --git a/drivers/media/i2c/smiapp-pll.c b/drivers/media/i2c/smiapp-pll.c index d8d5da7c52db..2335529b195c 100644 --- a/drivers/media/i2c/smiapp-pll.c +++ b/drivers/media/i2c/smiapp-pll.c @@ -87,6 +87,17 @@ static void print_pll(struct device *dev, struct smiapp_pll *pll) dev_dbg(dev, "vt_pix_clk_freq_hz \t%d\n", pll->vt_pix_clk_freq_hz); } +/* + * Heuristically guess the PLL tree for a given common multiplier and + * divisor. Begin with the operational timing and continue to video + * timing once operational timing has been verified. + * + * @mul is the PLL multiplier and @div is the common divisor + * (pre_pll_clk_div and op_sys_clk_div combined). The final PLL + * multiplier will be a multiple of @mul. + * + * @return Zero on success, error code on error. + */ static int __smiapp_pll_calculate(struct device *dev, const struct smiapp_pll_limits *limits, struct smiapp_pll *pll, uint32_t mul, @@ -95,6 +106,12 @@ static int __smiapp_pll_calculate(struct device *dev, uint32_t sys_div; uint32_t best_pix_div = INT_MAX >> 1; uint32_t vt_op_binning_div; + /* + * Higher multipliers (and divisors) are often required than + * necessitated by the external clock and the output clocks. + * There are limits for all values in the clock tree. These + * are the minimum and maximum multiplier for mul. + */ uint32_t more_mul_min, more_mul_max; uint32_t more_mul_factor; uint32_t min_vt_div, max_vt_div, vt_div; diff --git a/drivers/media/i2c/smiapp/smiapp-core.c b/drivers/media/i2c/smiapp/smiapp-core.c index 7ac7580f85c9..ae66d91bf713 100644 --- a/drivers/media/i2c/smiapp/smiapp-core.c +++ b/drivers/media/i2c/smiapp/smiapp-core.c @@ -1122,9 +1122,9 @@ static int smiapp_power_on(struct smiapp_sensor *sensor) rval = sensor->platform_data->set_xclk( &sensor->src->sd, sensor->platform_data->ext_clk); else - rval = clk_enable(sensor->ext_clk); + rval = clk_prepare_enable(sensor->ext_clk); if (rval < 0) { - dev_dbg(&client->dev, "failed to set xclk\n"); + dev_dbg(&client->dev, "failed to enable xclk\n"); goto out_xclk_fail; } usleep_range(1000, 1000); @@ -1244,7 +1244,7 @@ out_cci_addr_fail: if (sensor->platform_data->set_xclk) sensor->platform_data->set_xclk(&sensor->src->sd, 0); else - clk_disable(sensor->ext_clk); + clk_disable_unprepare(sensor->ext_clk); out_xclk_fail: regulator_disable(sensor->vana); @@ -1270,7 +1270,7 @@ static void smiapp_power_off(struct smiapp_sensor *sensor) if (sensor->platform_data->set_xclk) sensor->platform_data->set_xclk(&sensor->src->sd, 0); else - clk_disable(sensor->ext_clk); + clk_disable_unprepare(sensor->ext_clk); usleep_range(5000, 5000); regulator_disable(sensor->vana); sensor->streaming = 0; @@ -1835,12 +1835,12 @@ static void smiapp_set_compose_scaler(struct v4l2_subdev *subdev, * sensor->limits[SMIAPP_LIMIT_SCALER_N_MIN] / sensor->limits[SMIAPP_LIMIT_MIN_X_OUTPUT_SIZE]; - a = min(sensor->limits[SMIAPP_LIMIT_SCALER_M_MAX], - max(a, sensor->limits[SMIAPP_LIMIT_SCALER_M_MIN])); - b = min(sensor->limits[SMIAPP_LIMIT_SCALER_M_MAX], - max(b, sensor->limits[SMIAPP_LIMIT_SCALER_M_MIN])); - max_m = min(sensor->limits[SMIAPP_LIMIT_SCALER_M_MAX], - max(max_m, sensor->limits[SMIAPP_LIMIT_SCALER_M_MIN])); + a = clamp(a, sensor->limits[SMIAPP_LIMIT_SCALER_M_MIN], + sensor->limits[SMIAPP_LIMIT_SCALER_M_MAX]); + b = clamp(b, sensor->limits[SMIAPP_LIMIT_SCALER_M_MIN], + sensor->limits[SMIAPP_LIMIT_SCALER_M_MAX]); + max_m = clamp(max_m, sensor->limits[SMIAPP_LIMIT_SCALER_M_MIN], + sensor->limits[SMIAPP_LIMIT_SCALER_M_MAX]); dev_dbg(&client->dev, "scaling: a %d b %d max_m %d\n", a, b, max_m); @@ -2363,11 +2363,9 @@ static int smiapp_registered(struct v4l2_subdev *subdev) } if (!sensor->platform_data->set_xclk) { - sensor->ext_clk = devm_clk_get(&client->dev, - sensor->platform_data->ext_clk_name); + sensor->ext_clk = devm_clk_get(&client->dev, "ext_clk"); if (IS_ERR(sensor->ext_clk)) { - dev_err(&client->dev, "could not get clock %s\n", - sensor->platform_data->ext_clk_name); + dev_err(&client->dev, "could not get clock\n"); return -ENODEV; } @@ -2375,8 +2373,7 @@ static int smiapp_registered(struct v4l2_subdev *subdev) sensor->platform_data->ext_clk); if (rval < 0) { dev_err(&client->dev, - "unable to set clock %s freq to %u\n", - sensor->platform_data->ext_clk_name, + "unable to set clock freq to %u\n", sensor->platform_data->ext_clk); return -ENODEV; } @@ -2839,7 +2836,7 @@ static int smiapp_remove(struct i2c_client *client) if (sensor->platform_data->set_xclk) sensor->platform_data->set_xclk(&sensor->src->sd, 0); else - clk_disable(sensor->ext_clk); + clk_disable_unprepare(sensor->ext_clk); sensor->power_count = 0; } diff --git a/drivers/media/i2c/soc_camera/mt9m111.c b/drivers/media/i2c/soc_camera/mt9m111.c index de3605df47c5..6f4056668bbc 100644 --- a/drivers/media/i2c/soc_camera/mt9m111.c +++ b/drivers/media/i2c/soc_camera/mt9m111.c @@ -946,6 +946,10 @@ static int mt9m111_probe(struct i2c_client *client, if (!mt9m111) return -ENOMEM; + mt9m111->clk = v4l2_clk_get(&client->dev, "mclk"); + if (IS_ERR(mt9m111->clk)) + return -EPROBE_DEFER; + /* Default HIGHPOWER context */ mt9m111->ctx = &context_b; @@ -963,8 +967,10 @@ static int mt9m111_probe(struct i2c_client *client, &mt9m111_ctrl_ops, V4L2_CID_EXPOSURE_AUTO, 1, 0, V4L2_EXPOSURE_AUTO); mt9m111->subdev.ctrl_handler = &mt9m111->hdl; - if (mt9m111->hdl.error) - return mt9m111->hdl.error; + if (mt9m111->hdl.error) { + ret = mt9m111->hdl.error; + goto out_clkput; + } /* Second stage probe - when a capture adapter is there */ mt9m111->rect.left = MT9M111_MIN_DARK_COLS; @@ -975,18 +981,25 @@ static int mt9m111_probe(struct i2c_client *client, mt9m111->lastpage = -1; mutex_init(&mt9m111->power_lock); - mt9m111->clk = v4l2_clk_get(&client->dev, "mclk"); - if (IS_ERR(mt9m111->clk)) { - ret = PTR_ERR(mt9m111->clk); - goto eclkget; - } + ret = soc_camera_power_init(&client->dev, ssdd); + if (ret < 0) + goto out_hdlfree; ret = mt9m111_video_probe(client); - if (ret) { - v4l2_clk_put(mt9m111->clk); -eclkget: - v4l2_ctrl_handler_free(&mt9m111->hdl); - } + if (ret < 0) + goto out_hdlfree; + + mt9m111->subdev.dev = &client->dev; + ret = v4l2_async_register_subdev(&mt9m111->subdev); + if (ret < 0) + goto out_hdlfree; + + return 0; + +out_hdlfree: + v4l2_ctrl_handler_free(&mt9m111->hdl); +out_clkput: + v4l2_clk_put(mt9m111->clk); return ret; } @@ -995,6 +1008,7 @@ static int mt9m111_remove(struct i2c_client *client) { struct mt9m111 *mt9m111 = to_mt9m111(client); + v4l2_async_unregister_subdev(&mt9m111->subdev); v4l2_clk_put(mt9m111->clk); v4l2_device_unregister_subdev(&mt9m111->subdev); v4l2_ctrl_handler_free(&mt9m111->hdl); diff --git a/drivers/media/i2c/soc_camera/mt9t031.c b/drivers/media/i2c/soc_camera/mt9t031.c index 47d18d0bafe7..ee7bb0ffcecb 100644 --- a/drivers/media/i2c/soc_camera/mt9t031.c +++ b/drivers/media/i2c/soc_camera/mt9t031.c @@ -594,9 +594,12 @@ static int mt9t031_s_power(struct v4l2_subdev *sd, int on) ret = soc_camera_power_on(&client->dev, ssdd, mt9t031->clk); if (ret < 0) return ret; - vdev->dev.type = &mt9t031_dev_type; + if (vdev) + /* Not needed during probing, when vdev isn't available yet */ + vdev->dev.type = &mt9t031_dev_type; } else { - vdev->dev.type = NULL; + if (vdev) + vdev->dev.type = NULL; soc_camera_power_off(&client->dev, ssdd, mt9t031->clk); } diff --git a/drivers/media/i2c/ths7303.c b/drivers/media/i2c/ths7303.c index 0a2dacbd7a63..42276d93624c 100644 --- a/drivers/media/i2c/ths7303.c +++ b/drivers/media/i2c/ths7303.c @@ -291,10 +291,8 @@ static int ths7303_log_status(struct v4l2_subdev *sd) struct v4l2_bt_timings *bt = bt = &state->bt; u32 frame_width, frame_height; - frame_width = bt->width + bt->hfrontporch + - bt->hsync + bt->hbackporch; - frame_height = bt->height + bt->vfrontporch + - bt->vsync + bt->vbackporch; + frame_width = V4L2_DV_BT_FRAME_WIDTH(bt); + frame_height = V4L2_DV_BT_FRAME_HEIGHT(bt); v4l2_info(sd, "timings: %dx%d%s%d (%dx%d). Pix freq. = %d Hz. Polarities = 0x%x\n", bt->width, bt->height, bt->interlaced ? "i" : "p", diff --git a/drivers/media/i2c/ths8200.c b/drivers/media/i2c/ths8200.c index a24f90c5261c..a58a8f663ffb 100644 --- a/drivers/media/i2c/ths8200.c +++ b/drivers/media/i2c/ths8200.c @@ -21,6 +21,8 @@ #include <linux/module.h> #include <linux/v4l2-dv-timings.h> +#include <media/v4l2-dv-timings.h> +#include <media/v4l2-async.h> #include <media/v4l2-device.h> #include "ths8200_regs.h" @@ -42,18 +44,16 @@ struct ths8200_state { struct v4l2_dv_timings dv_timings; }; -static const struct v4l2_dv_timings ths8200_timings[] = { - V4L2_DV_BT_CEA_720X480P59_94, - V4L2_DV_BT_CEA_1280X720P24, - V4L2_DV_BT_CEA_1280X720P25, - V4L2_DV_BT_CEA_1280X720P30, - V4L2_DV_BT_CEA_1280X720P50, - V4L2_DV_BT_CEA_1280X720P60, - V4L2_DV_BT_CEA_1920X1080P24, - V4L2_DV_BT_CEA_1920X1080P25, - V4L2_DV_BT_CEA_1920X1080P30, - V4L2_DV_BT_CEA_1920X1080P50, - V4L2_DV_BT_CEA_1920X1080P60, +static const struct v4l2_dv_timings_cap ths8200_timings_cap = { + .type = V4L2_DV_BT_656_1120, + .bt = { + .max_width = 1920, + .max_height = 1080, + .min_pixelclock = 25000000, + .max_pixelclock = 148500000, + .standards = V4L2_DV_BT_STD_CEA861, + .capabilities = V4L2_DV_BT_CAP_PROGRESSIVE, + }, }; static inline struct ths8200_state *to_state(struct v4l2_subdev *sd) @@ -63,22 +63,22 @@ static inline struct ths8200_state *to_state(struct v4l2_subdev *sd) static inline unsigned hblanking(const struct v4l2_bt_timings *t) { - return t->hfrontporch + t->hsync + t->hbackporch; + return V4L2_DV_BT_BLANKING_WIDTH(t); } static inline unsigned htotal(const struct v4l2_bt_timings *t) { - return t->width + t->hfrontporch + t->hsync + t->hbackporch; + return V4L2_DV_BT_FRAME_WIDTH(t); } static inline unsigned vblanking(const struct v4l2_bt_timings *t) { - return t->vfrontporch + t->vsync + t->vbackporch; + return V4L2_DV_BT_BLANKING_HEIGHT(t); } static inline unsigned vtotal(const struct v4l2_bt_timings *t) { - return t->height + t->vfrontporch + t->vsync + t->vbackporch; + return V4L2_DV_BT_FRAME_HEIGHT(t); } static int ths8200_read(struct v4l2_subdev *sd, u8 reg) @@ -133,39 +133,6 @@ static int ths8200_s_register(struct v4l2_subdev *sd, } #endif -static void ths8200_print_timings(struct v4l2_subdev *sd, - struct v4l2_dv_timings *timings, - const char *txt, bool detailed) -{ - struct v4l2_bt_timings *bt = &timings->bt; - u32 htot, vtot; - - if (timings->type != V4L2_DV_BT_656_1120) - return; - - htot = htotal(bt); - vtot = vtotal(bt); - - v4l2_info(sd, "%s %dx%d%s%d (%dx%d)", - txt, bt->width, bt->height, bt->interlaced ? "i" : "p", - (htot * vtot) > 0 ? ((u32)bt->pixelclock / (htot * vtot)) : 0, - htot, vtot); - - if (detailed) { - v4l2_info(sd, " horizontal: fp = %d, %ssync = %d, bp = %d\n", - bt->hfrontporch, - (bt->polarities & V4L2_DV_HSYNC_POS_POL) ? "+" : "-", - bt->hsync, bt->hbackporch); - v4l2_info(sd, " vertical: fp = %d, %ssync = %d, bp = %d\n", - bt->vfrontporch, - (bt->polarities & V4L2_DV_VSYNC_POS_POL) ? "+" : "-", - bt->vsync, bt->vbackporch); - v4l2_info(sd, - " pixelclock: %lld, flags: 0x%x, standards: 0x%x\n", - bt->pixelclock, bt->flags, bt->standards); - } -} - static int ths8200_log_status(struct v4l2_subdev *sd) { struct ths8200_state *state = to_state(sd); @@ -182,9 +149,8 @@ static int ths8200_log_status(struct v4l2_subdev *sd) ths8200_read(sd, THS8200_DTG2_PIXEL_CNT_LSB), (ths8200_read(sd, THS8200_DTG2_LINE_CNT_MSB) & 0x07) * 256 + ths8200_read(sd, THS8200_DTG2_LINE_CNT_LSB)); - ths8200_print_timings(sd, &state->dv_timings, - "Configured format:", true); - + v4l2_print_dv_timings(sd->name, "Configured format:", + &state->dv_timings, true); return 0; } @@ -409,25 +375,15 @@ static int ths8200_s_dv_timings(struct v4l2_subdev *sd, struct v4l2_dv_timings *timings) { struct ths8200_state *state = to_state(sd); - int i; v4l2_dbg(1, debug, sd, "%s:\n", __func__); - if (timings->type != V4L2_DV_BT_656_1120) - return -EINVAL; - - /* TODO Support interlaced formats */ - if (timings->bt.interlaced) { - v4l2_dbg(1, debug, sd, "TODO Support interlaced formats\n"); + if (!v4l2_valid_dv_timings(timings, &ths8200_timings_cap, + NULL, NULL)) return -EINVAL; - } - - for (i = 0; i < ARRAY_SIZE(ths8200_timings); i++) { - if (v4l_match_dv_timings(&ths8200_timings[i], timings, 10)) - break; - } - if (i == ARRAY_SIZE(ths8200_timings)) { + if (!v4l2_find_dv_timings_cap(timings, &ths8200_timings_cap, 10, + NULL, NULL)) { v4l2_dbg(1, debug, sd, "Unsupported format\n"); return -EINVAL; } @@ -457,26 +413,14 @@ static int ths8200_g_dv_timings(struct v4l2_subdev *sd, static int ths8200_enum_dv_timings(struct v4l2_subdev *sd, struct v4l2_enum_dv_timings *timings) { - /* Check requested format index is within range */ - if (timings->index >= ARRAY_SIZE(ths8200_timings)) - return -EINVAL; - - timings->timings = ths8200_timings[timings->index]; - - return 0; + return v4l2_enum_dv_timings_cap(timings, &ths8200_timings_cap, + NULL, NULL); } static int ths8200_dv_timings_cap(struct v4l2_subdev *sd, struct v4l2_dv_timings_cap *cap) { - cap->type = V4L2_DV_BT_656_1120; - cap->bt.max_width = 1920; - cap->bt.max_height = 1080; - cap->bt.min_pixelclock = 27000000; - cap->bt.max_pixelclock = 148500000; - cap->bt.standards = V4L2_DV_BT_STD_CEA861; - cap->bt.capabilities = V4L2_DV_BT_CAP_PROGRESSIVE; - + *cap = ths8200_timings_cap; return 0; } @@ -500,6 +444,7 @@ static int ths8200_probe(struct i2c_client *client, { struct ths8200_state *state; struct v4l2_subdev *sd; + int error; /* Check if the adapter supports the needed features */ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) @@ -517,6 +462,10 @@ static int ths8200_probe(struct i2c_client *client, ths8200_core_init(sd); + error = v4l2_async_register_subdev(&state->sd); + if (error) + return error; + v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name, client->addr << 1, client->adapter->name); @@ -526,12 +475,13 @@ static int ths8200_probe(struct i2c_client *client, static int ths8200_remove(struct i2c_client *client) { struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ths8200_state *decoder = to_state(sd); v4l2_dbg(1, debug, sd, "%s removed @ 0x%x (%s)\n", client->name, client->addr << 1, client->adapter->name); ths8200_s_power(sd, false); - + v4l2_async_unregister_subdev(&decoder->sd); v4l2_device_unregister_subdev(sd); return 0; @@ -543,10 +493,19 @@ static struct i2c_device_id ths8200_id[] = { }; MODULE_DEVICE_TABLE(i2c, ths8200_id); +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id ths8200_of_match[] = { + { .compatible = "ti,ths8200", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ths8200_of_match); +#endif + static struct i2c_driver ths8200_driver = { .driver = { .owner = THIS_MODULE, .name = "ths8200", + .of_match_table = of_match_ptr(ths8200_of_match), }, .probe = ths8200_probe, .remove = ths8200_remove, diff --git a/drivers/media/i2c/tvp514x.c b/drivers/media/i2c/tvp514x.c index 9c6d66a9868f..91f3dd4cda1b 100644 --- a/drivers/media/i2c/tvp514x.c +++ b/drivers/media/i2c/tvp514x.c @@ -36,6 +36,7 @@ #include <linux/module.h> #include <linux/v4l2-mediabus.h> +#include <media/v4l2-async.h> #include <media/v4l2-device.h> #include <media/v4l2-common.h> #include <media/v4l2-mediabus.h> @@ -1175,16 +1176,22 @@ tvp514x_probe(struct i2c_client *client, const struct i2c_device_id *id) sd->ctrl_handler = &decoder->hdl; if (decoder->hdl.error) { ret = decoder->hdl.error; - - v4l2_ctrl_handler_free(&decoder->hdl); - return ret; + goto done; } v4l2_ctrl_handler_setup(&decoder->hdl); - v4l2_info(sd, "%s decoder driver registered !!\n", sd->name); - - return 0; + ret = v4l2_async_register_subdev(&decoder->sd); + if (!ret) + v4l2_info(sd, "%s decoder driver registered !!\n", sd->name); +done: + if (ret < 0) { + v4l2_ctrl_handler_free(&decoder->hdl); +#if defined(CONFIG_MEDIA_CONTROLLER) + media_entity_cleanup(&decoder->sd.entity); +#endif + } + return ret; } /** @@ -1199,6 +1206,7 @@ static int tvp514x_remove(struct i2c_client *client) struct v4l2_subdev *sd = i2c_get_clientdata(client); struct tvp514x_decoder *decoder = to_decoder(sd); + v4l2_async_unregister_subdev(&decoder->sd); v4l2_device_unregister_subdev(sd); #if defined(CONFIG_MEDIA_CONTROLLER) media_entity_cleanup(&decoder->sd.entity); diff --git a/drivers/media/i2c/tvp7002.c b/drivers/media/i2c/tvp7002.c index a4e49483de6a..24a08fa7e328 100644 --- a/drivers/media/i2c/tvp7002.c +++ b/drivers/media/i2c/tvp7002.c @@ -31,9 +31,12 @@ #include <linux/module.h> #include <linux/v4l2-dv-timings.h> #include <media/tvp7002.h> +#include <media/v4l2-async.h> #include <media/v4l2-device.h> #include <media/v4l2-common.h> #include <media/v4l2-ctrls.h> +#include <media/v4l2-of.h> + #include "tvp7002_reg.h" MODULE_DESCRIPTION("TI TVP7002 Video and Graphics Digitizer driver"); @@ -942,6 +945,48 @@ static const struct v4l2_subdev_ops tvp7002_ops = { .pad = &tvp7002_pad_ops, }; +static struct tvp7002_config * +tvp7002_get_pdata(struct i2c_client *client) +{ + struct v4l2_of_endpoint bus_cfg; + struct tvp7002_config *pdata; + struct device_node *endpoint; + unsigned int flags; + + if (!IS_ENABLED(CONFIG_OF) || !client->dev.of_node) + return client->dev.platform_data; + + endpoint = v4l2_of_get_next_endpoint(client->dev.of_node, NULL); + if (!endpoint) + return NULL; + + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + goto done; + + v4l2_of_parse_endpoint(endpoint, &bus_cfg); + flags = bus_cfg.bus.parallel.flags; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + pdata->hs_polarity = 1; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) + pdata->vs_polarity = 1; + + if (flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + pdata->clk_polarity = 1; + + if (flags & V4L2_MBUS_FIELD_EVEN_HIGH) + pdata->fid_polarity = 1; + + if (flags & V4L2_MBUS_VIDEO_SOG_ACTIVE_HIGH) + pdata->sog_polarity = 1; + +done: + of_node_put(endpoint); + return pdata; +} + /* * tvp7002_probe - Probe a TVP7002 device * @c: ptr to i2c_client struct @@ -953,32 +998,32 @@ static const struct v4l2_subdev_ops tvp7002_ops = { */ static int tvp7002_probe(struct i2c_client *c, const struct i2c_device_id *id) { + struct tvp7002_config *pdata = tvp7002_get_pdata(c); struct v4l2_subdev *sd; struct tvp7002 *device; struct v4l2_dv_timings timings; int polarity_a; int polarity_b; u8 revision; - int error; + if (pdata == NULL) { + dev_err(&c->dev, "No platform data\n"); + return -EINVAL; + } + /* Check if the adapter supports the needed features */ if (!i2c_check_functionality(c->adapter, I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) return -EIO; - if (!c->dev.platform_data) { - v4l_err(c, "No platform data!!\n"); - return -ENODEV; - } - device = devm_kzalloc(&c->dev, sizeof(struct tvp7002), GFP_KERNEL); if (!device) return -ENOMEM; sd = &device->sd; - device->pdata = c->dev.platform_data; + device->pdata = pdata; device->current_timings = tvp7002_timings; /* Tell v4l2 the device is ready */ @@ -1039,6 +1084,10 @@ static int tvp7002_probe(struct i2c_client *c, const struct i2c_device_id *id) } v4l2_ctrl_handler_setup(&device->hdl); + error = v4l2_async_register_subdev(&device->sd); + if (error) + goto error; + return 0; error: @@ -1063,6 +1112,7 @@ static int tvp7002_remove(struct i2c_client *c) v4l2_dbg(1, debug, sd, "Removing tvp7002 adapter" "on address 0x%x\n", c->addr); + v4l2_async_unregister_subdev(&device->sd); #if defined(CONFIG_MEDIA_CONTROLLER) media_entity_cleanup(&device->sd.entity); #endif @@ -1078,9 +1128,18 @@ static const struct i2c_device_id tvp7002_id[] = { }; MODULE_DEVICE_TABLE(i2c, tvp7002_id); +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id tvp7002_of_match[] = { + { .compatible = "ti,tvp7002", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, tvp7002_of_match); +#endif + /* I2C driver data */ static struct i2c_driver tvp7002_driver = { .driver = { + .of_match_table = of_match_ptr(tvp7002_of_match), .owner = THIS_MODULE, .name = TVP7002_MODULE_NAME, }, diff --git a/drivers/media/media-entity.c b/drivers/media/media-entity.c index cb30ffbd5ba8..2c286c307145 100644 --- a/drivers/media/media-entity.c +++ b/drivers/media/media-entity.c @@ -20,6 +20,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include <linux/bitmap.h> #include <linux/module.h> #include <linux/slab.h> #include <media/media-entity.h> @@ -121,7 +122,6 @@ static struct media_entity *stack_pop(struct media_entity_graph *graph) return entity; } -#define stack_peek(en) ((en)->stack[(en)->top - 1].entity) #define link_top(en) ((en)->stack[(en)->top].link) #define stack_top(en) ((en)->stack[(en)->top].entity) @@ -140,6 +140,12 @@ void media_entity_graph_walk_start(struct media_entity_graph *graph, { graph->top = 0; graph->stack[graph->top].entity = NULL; + bitmap_zero(graph->entities, MEDIA_ENTITY_ENUM_MAX_ID); + + if (WARN_ON(entity->id >= MEDIA_ENTITY_ENUM_MAX_ID)) + return; + + __set_bit(entity->id, graph->entities); stack_push(graph, entity); } EXPORT_SYMBOL_GPL(media_entity_graph_walk_start); @@ -180,9 +186,11 @@ media_entity_graph_walk_next(struct media_entity_graph *graph) /* Get the entity in the other end of the link . */ next = media_entity_other(entity, link); + if (WARN_ON(next->id >= MEDIA_ENTITY_ENUM_MAX_ID)) + return NULL; - /* Was it the entity we came here from? */ - if (next == stack_peek(graph)) { + /* Has the entity already been visited? */ + if (__test_and_set_bit(next->id, graph->entities)) { link_top(graph)++; continue; } diff --git a/drivers/media/pci/bt8xx/bttv-cards.c b/drivers/media/pci/bt8xx/bttv-cards.c index e564aac0aa30..d85cb0ace4dc 100644 --- a/drivers/media/pci/bt8xx/bttv-cards.c +++ b/drivers/media/pci/bt8xx/bttv-cards.c @@ -4441,9 +4441,7 @@ static void tibetCS16_init(struct bttv *btv) * is {3, 0, 2, 1}, i.e. the first controller to be detected is logical * unit 3, the second (which is the master) is logical unit 0, etc. * We need to maintain the status of the analog switch (which of the 16 - * cameras is connected to which of the 4 controllers). Rather than - * add to the bttv structure for this, we use the data reserved for - * the mbox (unused for this card type). + * cameras is connected to which of the 4 controllers) in sw_status array. */ /* @@ -4478,7 +4476,6 @@ static void kodicom4400r_write(struct bttv *btv, */ static void kodicom4400r_muxsel(struct bttv *btv, unsigned int input) { - char *sw_status; int xaddr, yaddr; struct bttv *mctlr; static unsigned char map[4] = {3, 0, 2, 1}; @@ -4489,14 +4486,13 @@ static void kodicom4400r_muxsel(struct bttv *btv, unsigned int input) } yaddr = (btv->c.nr - mctlr->c.nr + 1) & 3; /* the '&' is for safety */ yaddr = map[yaddr]; - sw_status = (char *)(&mctlr->mbox_we); xaddr = input & 0xf; /* Check if the controller/camera pair has changed, else ignore */ - if (sw_status[yaddr] != xaddr) + if (mctlr->sw_status[yaddr] != xaddr) { /* "open" the old switch, "close" the new one, save the new */ - kodicom4400r_write(mctlr, sw_status[yaddr], yaddr, 0); - sw_status[yaddr] = xaddr; + kodicom4400r_write(mctlr, mctlr->sw_status[yaddr], yaddr, 0); + mctlr->sw_status[yaddr] = xaddr; kodicom4400r_write(mctlr, xaddr, yaddr, 1); } } @@ -4509,7 +4505,6 @@ static void kodicom4400r_muxsel(struct bttv *btv, unsigned int input) */ static void kodicom4400r_init(struct bttv *btv) { - char *sw_status = (char *)(&btv->mbox_we); int ix; gpio_inout(0x0003ff, 0x0003ff); @@ -4517,7 +4512,7 @@ static void kodicom4400r_init(struct bttv *btv) gpio_write(0); /* Preset camera 0 to the 4 controllers */ for (ix = 0; ix < 4; ix++) { - sw_status[ix] = ix; + btv->sw_status[ix] = ix; kodicom4400r_write(btv, ix, ix, 1); } /* @@ -4794,7 +4789,6 @@ static void gv800s_write(struct bttv *btv, static void gv800s_muxsel(struct bttv *btv, unsigned int input) { struct bttv *mctlr; - char *sw_status; int xaddr, yaddr; static unsigned int map[4][4] = { { 0x0, 0x4, 0xa, 0x6 }, { 0x1, 0x5, 0xb, 0x7 }, @@ -4807,14 +4801,13 @@ static void gv800s_muxsel(struct bttv *btv, unsigned int input) return; } yaddr = (btv->c.nr - mctlr->c.nr) & 3; - sw_status = (char *)(&mctlr->mbox_we); xaddr = map[yaddr][input] & 0xf; /* Check if the controller/camera pair has changed, ignore otherwise */ - if (sw_status[yaddr] != xaddr) { + if (mctlr->sw_status[yaddr] != xaddr) { /* disable the old switch, enable the new one and save status */ - gv800s_write(mctlr, sw_status[yaddr], yaddr, 0); - sw_status[yaddr] = xaddr; + gv800s_write(mctlr, mctlr->sw_status[yaddr], yaddr, 0); + mctlr->sw_status[yaddr] = xaddr; gv800s_write(mctlr, xaddr, yaddr, 1); } } @@ -4822,7 +4815,6 @@ static void gv800s_muxsel(struct bttv *btv, unsigned int input) /* GeoVision GV-800(S) "master" chip init */ static void gv800s_init(struct bttv *btv) { - char *sw_status = (char *)(&btv->mbox_we); int ix; gpio_inout(0xf107f, 0xf107f); @@ -4831,7 +4823,7 @@ static void gv800s_init(struct bttv *btv) /* Preset camera 0 to the 4 controllers */ for (ix = 0; ix < 4; ix++) { - sw_status[ix] = ix; + btv->sw_status[ix] = ix; gv800s_write(btv, ix, ix, 1); } diff --git a/drivers/media/pci/bt8xx/bttvp.h b/drivers/media/pci/bt8xx/bttvp.h index 9c1cc2c50ee2..6eefb595d0fa 100644 --- a/drivers/media/pci/bt8xx/bttvp.h +++ b/drivers/media/pci/bt8xx/bttvp.h @@ -459,6 +459,9 @@ struct bttv { int mbox_iow; int mbox_csel; + /* switch status for multi-controller cards */ + char sw_status[4]; + /* risc memory management data - must acquire s_lock before changing these - only the irq handler is supported to touch top + bottom + vcurr */ diff --git a/drivers/media/pci/cx23885/Kconfig b/drivers/media/pci/cx23885/Kconfig index b3688aa8acc3..5104c802f72f 100644 --- a/drivers/media/pci/cx23885/Kconfig +++ b/drivers/media/pci/cx23885/Kconfig @@ -29,6 +29,7 @@ config VIDEO_CX23885 select DVB_STV0367 if MEDIA_SUBDRV_AUTOSELECT select DVB_TDA10071 if MEDIA_SUBDRV_AUTOSELECT select DVB_A8293 if MEDIA_SUBDRV_AUTOSELECT + select DVB_MB86A20S if MEDIA_SUBDRV_AUTOSELECT select MEDIA_TUNER_MT2063 if MEDIA_SUBDRV_AUTOSELECT select MEDIA_TUNER_MT2131 if MEDIA_SUBDRV_AUTOSELECT select MEDIA_TUNER_XC2028 if MEDIA_SUBDRV_AUTOSELECT diff --git a/drivers/media/pci/cx23885/cx23885-av.c b/drivers/media/pci/cx23885/cx23885-av.c index e958a01fd554..c443b7ac5adf 100644 --- a/drivers/media/pci/cx23885/cx23885-av.c +++ b/drivers/media/pci/cx23885/cx23885-av.c @@ -23,6 +23,7 @@ #include "cx23885.h" #include "cx23885-av.h" +#include "cx23885-video.h" void cx23885_av_work_handler(struct work_struct *work) { @@ -32,5 +33,17 @@ void cx23885_av_work_handler(struct work_struct *work) v4l2_subdev_call(dev->sd_cx25840, core, interrupt_service_routine, PCI_MSK_AV_CORE, &handled); + + /* Getting here with the interrupt not handled + then probbaly flatiron does have pending interrupts. + */ + if (!handled) { + /* clear left and right adc channel interrupt request flag */ + cx23885_flatiron_write(dev, 0x1f, + cx23885_flatiron_read(dev, 0x1f) | 0x80); + cx23885_flatiron_write(dev, 0x23, + cx23885_flatiron_read(dev, 0x23) | 0x80); + } + cx23885_irq_enable(dev, PCI_MSK_AV_CORE); } diff --git a/drivers/media/pci/cx23885/cx23885-cards.c b/drivers/media/pci/cx23885/cx23885-cards.c index 7e923f8dd2f5..6a71a965e757 100644 --- a/drivers/media/pci/cx23885/cx23885-cards.c +++ b/drivers/media/pci/cx23885/cx23885-cards.c @@ -528,11 +528,12 @@ struct cx23885_board cx23885_boards[] = { } }, }, [CX23885_BOARD_MYGICA_X8507] = { - .name = "Mygica X8507", + .name = "Mygica X8502/X8507 ISDB-T", .tuner_type = TUNER_XC5000, .tuner_addr = 0x61, .tuner_bus = 1, .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_DVB, .input = { { .type = CX23885_VMUX_TELEVISION, @@ -1281,7 +1282,7 @@ void cx23885_gpio_setup(struct cx23885_dev *dev) case CX23885_BOARD_MYGICA_X8507: /* GPIO-0 (0)Analog / (1)Digital TV */ /* GPIO-1 reset XC5000 */ - /* GPIO-2 reset LGS8GL5 / LGS8G75 */ + /* GPIO-2 demod reset */ cx23885_gpio_enable(dev, GPIO_0 | GPIO_1 | GPIO_2, 1); cx23885_gpio_clear(dev, GPIO_1 | GPIO_2); mdelay(100); @@ -1677,6 +1678,7 @@ void cx23885_card_setup(struct cx23885_dev *dev) break; case CX23885_BOARD_MYGICA_X8506: case CX23885_BOARD_MAGICPRO_PROHDTVE2: + case CX23885_BOARD_MYGICA_X8507: ts1->gen_ctrl_val = 0x5; /* Parallel */ ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; diff --git a/drivers/media/pci/cx23885/cx23885-core.c b/drivers/media/pci/cx23885/cx23885-core.c index 268654ac9a9f..9f63d93239ec 100644 --- a/drivers/media/pci/cx23885/cx23885-core.c +++ b/drivers/media/pci/cx23885/cx23885-core.c @@ -1941,10 +1941,7 @@ static irqreturn_t cx23885_irq(int irq, void *dev_id) if ((pci_status & pci_mask) & PCI_MSK_AV_CORE) { cx23885_irq_disable(dev, PCI_MSK_AV_CORE); - if (!schedule_work(&dev->cx25840_work)) - printk(KERN_ERR "%s: failed to set up deferred work for" - " AV Core/IR interrupt. Interrupt is disabled" - " and won't be re-enabled\n", dev->name); + schedule_work(&dev->cx25840_work); handled++; } diff --git a/drivers/media/pci/cx23885/cx23885-dvb.c b/drivers/media/pci/cx23885/cx23885-dvb.c index 9c5ed10b2c5e..971e4ff1b87f 100644 --- a/drivers/media/pci/cx23885/cx23885-dvb.c +++ b/drivers/media/pci/cx23885/cx23885-dvb.c @@ -69,6 +69,7 @@ #include "stb6100_cfg.h" #include "tda10071.h" #include "a8293.h" +#include "mb86a20s.h" static unsigned int debug; @@ -119,8 +120,6 @@ static void dvb_buf_release(struct videobuf_queue *q, cx23885_free_buffer(q, (struct cx23885_buffer *)vb); } -static int cx23885_dvb_set_frontend(struct dvb_frontend *fe); - static void cx23885_dvb_gate_ctrl(struct cx23885_tsport *port, int open) { struct videobuf_dvb_frontends *f; @@ -135,12 +134,6 @@ static void cx23885_dvb_gate_ctrl(struct cx23885_tsport *port, int open) if (fe && fe->dvb.frontend && fe->dvb.frontend->ops.i2c_gate_ctrl) fe->dvb.frontend->ops.i2c_gate_ctrl(fe->dvb.frontend, open); - - /* - * FIXME: Improve this path to avoid calling the - * cx23885_dvb_set_frontend() every time it passes here. - */ - cx23885_dvb_set_frontend(fe->dvb.frontend); } static struct videobuf_queue_ops dvb_qops = { @@ -500,6 +493,15 @@ static struct xc5000_config mygica_x8506_xc5000_config = { .if_khz = 5380, }; +static struct mb86a20s_config mygica_x8507_mb86a20s_config = { + .demod_address = 0x10, +}; + +static struct xc5000_config mygica_x8507_xc5000_config = { + .i2c_address = 0x61, + .if_khz = 4000, +}; + static struct stv090x_config prof_8000_stv090x_config = { .device = STV0903, .demod_mode = STV090x_SINGLE, @@ -556,14 +558,27 @@ static int cx23885_dvb_set_frontend(struct dvb_frontend *fe) } break; case CX23885_BOARD_MYGICA_X8506: + case CX23885_BOARD_MYGICA_X8507: case CX23885_BOARD_MAGICPRO_PROHDTVE2: /* Select Digital TV */ cx23885_gpio_set(dev, GPIO_0); break; } + + /* Call the real set_frontend */ + if (port->set_frontend) + return port->set_frontend(fe); + return 0; } +static void cx23885_set_frontend_hook(struct cx23885_tsport *port, + struct dvb_frontend *fe) +{ + port->set_frontend = fe->ops.set_frontend; + fe->ops.set_frontend = cx23885_dvb_set_frontend; +} + static struct lgs8gxx_config magicpro_prohdtve2_lgs8g75_config = { .prod = LGS8GXX_PROD_LGS8G75, .demod_address = 0x19, @@ -771,6 +786,8 @@ static int dvb_register(struct cx23885_tsport *port) 0x60, &dev->i2c_bus[1].i2c_adap, &hauppauge_hvr127x_config); } + if (dev->board == CX23885_BOARD_HAUPPAUGE_HVR1275) + cx23885_set_frontend_hook(port, fe0->dvb.frontend); break; case CX23885_BOARD_HAUPPAUGE_HVR1255: case CX23885_BOARD_HAUPPAUGE_HVR1255_22111: @@ -1106,6 +1123,21 @@ static int dvb_register(struct cx23885_tsport *port) &i2c_bus2->i2c_adap, &mygica_x8506_xc5000_config); } + cx23885_set_frontend_hook(port, fe0->dvb.frontend); + break; + case CX23885_BOARD_MYGICA_X8507: + i2c_bus = &dev->i2c_bus[0]; + i2c_bus2 = &dev->i2c_bus[1]; + fe0->dvb.frontend = dvb_attach(mb86a20s_attach, + &mygica_x8507_mb86a20s_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(xc5000_attach, + fe0->dvb.frontend, + &i2c_bus2->i2c_adap, + &mygica_x8507_xc5000_config); + } + cx23885_set_frontend_hook(port, fe0->dvb.frontend); break; case CX23885_BOARD_MAGICPRO_PROHDTVE2: i2c_bus = &dev->i2c_bus[0]; @@ -1119,6 +1151,7 @@ static int dvb_register(struct cx23885_tsport *port) &i2c_bus2->i2c_adap, &magicpro_prohdtve2_xc5000_config); } + cx23885_set_frontend_hook(port, fe0->dvb.frontend); break; case CX23885_BOARD_HAUPPAUGE_HVR1850: i2c_bus = &dev->i2c_bus[0]; @@ -1249,6 +1282,10 @@ static int dvb_register(struct cx23885_tsport *port) fe0->dvb.frontend = dvb_attach(ds3000_attach, &tevii_ds3000_config, &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(ts2020_attach, fe0->dvb.frontend, + &tevii_ts2020_config, &i2c_bus->i2c_adap); + } break; case CX23885_BOARD_PROF_8000: i2c_bus = &dev->i2c_bus[0]; diff --git a/drivers/media/pci/cx23885/cx23885-video.c b/drivers/media/pci/cx23885/cx23885-video.c index e33d1a7dfdd0..161686832b20 100644 --- a/drivers/media/pci/cx23885/cx23885-video.c +++ b/drivers/media/pci/cx23885/cx23885-video.c @@ -32,6 +32,7 @@ #include <asm/div64.h> #include "cx23885.h" +#include "cx23885-video.h" #include <media/v4l2-common.h> #include <media/v4l2-ioctl.h> #include "cx23885-ioctl.h" @@ -417,7 +418,7 @@ static void res_free(struct cx23885_dev *dev, struct cx23885_fh *fh, mutex_unlock(&dev->lock); } -static int cx23885_flatiron_write(struct cx23885_dev *dev, u8 reg, u8 data) +int cx23885_flatiron_write(struct cx23885_dev *dev, u8 reg, u8 data) { /* 8 bit registers, 8 bit values */ u8 buf[] = { reg, data }; @@ -428,7 +429,7 @@ static int cx23885_flatiron_write(struct cx23885_dev *dev, u8 reg, u8 data) return i2c_transfer(&dev->i2c_bus[2].i2c_adap, &msg, 1); } -static u8 cx23885_flatiron_read(struct cx23885_dev *dev, u8 reg) +u8 cx23885_flatiron_read(struct cx23885_dev *dev, u8 reg) { /* 8 bit registers, 8 bit values */ int ret; diff --git a/drivers/media/pci/cx23885/cx23885-video.h b/drivers/media/pci/cx23885/cx23885-video.h new file mode 100644 index 000000000000..c961a2b0de0f --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-video.h @@ -0,0 +1,26 @@ +/* + * Driver for the Conexant CX23885/7/8 PCIe bridge + * + * Copyright (C) 2010 Andy Walls <awalls@md.metrocast.net> + * + * 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. + */ + +#ifndef _CX23885_VIDEO_H_ +#define _CX23885_VIDEO_H_ +int cx23885_flatiron_write(struct cx23885_dev *dev, u8 reg, u8 data); +u8 cx23885_flatiron_read(struct cx23885_dev *dev, u8 reg); +#endif diff --git a/drivers/media/pci/cx23885/cx23885.h b/drivers/media/pci/cx23885/cx23885.h index 5687d3f678db..038caf53908b 100644 --- a/drivers/media/pci/cx23885/cx23885.h +++ b/drivers/media/pci/cx23885/cx23885.h @@ -320,6 +320,8 @@ struct cx23885_tsport { /* Workaround for a temp dvb_frontend that the tuner can attached to */ struct dvb_frontend analog_fe; + + int (*set_frontend)(struct dvb_frontend *fe); }; struct cx23885_kernel_ir { diff --git a/drivers/media/pci/cx88/Kconfig b/drivers/media/pci/cx88/Kconfig index bb05eca2da29..a63a9ad163b2 100644 --- a/drivers/media/pci/cx88/Kconfig +++ b/drivers/media/pci/cx88/Kconfig @@ -72,9 +72,9 @@ config VIDEO_CX88_DVB To compile this driver as a module, choose M here: the module will be called cx88-dvb. -config VIDEO_CX88_VP3054 - tristate "VP-3054 Secondary I2C Bus Support" - default m +config VIDEO_CX88_ENABLE_VP3054 + bool "VP-3054 Secondary I2C Bus Support" + default y depends on VIDEO_CX88_DVB && DVB_MT352 ---help--- This adds DVB-T support for cards based on the @@ -82,6 +82,11 @@ config VIDEO_CX88_VP3054 which also require support for the VP-3054 Secondary I2C bus, such at DNTV Live! DVB-T Pro. +config VIDEO_CX88_VP3054 + tristate + depends on VIDEO_CX88_DVB && VIDEO_CX88_ENABLE_VP3054 + default y + config VIDEO_CX88_MPEG tristate depends on VIDEO_CX88_DVB || VIDEO_CX88_BLACKBIRD diff --git a/drivers/media/pci/cx88/cx88.h b/drivers/media/pci/cx88/cx88.h index afe0eaea81b4..28893a6b249e 100644 --- a/drivers/media/pci/cx88/cx88.h +++ b/drivers/media/pci/cx88/cx88.h @@ -259,7 +259,7 @@ struct cx88_input { }; enum cx88_audio_chip { - CX88_AUDIO_WM8775, + CX88_AUDIO_WM8775 = 1, CX88_AUDIO_TVAUDIO, }; diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 08de865cc399..8068d7b64155 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -203,13 +203,23 @@ config VIDEO_SAMSUNG_EXYNOS_GSC config VIDEO_SH_VEU tristate "SuperH VEU mem2mem video processing driver" - depends on VIDEO_DEV && VIDEO_V4L2 && GENERIC_HARDIRQS + depends on VIDEO_DEV && VIDEO_V4L2 && GENERIC_HARDIRQS && HAS_DMA select VIDEOBUF2_DMA_CONTIG select V4L2_MEM2MEM_DEV help Support for the Video Engine Unit (VEU) on SuperH and SH-Mobile SoCs. +config VIDEO_RENESAS_VSP1 + tristate "Renesas VSP1 Video Processing Engine" + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + ---help--- + This is a V4L2 driver for the Renesas VSP1 video processing engine. + + To compile this driver as a module, choose M here: the module + will be called vsp1. + endif # V4L_MEM2MEM_DRIVERS menuconfig V4L_TEST_DRIVERS diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index eee28dd78d7d..4e4da482c522 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -46,6 +46,8 @@ obj-$(CONFIG_VIDEO_SH_VOU) += sh_vou.o obj-$(CONFIG_SOC_CAMERA) += soc_camera/ +obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1/ + obj-y += davinci/ obj-$(CONFIG_ARCH_OMAP) += omap/ diff --git a/drivers/media/platform/blackfin/bfin_capture.c b/drivers/media/platform/blackfin/bfin_capture.c index 7f838c681cea..4c1105977090 100644 --- a/drivers/media/platform/blackfin/bfin_capture.c +++ b/drivers/media/platform/blackfin/bfin_capture.c @@ -388,13 +388,8 @@ static int bcap_start_streaming(struct vb2_queue *vq, unsigned int count) params.hdelay = bt->hsync + bt->hbackporch; params.vdelay = bt->vsync + bt->vbackporch; - params.line = bt->hfrontporch + bt->hsync - + bt->hbackporch + bt->width; - params.frame = bt->vfrontporch + bt->vsync - + bt->vbackporch + bt->height; - if (bt->interlaced) - params.frame += bt->il_vfrontporch + bt->il_vsync - + bt->il_vbackporch; + params.line = V4L2_DV_BT_FRAME_WIDTH(bt); + params.frame = V4L2_DV_BT_FRAME_HEIGHT(bt); } else if (bcap_dev->cfg->inputs[bcap_dev->cur_input].capabilities & V4L2_IN_CAP_STD) { params.hdelay = 0; diff --git a/drivers/media/platform/coda.c b/drivers/media/platform/coda.c index bd9405df1bd6..449d2fec9e87 100644 --- a/drivers/media/platform/coda.c +++ b/drivers/media/platform/coda.c @@ -18,6 +18,7 @@ #include <linux/interrupt.h> #include <linux/io.h> #include <linux/irq.h> +#include <linux/kfifo.h> #include <linux/module.h> #include <linux/of_device.h> #include <linux/platform_device.h> @@ -28,6 +29,7 @@ #include <media/v4l2-ctrls.h> #include <media/v4l2-device.h> +#include <media/v4l2-event.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-mem2mem.h> #include <media/videobuf2-core.h> @@ -41,13 +43,16 @@ #define CODA_FMO_BUF_SIZE 32 #define CODADX6_WORK_BUF_SIZE (288 * 1024 + CODA_FMO_BUF_SIZE * 8 * 1024) -#define CODA7_WORK_BUF_SIZE (512 * 1024 + CODA_FMO_BUF_SIZE * 8 * 1024) +#define CODA7_WORK_BUF_SIZE (128 * 1024) +#define CODA7_TEMP_BUF_SIZE (304 * 1024) #define CODA_PARA_BUF_SIZE (10 * 1024) #define CODA_ISRAM_SIZE (2048 * 2) #define CODADX6_IRAM_SIZE 0xb000 -#define CODA7_IRAM_SIZE 0x14000 /* 81920 bytes */ +#define CODA7_IRAM_SIZE 0x14000 -#define CODA_MAX_FRAMEBUFFERS 2 +#define CODA7_PS_BUF_SIZE 0x28000 + +#define CODA_MAX_FRAMEBUFFERS 8 #define MAX_W 8192 #define MAX_H 8192 @@ -129,6 +134,7 @@ struct coda_dev { struct clk *clk_ahb; struct coda_aux_buf codebuf; + struct coda_aux_buf tempbuf; struct coda_aux_buf workbuf; struct gen_pool *iram_pool; long unsigned int iram_vaddr; @@ -153,6 +159,7 @@ struct coda_params { u8 mpeg4_inter_qp; u8 gop_size; int codec_mode; + int codec_mode_aux; enum v4l2_mpeg_video_multi_slice_mode slice_mode; u32 framerate; u16 bitrate; @@ -160,13 +167,30 @@ struct coda_params { u32 slice_max_mb; }; +struct coda_iram_info { + u32 axi_sram_use; + phys_addr_t buf_bit_use; + phys_addr_t buf_ip_ac_dc_use; + phys_addr_t buf_dbk_y_use; + phys_addr_t buf_dbk_c_use; + phys_addr_t buf_ovl_use; + phys_addr_t buf_btp_use; + phys_addr_t search_ram_paddr; + int search_ram_size; +}; + struct coda_ctx { struct coda_dev *dev; + struct mutex buffer_mutex; struct list_head list; + struct work_struct skip_run; int aborting; + int initialized; int streamon_out; int streamon_cap; u32 isequence; + u32 qsequence; + u32 osequence; struct coda_q_data q_data[2]; enum coda_inst_type inst_type; struct coda_codec *codec; @@ -176,12 +200,25 @@ struct coda_ctx { struct v4l2_ctrl_handler ctrls; struct v4l2_fh fh; int gopcounter; + int runcounter; char vpu_header[3][64]; int vpu_header_size[3]; + struct kfifo bitstream_fifo; + struct mutex bitstream_mutex; + struct coda_aux_buf bitstream; + bool prescan_failed; struct coda_aux_buf parabuf; + struct coda_aux_buf psbuf; + struct coda_aux_buf slicebuf; struct coda_aux_buf internal_frames[CODA_MAX_FRAMEBUFFERS]; + struct coda_aux_buf workbuf; int num_internal_frames; int idx; + int reg_idx; + struct coda_iram_info iram_info; + u32 bit_stream_param; + u32 frm_dis_flg; + int display_idx; }; static const u8 coda_filler_nal[14] = { 0x00, 0x00, 0x00, 0x01, 0x0c, 0xff, @@ -228,10 +265,22 @@ static int coda_wait_timeout(struct coda_dev *dev) static void coda_command_async(struct coda_ctx *ctx, int cmd) { struct coda_dev *dev = ctx->dev; + + if (dev->devtype->product == CODA_7541) { + /* Restore context related registers to CODA */ + coda_write(dev, ctx->bit_stream_param, + CODA_REG_BIT_BIT_STREAM_PARAM); + coda_write(dev, ctx->frm_dis_flg, + CODA_REG_BIT_FRM_DIS_FLG(ctx->reg_idx)); + coda_write(dev, ctx->workbuf.paddr, CODA_REG_BIT_WORK_BUF_ADDR); + } + coda_write(dev, CODA_REG_BIT_BUSY_FLAG, CODA_REG_BIT_BUSY); coda_write(dev, ctx->idx, CODA_REG_BIT_RUN_INDEX); coda_write(dev, ctx->params.codec_mode, CODA_REG_BIT_RUN_COD_STD); + coda_write(dev, ctx->params.codec_mode_aux, CODA7_REG_BIT_RUN_AUX_STD); + coda_write(dev, cmd, CODA_REG_BIT_RUN_COMMAND); } @@ -297,6 +346,8 @@ static struct coda_codec codadx6_codecs[] = { static struct coda_codec coda7_codecs[] = { CODA_CODEC(CODA7_MODE_ENCODE_H264, V4L2_PIX_FMT_YUV420, V4L2_PIX_FMT_H264, 1280, 720), CODA_CODEC(CODA7_MODE_ENCODE_MP4, V4L2_PIX_FMT_YUV420, V4L2_PIX_FMT_MPEG4, 1280, 720), + CODA_CODEC(CODA7_MODE_DECODE_H264, V4L2_PIX_FMT_H264, V4L2_PIX_FMT_YUV420, 1920, 1080), + CODA_CODEC(CODA7_MODE_DECODE_MP4, V4L2_PIX_FMT_MPEG4, V4L2_PIX_FMT_YUV420, 1920, 1080), }; static bool coda_format_is_yuv(u32 fourcc) @@ -365,7 +416,7 @@ static int vidioc_querycap(struct file *file, void *priv, } static int enum_fmt(void *priv, struct v4l2_fmtdesc *f, - enum v4l2_buf_type type) + enum v4l2_buf_type type, int src_fourcc) { struct coda_ctx *ctx = fh_to_ctx(priv); struct coda_codec *codecs = ctx->dev->devtype->codecs; @@ -377,7 +428,8 @@ static int enum_fmt(void *priv, struct v4l2_fmtdesc *f, for (i = 0; i < num_formats; i++) { /* Both uncompressed formats are always supported */ - if (coda_format_is_yuv(formats[i].fourcc)) { + if (coda_format_is_yuv(formats[i].fourcc) && + !coda_format_is_yuv(src_fourcc)) { if (num == f->index) break; ++num; @@ -385,8 +437,10 @@ static int enum_fmt(void *priv, struct v4l2_fmtdesc *f, } /* Compressed formats may be supported, check the codec list */ for (k = 0; k < num_codecs; k++) { + /* if src_fourcc is set, only consider matching codecs */ if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE && - formats[i].fourcc == codecs[k].dst_fourcc) + formats[i].fourcc == codecs[k].dst_fourcc && + (!src_fourcc || src_fourcc == codecs[k].src_fourcc)) break; if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT && formats[i].fourcc == codecs[k].src_fourcc) @@ -413,13 +467,26 @@ static int enum_fmt(void *priv, struct v4l2_fmtdesc *f, static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f) { - return enum_fmt(priv, f, V4L2_BUF_TYPE_VIDEO_CAPTURE); + struct coda_ctx *ctx = fh_to_ctx(priv); + struct vb2_queue *src_vq; + struct coda_q_data *q_data_src; + + /* If the source format is already fixed, only list matching formats */ + src_vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + if (vb2_is_streaming(src_vq)) { + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + + return enum_fmt(priv, f, V4L2_BUF_TYPE_VIDEO_CAPTURE, + q_data_src->fourcc); + } + + return enum_fmt(priv, f, V4L2_BUF_TYPE_VIDEO_CAPTURE, 0); } static int vidioc_enum_fmt_vid_out(struct file *file, void *priv, struct v4l2_fmtdesc *f) { - return enum_fmt(priv, f, V4L2_BUF_TYPE_VIDEO_OUTPUT); + return enum_fmt(priv, f, V4L2_BUF_TYPE_VIDEO_OUTPUT, 0); } static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f) @@ -492,15 +559,45 @@ static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct coda_ctx *ctx = fh_to_ctx(priv); - struct coda_codec *codec = NULL; + struct coda_codec *codec; + struct vb2_queue *src_vq; + int ret; - /* Determine codec by the encoded format */ - codec = coda_find_codec(ctx->dev, V4L2_PIX_FMT_YUV420, - f->fmt.pix.pixelformat); + /* + * If the source format is already fixed, try to find a codec that + * converts to the given destination format + */ + src_vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + if (vb2_is_streaming(src_vq)) { + struct coda_q_data *q_data_src; + + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + codec = coda_find_codec(ctx->dev, q_data_src->fourcc, + f->fmt.pix.pixelformat); + if (!codec) + return -EINVAL; + } else { + /* Otherwise determine codec by encoded format, if possible */ + codec = coda_find_codec(ctx->dev, V4L2_PIX_FMT_YUV420, + f->fmt.pix.pixelformat); + } f->fmt.pix.colorspace = ctx->colorspace; - return vidioc_try_fmt(codec, f); + ret = vidioc_try_fmt(codec, f); + if (ret < 0) + return ret; + + /* The h.264 decoder only returns complete 16x16 macroblocks */ + if (codec && codec->src_fourcc == V4L2_PIX_FMT_H264) { + f->fmt.pix.width = round_up(f->fmt.pix.width, 16); + f->fmt.pix.height = round_up(f->fmt.pix.height, 16); + f->fmt.pix.bytesperline = f->fmt.pix.width; + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * + f->fmt.pix.height * 3 / 2; + } + + return 0; } static int vidioc_try_fmt_vid_out(struct file *file, void *priv, @@ -610,11 +707,35 @@ static int vidioc_expbuf(struct file *file, void *priv, return v4l2_m2m_expbuf(file, ctx->m2m_ctx, eb); } +static bool coda_buf_is_end_of_stream(struct coda_ctx *ctx, + struct v4l2_buffer *buf) +{ + struct vb2_queue *src_vq; + + src_vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + + return ((ctx->bit_stream_param & CODA_BIT_STREAM_END_FLAG) && + (buf->sequence == (ctx->qsequence - 1))); +} + static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) { struct coda_ctx *ctx = fh_to_ctx(priv); + int ret; + + ret = v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); + + /* If this is the last capture buffer, emit an end-of-stream event */ + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && + coda_buf_is_end_of_stream(ctx, buf)) { + const struct v4l2_event eos_event = { + .type = V4L2_EVENT_EOS + }; - return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); + v4l2_event_queue_fh(&ctx->fh, &eos_event); + } + + return ret; } static int vidioc_create_bufs(struct file *file, void *priv, @@ -637,8 +758,53 @@ static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type type) { struct coda_ctx *ctx = fh_to_ctx(priv); + int ret; - return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); + /* + * This indirectly calls __vb2_queue_cancel, which dequeues all buffers. + * We therefore have to lock it against running hardware in this context, + * which still needs the buffers. + */ + mutex_lock(&ctx->buffer_mutex); + ret = v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); + mutex_unlock(&ctx->buffer_mutex); + + return ret; +} + +static int vidioc_decoder_cmd(struct file *file, void *fh, + struct v4l2_decoder_cmd *dc) +{ + struct coda_ctx *ctx = fh_to_ctx(fh); + + if (dc->cmd != V4L2_DEC_CMD_STOP) + return -EINVAL; + + if ((dc->flags & V4L2_DEC_CMD_STOP_TO_BLACK) || + (dc->flags & V4L2_DEC_CMD_STOP_IMMEDIATELY)) + return -EINVAL; + + if (dc->stop.pts != 0) + return -EINVAL; + + if (ctx->inst_type != CODA_INST_DECODER) + return -EINVAL; + + /* Set the strem-end flag on this context */ + ctx->bit_stream_param |= CODA_BIT_STREAM_END_FLAG; + + return 0; +} + +static int vidioc_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 0, NULL); + default: + return v4l2_ctrl_subscribe_event(fh, sub); + } } static const struct v4l2_ioctl_ops coda_ioctl_ops = { @@ -664,14 +830,206 @@ static const struct v4l2_ioctl_ops coda_ioctl_ops = { .vidioc_streamon = vidioc_streamon, .vidioc_streamoff = vidioc_streamoff, + + .vidioc_decoder_cmd = vidioc_decoder_cmd, + + .vidioc_subscribe_event = vidioc_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; +static int coda_start_decoding(struct coda_ctx *ctx); + +static void coda_skip_run(struct work_struct *work) +{ + struct coda_ctx *ctx = container_of(work, struct coda_ctx, skip_run); + + v4l2_m2m_job_finish(ctx->dev->m2m_dev, ctx->m2m_ctx); +} + +static inline int coda_get_bitstream_payload(struct coda_ctx *ctx) +{ + return kfifo_len(&ctx->bitstream_fifo); +} + +static void coda_kfifo_sync_from_device(struct coda_ctx *ctx) +{ + struct __kfifo *kfifo = &ctx->bitstream_fifo.kfifo; + struct coda_dev *dev = ctx->dev; + u32 rd_ptr; + + rd_ptr = coda_read(dev, CODA_REG_BIT_RD_PTR(ctx->reg_idx)); + kfifo->out = (kfifo->in & ~kfifo->mask) | + (rd_ptr - ctx->bitstream.paddr); + if (kfifo->out > kfifo->in) + kfifo->out -= kfifo->mask + 1; +} + +static void coda_kfifo_sync_to_device_full(struct coda_ctx *ctx) +{ + struct __kfifo *kfifo = &ctx->bitstream_fifo.kfifo; + struct coda_dev *dev = ctx->dev; + u32 rd_ptr, wr_ptr; + + rd_ptr = ctx->bitstream.paddr + (kfifo->out & kfifo->mask); + coda_write(dev, rd_ptr, CODA_REG_BIT_RD_PTR(ctx->reg_idx)); + wr_ptr = ctx->bitstream.paddr + (kfifo->in & kfifo->mask); + coda_write(dev, wr_ptr, CODA_REG_BIT_WR_PTR(ctx->reg_idx)); +} + +static void coda_kfifo_sync_to_device_write(struct coda_ctx *ctx) +{ + struct __kfifo *kfifo = &ctx->bitstream_fifo.kfifo; + struct coda_dev *dev = ctx->dev; + u32 wr_ptr; + + wr_ptr = ctx->bitstream.paddr + (kfifo->in & kfifo->mask); + coda_write(dev, wr_ptr, CODA_REG_BIT_WR_PTR(ctx->reg_idx)); +} + +static int coda_bitstream_queue(struct coda_ctx *ctx, struct vb2_buffer *src_buf) +{ + u32 src_size = vb2_get_plane_payload(src_buf, 0); + u32 n; + + n = kfifo_in(&ctx->bitstream_fifo, vb2_plane_vaddr(src_buf, 0), src_size); + if (n < src_size) + return -ENOSPC; + + dma_sync_single_for_device(&ctx->dev->plat_dev->dev, ctx->bitstream.paddr, + ctx->bitstream.size, DMA_TO_DEVICE); + + ctx->qsequence++; + + return 0; +} + +static bool coda_bitstream_try_queue(struct coda_ctx *ctx, + struct vb2_buffer *src_buf) +{ + int ret; + + if (coda_get_bitstream_payload(ctx) + + vb2_get_plane_payload(src_buf, 0) + 512 >= ctx->bitstream.size) + return false; + + if (vb2_plane_vaddr(src_buf, 0) == NULL) { + v4l2_err(&ctx->dev->v4l2_dev, "trying to queue empty buffer\n"); + return true; + } + + ret = coda_bitstream_queue(ctx, src_buf); + if (ret < 0) { + v4l2_err(&ctx->dev->v4l2_dev, "bitstream buffer overflow\n"); + return false; + } + /* Sync read pointer to device */ + if (ctx == v4l2_m2m_get_curr_priv(ctx->dev->m2m_dev)) + coda_kfifo_sync_to_device_write(ctx); + + ctx->prescan_failed = false; + + return true; +} + +static void coda_fill_bitstream(struct coda_ctx *ctx) +{ + struct vb2_buffer *src_buf; + + while (v4l2_m2m_num_src_bufs_ready(ctx->m2m_ctx) > 0) { + src_buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); + + if (coda_bitstream_try_queue(ctx, src_buf)) { + src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + } else { + break; + } + } +} + /* * Mem-to-mem operations. */ -static void coda_device_run(void *m2m_priv) +static int coda_prepare_decode(struct coda_ctx *ctx) +{ + struct vb2_buffer *dst_buf; + struct coda_dev *dev = ctx->dev; + struct coda_q_data *q_data_dst; + u32 stridey, height; + u32 picture_y, picture_cb, picture_cr; + + dst_buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + + if (ctx->params.rot_mode & CODA_ROT_90) { + stridey = q_data_dst->height; + height = q_data_dst->width; + } else { + stridey = q_data_dst->width; + height = q_data_dst->height; + } + + /* Try to copy source buffer contents into the bitstream ringbuffer */ + mutex_lock(&ctx->bitstream_mutex); + coda_fill_bitstream(ctx); + mutex_unlock(&ctx->bitstream_mutex); + + if (coda_get_bitstream_payload(ctx) < 512 && + (!(ctx->bit_stream_param & CODA_BIT_STREAM_END_FLAG))) { + v4l2_dbg(1, coda_debug, &dev->v4l2_dev, + "bitstream payload: %d, skipping\n", + coda_get_bitstream_payload(ctx)); + schedule_work(&ctx->skip_run); + return -EAGAIN; + } + + /* Run coda_start_decoding (again) if not yet initialized */ + if (!ctx->initialized) { + int ret = coda_start_decoding(ctx); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "failed to start decoding\n"); + schedule_work(&ctx->skip_run); + return -EAGAIN; + } else { + ctx->initialized = 1; + } + } + + /* Set rotator output */ + picture_y = vb2_dma_contig_plane_dma_addr(dst_buf, 0); + if (q_data_dst->fourcc == V4L2_PIX_FMT_YVU420) { + /* Switch Cr and Cb for YVU420 format */ + picture_cr = picture_y + stridey * height; + picture_cb = picture_cr + stridey / 2 * height / 2; + } else { + picture_cb = picture_y + stridey * height; + picture_cr = picture_cb + stridey / 2 * height / 2; + } + coda_write(dev, picture_y, CODA_CMD_DEC_PIC_ROT_ADDR_Y); + coda_write(dev, picture_cb, CODA_CMD_DEC_PIC_ROT_ADDR_CB); + coda_write(dev, picture_cr, CODA_CMD_DEC_PIC_ROT_ADDR_CR); + coda_write(dev, stridey, CODA_CMD_DEC_PIC_ROT_STRIDE); + coda_write(dev, CODA_ROT_MIR_ENABLE | ctx->params.rot_mode, + CODA_CMD_DEC_PIC_ROT_MODE); + + switch (dev->devtype->product) { + case CODA_DX6: + /* TBD */ + case CODA_7541: + coda_write(dev, CODA_PRE_SCAN_EN, CODA_CMD_DEC_PIC_OPTION); + break; + } + + coda_write(dev, 0, CODA_CMD_DEC_PIC_SKIP_NUM); + + coda_write(dev, 0, CODA_CMD_DEC_PIC_BB_START); + coda_write(dev, 0, CODA_CMD_DEC_PIC_START_BYTE); + + return 0; +} + +static void coda_prepare_encode(struct coda_ctx *ctx) { - struct coda_ctx *ctx = m2m_priv; struct coda_q_data *q_data_src, *q_data_dst; struct vb2_buffer *src_buf, *dst_buf; struct coda_dev *dev = ctx->dev; @@ -681,17 +1039,15 @@ static void coda_device_run(void *m2m_priv) u32 pic_stream_buffer_addr, pic_stream_buffer_size; u32 dst_fourcc; - mutex_lock(&dev->coda_mutex); - src_buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); dst_buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); dst_fourcc = q_data_dst->fourcc; - src_buf->v4l2_buf.sequence = ctx->isequence; - dst_buf->v4l2_buf.sequence = ctx->isequence; - ctx->isequence++; + src_buf->v4l2_buf.sequence = ctx->osequence; + dst_buf->v4l2_buf.sequence = ctx->osequence; + ctx->osequence++; /* * Workaround coda firmware BUG that only marks the first @@ -793,16 +1149,53 @@ static void coda_device_run(void *m2m_priv) coda_write(dev, pic_stream_buffer_addr, CODA_CMD_ENC_PIC_BB_START); coda_write(dev, pic_stream_buffer_size / 1024, CODA_CMD_ENC_PIC_BB_SIZE); +} - if (dev->devtype->product == CODA_7541) { - coda_write(dev, CODA7_USE_BIT_ENABLE | CODA7_USE_HOST_BIT_ENABLE | - CODA7_USE_ME_ENABLE | CODA7_USE_HOST_ME_ENABLE, - CODA7_REG_BIT_AXI_SRAM_USE); +static void coda_device_run(void *m2m_priv) +{ + struct coda_ctx *ctx = m2m_priv; + struct coda_dev *dev = ctx->dev; + int ret; + + mutex_lock(&ctx->buffer_mutex); + + /* + * If streamoff dequeued all buffers before we could get the lock, + * just bail out immediately. + */ + if ((!v4l2_m2m_num_src_bufs_ready(ctx->m2m_ctx) && + ctx->inst_type != CODA_INST_DECODER) || + !v4l2_m2m_num_dst_bufs_ready(ctx->m2m_ctx)) { + v4l2_dbg(1, coda_debug, &dev->v4l2_dev, + "%d: device_run without buffers\n", ctx->idx); + mutex_unlock(&ctx->buffer_mutex); + schedule_work(&ctx->skip_run); + return; } + mutex_lock(&dev->coda_mutex); + + if (ctx->inst_type == CODA_INST_DECODER) { + ret = coda_prepare_decode(ctx); + if (ret < 0) { + mutex_unlock(&dev->coda_mutex); + mutex_unlock(&ctx->buffer_mutex); + /* job_finish scheduled by prepare_decode */ + return; + } + } else { + coda_prepare_encode(ctx); + } + + if (dev->devtype->product != CODA_DX6) + coda_write(dev, ctx->iram_info.axi_sram_use, + CODA7_REG_BIT_AXI_SRAM_USE); + /* 1 second timeout in case CODA locks up */ schedule_delayed_work(&dev->timeout, HZ); + if (ctx->inst_type == CODA_INST_DECODER) + coda_kfifo_sync_to_device_full(ctx); coda_command_async(ctx, CODA_COMMAND_PIC_RUN); } @@ -812,15 +1205,32 @@ static int coda_job_ready(void *m2m_priv) /* * For both 'P' and 'key' frame cases 1 picture - * and 1 frame are needed. + * and 1 frame are needed. In the decoder case, + * the compressed frame can be in the bitstream. */ - if (!v4l2_m2m_num_src_bufs_ready(ctx->m2m_ctx) || - !v4l2_m2m_num_dst_bufs_ready(ctx->m2m_ctx)) { + if (!v4l2_m2m_num_src_bufs_ready(ctx->m2m_ctx) && + ctx->inst_type != CODA_INST_DECODER) { v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, "not ready: not enough video buffers.\n"); return 0; } + if (!v4l2_m2m_num_dst_bufs_ready(ctx->m2m_ctx)) { + v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, + "not ready: not enough video capture buffers.\n"); + return 0; + } + + if (ctx->prescan_failed || + ((ctx->inst_type == CODA_INST_DECODER) && + (coda_get_bitstream_payload(ctx) < 512) && + !(ctx->bit_stream_param & CODA_BIT_STREAM_END_FLAG))) { + v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, + "%d: not ready: not enough bitstream data.\n", + ctx->idx); + return 0; + } + if (ctx->aborting) { v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, "not ready: aborting\n"); @@ -936,7 +1346,29 @@ static int coda_buf_prepare(struct vb2_buffer *vb) static void coda_buf_queue(struct vb2_buffer *vb) { struct coda_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); - v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); + struct coda_q_data *q_data; + + q_data = get_q_data(ctx, vb->vb2_queue->type); + + /* + * In the decoder case, immediately try to copy the buffer into the + * bitstream ringbuffer and mark it as ready to be dequeued. + */ + if (q_data->fourcc == V4L2_PIX_FMT_H264 && + vb->vb2_queue->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + /* + * For backwards compatiblity, queuing an empty buffer marks + * the stream end + */ + if (vb2_get_plane_payload(vb, 0) == 0) + ctx->bit_stream_param |= CODA_BIT_STREAM_END_FLAG; + mutex_lock(&ctx->bitstream_mutex); + v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); + coda_fill_bitstream(ctx); + mutex_unlock(&ctx->bitstream_mutex); + } else { + v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); + } } static void coda_wait_prepare(struct vb2_queue *q) @@ -951,21 +1383,6 @@ static void coda_wait_finish(struct vb2_queue *q) coda_lock(ctx); } -static void coda_free_framebuffers(struct coda_ctx *ctx) -{ - int i; - - for (i = 0; i < CODA_MAX_FRAMEBUFFERS; i++) { - if (ctx->internal_frames[i].vaddr) { - dma_free_coherent(&ctx->dev->plat_dev->dev, - ctx->internal_frames[i].size, - ctx->internal_frames[i].vaddr, - ctx->internal_frames[i].paddr); - ctx->internal_frames[i].vaddr = NULL; - } - } -} - static void coda_parabuf_write(struct coda_ctx *ctx, int index, u32 value) { struct coda_dev *dev = ctx->dev; @@ -977,29 +1394,69 @@ static void coda_parabuf_write(struct coda_ctx *ctx, int index, u32 value) p[index ^ 1] = value; } +static int coda_alloc_aux_buf(struct coda_dev *dev, + struct coda_aux_buf *buf, size_t size) +{ + buf->vaddr = dma_alloc_coherent(&dev->plat_dev->dev, size, &buf->paddr, + GFP_KERNEL); + if (!buf->vaddr) + return -ENOMEM; + + buf->size = size; + + return 0; +} + +static inline int coda_alloc_context_buf(struct coda_ctx *ctx, + struct coda_aux_buf *buf, size_t size) +{ + return coda_alloc_aux_buf(ctx->dev, buf, size); +} + +static void coda_free_aux_buf(struct coda_dev *dev, + struct coda_aux_buf *buf) +{ + if (buf->vaddr) { + dma_free_coherent(&dev->plat_dev->dev, buf->size, + buf->vaddr, buf->paddr); + buf->vaddr = NULL; + buf->size = 0; + } +} + +static void coda_free_framebuffers(struct coda_ctx *ctx) +{ + int i; + + for (i = 0; i < CODA_MAX_FRAMEBUFFERS; i++) + coda_free_aux_buf(ctx->dev, &ctx->internal_frames[i]); +} + static int coda_alloc_framebuffers(struct coda_ctx *ctx, struct coda_q_data *q_data, u32 fourcc) { struct coda_dev *dev = ctx->dev; - int height = q_data->height; dma_addr_t paddr; int ysize; + int ret; int i; + if (ctx->codec && ctx->codec->src_fourcc == V4L2_PIX_FMT_H264) + height = round_up(height, 16); ysize = round_up(q_data->width, 8) * height; /* Allocate frame buffers */ - ctx->num_internal_frames = CODA_MAX_FRAMEBUFFERS; for (i = 0; i < ctx->num_internal_frames; i++) { - ctx->internal_frames[i].size = q_data->sizeimage; - if (fourcc == V4L2_PIX_FMT_H264 && dev->devtype->product != CODA_DX6) + size_t size; + + size = q_data->sizeimage; + if (ctx->codec->src_fourcc == V4L2_PIX_FMT_H264 && + dev->devtype->product != CODA_DX6) ctx->internal_frames[i].size += ysize/4; - ctx->internal_frames[i].vaddr = dma_alloc_coherent( - &dev->plat_dev->dev, ctx->internal_frames[i].size, - &ctx->internal_frames[i].paddr, GFP_KERNEL); - if (!ctx->internal_frames[i].vaddr) { + ret = coda_alloc_context_buf(ctx, &ctx->internal_frames[i], size); + if (ret < 0) { coda_free_framebuffers(ctx); - return -ENOMEM; + return ret; } } @@ -1010,10 +1467,20 @@ static int coda_alloc_framebuffers(struct coda_ctx *ctx, struct coda_q_data *q_d coda_parabuf_write(ctx, i * 3 + 1, paddr + ysize); /* Cb */ coda_parabuf_write(ctx, i * 3 + 2, paddr + ysize + ysize/4); /* Cr */ - if (dev->devtype->product != CODA_DX6 && fourcc == V4L2_PIX_FMT_H264) - coda_parabuf_write(ctx, 96 + i, ctx->internal_frames[i].paddr + ysize + ysize/4 + ysize/4); + /* mvcol buffer for h.264 */ + if (ctx->codec->src_fourcc == V4L2_PIX_FMT_H264 && + dev->devtype->product != CODA_DX6) + coda_parabuf_write(ctx, 96 + i, + ctx->internal_frames[i].paddr + + ysize + ysize/4 + ysize/4); } + /* mvcol buffer for mpeg4 */ + if ((dev->devtype->product != CODA_DX6) && + (ctx->codec->src_fourcc == V4L2_PIX_FMT_MPEG4)) + coda_parabuf_write(ctx, 97, ctx->internal_frames[i].paddr + + ysize + ysize/4 + ysize/4); + return 0; } @@ -1035,6 +1502,371 @@ static int coda_h264_padding(int size, char *p) return nal_size; } +static void coda_setup_iram(struct coda_ctx *ctx) +{ + struct coda_iram_info *iram_info = &ctx->iram_info; + struct coda_dev *dev = ctx->dev; + int ipacdc_size; + int bitram_size; + int dbk_size; + int ovl_size; + int mb_width; + int me_size; + int size; + + memset(iram_info, 0, sizeof(*iram_info)); + size = dev->iram_size; + + if (dev->devtype->product == CODA_DX6) + return; + + if (ctx->inst_type == CODA_INST_ENCODER) { + struct coda_q_data *q_data_src; + + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + mb_width = DIV_ROUND_UP(q_data_src->width, 16); + + /* Prioritize in case IRAM is too small for everything */ + me_size = round_up(round_up(q_data_src->width, 16) * 36 + 2048, + 1024); + iram_info->search_ram_size = me_size; + if (size >= iram_info->search_ram_size) { + if (dev->devtype->product == CODA_7541) + iram_info->axi_sram_use |= CODA7_USE_HOST_ME_ENABLE; + iram_info->search_ram_paddr = dev->iram_paddr; + size -= iram_info->search_ram_size; + } else { + pr_err("IRAM is smaller than the search ram size\n"); + goto out; + } + + /* Only H.264BP and H.263P3 are considered */ + dbk_size = round_up(128 * mb_width, 1024); + if (size >= dbk_size) { + iram_info->axi_sram_use |= CODA7_USE_HOST_DBK_ENABLE; + iram_info->buf_dbk_y_use = dev->iram_paddr + + iram_info->search_ram_size; + iram_info->buf_dbk_c_use = iram_info->buf_dbk_y_use + + dbk_size / 2; + size -= dbk_size; + } else { + goto out; + } + + bitram_size = round_up(128 * mb_width, 1024); + if (size >= bitram_size) { + iram_info->axi_sram_use |= CODA7_USE_HOST_BIT_ENABLE; + iram_info->buf_bit_use = iram_info->buf_dbk_c_use + + dbk_size / 2; + size -= bitram_size; + } else { + goto out; + } + + ipacdc_size = round_up(128 * mb_width, 1024); + if (size >= ipacdc_size) { + iram_info->axi_sram_use |= CODA7_USE_HOST_IP_ENABLE; + iram_info->buf_ip_ac_dc_use = iram_info->buf_bit_use + + bitram_size; + size -= ipacdc_size; + } + + /* OVL and BTP disabled for encoder */ + } else if (ctx->inst_type == CODA_INST_DECODER) { + struct coda_q_data *q_data_dst; + int mb_height; + + q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + mb_width = DIV_ROUND_UP(q_data_dst->width, 16); + mb_height = DIV_ROUND_UP(q_data_dst->height, 16); + + dbk_size = round_up(256 * mb_width, 1024); + if (size >= dbk_size) { + iram_info->axi_sram_use |= CODA7_USE_HOST_DBK_ENABLE; + iram_info->buf_dbk_y_use = dev->iram_paddr; + iram_info->buf_dbk_c_use = dev->iram_paddr + + dbk_size / 2; + size -= dbk_size; + } else { + goto out; + } + + bitram_size = round_up(128 * mb_width, 1024); + if (size >= bitram_size) { + iram_info->axi_sram_use |= CODA7_USE_HOST_BIT_ENABLE; + iram_info->buf_bit_use = iram_info->buf_dbk_c_use + + dbk_size / 2; + size -= bitram_size; + } else { + goto out; + } + + ipacdc_size = round_up(128 * mb_width, 1024); + if (size >= ipacdc_size) { + iram_info->axi_sram_use |= CODA7_USE_HOST_IP_ENABLE; + iram_info->buf_ip_ac_dc_use = iram_info->buf_bit_use + + bitram_size; + size -= ipacdc_size; + } else { + goto out; + } + + ovl_size = round_up(80 * mb_width, 1024); + } + +out: + switch (dev->devtype->product) { + case CODA_DX6: + break; + case CODA_7541: + /* i.MX53 uses secondary AXI for IRAM access */ + if (iram_info->axi_sram_use & CODA7_USE_HOST_BIT_ENABLE) + iram_info->axi_sram_use |= CODA7_USE_BIT_ENABLE; + if (iram_info->axi_sram_use & CODA7_USE_HOST_IP_ENABLE) + iram_info->axi_sram_use |= CODA7_USE_IP_ENABLE; + if (iram_info->axi_sram_use & CODA7_USE_HOST_DBK_ENABLE) + iram_info->axi_sram_use |= CODA7_USE_DBK_ENABLE; + if (iram_info->axi_sram_use & CODA7_USE_HOST_OVL_ENABLE) + iram_info->axi_sram_use |= CODA7_USE_OVL_ENABLE; + if (iram_info->axi_sram_use & CODA7_USE_HOST_ME_ENABLE) + iram_info->axi_sram_use |= CODA7_USE_ME_ENABLE; + } + + if (!(iram_info->axi_sram_use & CODA7_USE_HOST_IP_ENABLE)) + v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, + "IRAM smaller than needed\n"); + + if (dev->devtype->product == CODA_7541) { + /* TODO - Enabling these causes picture errors on CODA7541 */ + if (ctx->inst_type == CODA_INST_DECODER) { + /* fw 1.4.50 */ + iram_info->axi_sram_use &= ~(CODA7_USE_HOST_IP_ENABLE | + CODA7_USE_IP_ENABLE); + } else { + /* fw 13.4.29 */ + iram_info->axi_sram_use &= ~(CODA7_USE_HOST_IP_ENABLE | + CODA7_USE_HOST_DBK_ENABLE | + CODA7_USE_IP_ENABLE | + CODA7_USE_DBK_ENABLE); + } + } +} + +static void coda_free_context_buffers(struct coda_ctx *ctx) +{ + struct coda_dev *dev = ctx->dev; + + coda_free_aux_buf(dev, &ctx->slicebuf); + coda_free_aux_buf(dev, &ctx->psbuf); + if (dev->devtype->product != CODA_DX6) + coda_free_aux_buf(dev, &ctx->workbuf); +} + +static int coda_alloc_context_buffers(struct coda_ctx *ctx, + struct coda_q_data *q_data) +{ + struct coda_dev *dev = ctx->dev; + size_t size; + int ret; + + switch (dev->devtype->product) { + case CODA_7541: + size = CODA7_WORK_BUF_SIZE; + break; + default: + return 0; + } + + if (ctx->psbuf.vaddr) { + v4l2_err(&dev->v4l2_dev, "psmembuf still allocated\n"); + return -EBUSY; + } + if (ctx->slicebuf.vaddr) { + v4l2_err(&dev->v4l2_dev, "slicebuf still allocated\n"); + return -EBUSY; + } + if (ctx->workbuf.vaddr) { + v4l2_err(&dev->v4l2_dev, "context buffer still allocated\n"); + ret = -EBUSY; + return -ENOMEM; + } + + if (q_data->fourcc == V4L2_PIX_FMT_H264) { + /* worst case slice size */ + size = (DIV_ROUND_UP(q_data->width, 16) * + DIV_ROUND_UP(q_data->height, 16)) * 3200 / 8 + 512; + ret = coda_alloc_context_buf(ctx, &ctx->slicebuf, size); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "failed to allocate %d byte slice buffer", + ctx->slicebuf.size); + return ret; + } + } + + if (dev->devtype->product == CODA_7541) { + ret = coda_alloc_context_buf(ctx, &ctx->psbuf, CODA7_PS_BUF_SIZE); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "failed to allocate psmem buffer"); + goto err; + } + } + + ret = coda_alloc_context_buf(ctx, &ctx->workbuf, size); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "failed to allocate %d byte context buffer", + ctx->workbuf.size); + goto err; + } + + return 0; + +err: + coda_free_context_buffers(ctx); + return ret; +} + +static int coda_start_decoding(struct coda_ctx *ctx) +{ + struct coda_q_data *q_data_src, *q_data_dst; + u32 bitstream_buf, bitstream_size; + struct coda_dev *dev = ctx->dev; + int width, height; + u32 src_fourcc; + u32 val; + int ret; + + /* Start decoding */ + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + bitstream_buf = ctx->bitstream.paddr; + bitstream_size = ctx->bitstream.size; + src_fourcc = q_data_src->fourcc; + + coda_write(dev, ctx->parabuf.paddr, CODA_REG_BIT_PARA_BUF_ADDR); + + /* Update coda bitstream read and write pointers from kfifo */ + coda_kfifo_sync_to_device_full(ctx); + + ctx->display_idx = -1; + ctx->frm_dis_flg = 0; + coda_write(dev, 0, CODA_REG_BIT_FRM_DIS_FLG(ctx->reg_idx)); + + coda_write(dev, CODA_BIT_DEC_SEQ_INIT_ESCAPE, + CODA_REG_BIT_BIT_STREAM_PARAM); + + coda_write(dev, bitstream_buf, CODA_CMD_DEC_SEQ_BB_START); + coda_write(dev, bitstream_size / 1024, CODA_CMD_DEC_SEQ_BB_SIZE); + val = 0; + if (dev->devtype->product == CODA_7541) + val |= CODA_REORDER_ENABLE; + coda_write(dev, val, CODA_CMD_DEC_SEQ_OPTION); + + ctx->params.codec_mode = ctx->codec->mode; + ctx->params.codec_mode_aux = 0; + if (src_fourcc == V4L2_PIX_FMT_H264) { + if (dev->devtype->product == CODA_7541) { + coda_write(dev, ctx->psbuf.paddr, + CODA_CMD_DEC_SEQ_PS_BB_START); + coda_write(dev, (CODA7_PS_BUF_SIZE / 1024), + CODA_CMD_DEC_SEQ_PS_BB_SIZE); + } + } + + if (coda_command_sync(ctx, CODA_COMMAND_SEQ_INIT)) { + v4l2_err(&dev->v4l2_dev, "CODA_COMMAND_SEQ_INIT timeout\n"); + coda_write(dev, 0, CODA_REG_BIT_BIT_STREAM_PARAM); + return -ETIMEDOUT; + } + + /* Update kfifo out pointer from coda bitstream read pointer */ + coda_kfifo_sync_from_device(ctx); + + coda_write(dev, 0, CODA_REG_BIT_BIT_STREAM_PARAM); + + if (coda_read(dev, CODA_RET_DEC_SEQ_SUCCESS) == 0) { + v4l2_err(&dev->v4l2_dev, + "CODA_COMMAND_SEQ_INIT failed, error code = %d\n", + coda_read(dev, CODA_RET_DEC_SEQ_ERR_REASON)); + return -EAGAIN; + } + + val = coda_read(dev, CODA_RET_DEC_SEQ_SRC_SIZE); + if (dev->devtype->product == CODA_DX6) { + width = (val >> CODADX6_PICWIDTH_OFFSET) & CODADX6_PICWIDTH_MASK; + height = val & CODADX6_PICHEIGHT_MASK; + } else { + width = (val >> CODA7_PICWIDTH_OFFSET) & CODA7_PICWIDTH_MASK; + height = val & CODA7_PICHEIGHT_MASK; + } + + if (width > q_data_dst->width || height > q_data_dst->height) { + v4l2_err(&dev->v4l2_dev, "stream is %dx%d, not %dx%d\n", + width, height, q_data_dst->width, q_data_dst->height); + return -EINVAL; + } + + width = round_up(width, 16); + height = round_up(height, 16); + + v4l2_dbg(1, coda_debug, &dev->v4l2_dev, "%s instance %d now: %dx%d\n", + __func__, ctx->idx, width, height); + + ctx->num_internal_frames = coda_read(dev, CODA_RET_DEC_SEQ_FRAME_NEED) + 1; + if (ctx->num_internal_frames > CODA_MAX_FRAMEBUFFERS) { + v4l2_err(&dev->v4l2_dev, + "not enough framebuffers to decode (%d < %d)\n", + CODA_MAX_FRAMEBUFFERS, ctx->num_internal_frames); + return -EINVAL; + } + + ret = coda_alloc_framebuffers(ctx, q_data_dst, src_fourcc); + if (ret < 0) + return ret; + + /* Tell the decoder how many frame buffers we allocated. */ + coda_write(dev, ctx->num_internal_frames, CODA_CMD_SET_FRAME_BUF_NUM); + coda_write(dev, width, CODA_CMD_SET_FRAME_BUF_STRIDE); + + if (dev->devtype->product != CODA_DX6) { + /* Set secondary AXI IRAM */ + coda_setup_iram(ctx); + + coda_write(dev, ctx->iram_info.buf_bit_use, + CODA7_CMD_SET_FRAME_AXI_BIT_ADDR); + coda_write(dev, ctx->iram_info.buf_ip_ac_dc_use, + CODA7_CMD_SET_FRAME_AXI_IPACDC_ADDR); + coda_write(dev, ctx->iram_info.buf_dbk_y_use, + CODA7_CMD_SET_FRAME_AXI_DBKY_ADDR); + coda_write(dev, ctx->iram_info.buf_dbk_c_use, + CODA7_CMD_SET_FRAME_AXI_DBKC_ADDR); + coda_write(dev, ctx->iram_info.buf_ovl_use, + CODA7_CMD_SET_FRAME_AXI_OVL_ADDR); + } + + if (src_fourcc == V4L2_PIX_FMT_H264) { + coda_write(dev, ctx->slicebuf.paddr, + CODA_CMD_SET_FRAME_SLICE_BB_START); + coda_write(dev, ctx->slicebuf.size / 1024, + CODA_CMD_SET_FRAME_SLICE_BB_SIZE); + } + + if (dev->devtype->product == CODA_7541) { + int max_mb_x = 1920 / 16; + int max_mb_y = 1088 / 16; + int max_mb_num = max_mb_x * max_mb_y; + coda_write(dev, max_mb_num << 16 | max_mb_x << 8 | max_mb_y, + CODA7_CMD_SET_FRAME_MAX_DEC_SIZE); + } + + if (coda_command_sync(ctx, CODA_COMMAND_SET_FRAME_BUF)) { + v4l2_err(&ctx->dev->v4l2_dev, + "CODA_COMMAND_SET_FRAME_BUF timeout\n"); + return -ETIMEDOUT; + } + + return 0; +} + static int coda_encode_header(struct coda_ctx *ctx, struct vb2_buffer *buf, int header_code, u8 *header, int *size) { @@ -1050,7 +1882,7 @@ static int coda_encode_header(struct coda_ctx *ctx, struct vb2_buffer *buf, v4l2_err(&dev->v4l2_dev, "CODA_COMMAND_ENCODE_HEADER timeout\n"); return ret; } - *size = coda_read(dev, CODA_REG_BIT_WR_PTR(ctx->idx)) - + *size = coda_read(dev, CODA_REG_BIT_WR_PTR(ctx->reg_idx)) - coda_read(dev, CODA_CMD_ENC_HEADER_BB_START); memcpy(header, vb2_plane_vaddr(buf, 0), *size); @@ -1069,26 +1901,36 @@ static int coda_start_streaming(struct vb2_queue *q, unsigned int count) u32 value; int ret = 0; - if (count < 1) - return -EINVAL; + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + if (q_data_src->fourcc == V4L2_PIX_FMT_H264) { + if (coda_get_bitstream_payload(ctx) < 512) + return -EINVAL; + } else { + if (count < 1) + return -EINVAL; + } - if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) ctx->streamon_out = 1; - else - ctx->streamon_cap = 1; - q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); - if (ctx->streamon_out) { if (coda_format_is_yuv(q_data_src->fourcc)) ctx->inst_type = CODA_INST_ENCODER; else ctx->inst_type = CODA_INST_DECODER; + } else { + if (count < 1) + return -EINVAL; + + ctx->streamon_cap = 1; } /* Don't start the coda unless both queues are on */ if (!(ctx->streamon_out & ctx->streamon_cap)) return 0; + /* Allow device_run with no buffers queued and after streamoff */ + v4l2_m2m_set_src_buffered(ctx->m2m_ctx, true); + ctx->gopcounter = ctx->params.gop_size - 1; buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); bitstream_buf = vb2_dma_contig_plane_dma_addr(buf, 0); @@ -1103,6 +1945,25 @@ static int coda_start_streaming(struct vb2_queue *q, unsigned int count) return -EINVAL; } + /* Allocate per-instance buffers */ + ret = coda_alloc_context_buffers(ctx, q_data_src); + if (ret < 0) + return ret; + + if (ctx->inst_type == CODA_INST_DECODER) { + mutex_lock(&dev->coda_mutex); + ret = coda_start_decoding(ctx); + mutex_unlock(&dev->coda_mutex); + if (ret == -EAGAIN) { + return 0; + } else if (ret < 0) { + return ret; + } else { + ctx->initialized = 1; + return 0; + } + } + if (!coda_is_initialized(dev)) { v4l2_err(v4l2_dev, "coda is not initialized.\n"); return -EFAULT; @@ -1111,8 +1972,8 @@ static int coda_start_streaming(struct vb2_queue *q, unsigned int count) mutex_lock(&dev->coda_mutex); coda_write(dev, ctx->parabuf.paddr, CODA_REG_BIT_PARA_BUF_ADDR); - coda_write(dev, bitstream_buf, CODA_REG_BIT_RD_PTR(ctx->idx)); - coda_write(dev, bitstream_buf, CODA_REG_BIT_WR_PTR(ctx->idx)); + coda_write(dev, bitstream_buf, CODA_REG_BIT_RD_PTR(ctx->reg_idx)); + coda_write(dev, bitstream_buf, CODA_REG_BIT_WR_PTR(ctx->reg_idx)); switch (dev->devtype->product) { case CODA_DX6: coda_write(dev, CODADX6_STREAM_BUF_DYNALLOC_EN | @@ -1207,6 +2068,8 @@ static int coda_start_streaming(struct vb2_queue *q, unsigned int count) } coda_write(dev, value, CODA_CMD_ENC_SEQ_OPTION); + coda_setup_iram(ctx); + if (dst_fourcc == V4L2_PIX_FMT_H264) { value = (FMO_SLICE_SAVE_BUF_SIZE << 7); value |= (0 & CODA_FMOPARAM_TYPE_MASK) << CODA_FMOPARAM_TYPE_OFFSET; @@ -1214,8 +2077,10 @@ static int coda_start_streaming(struct vb2_queue *q, unsigned int count) if (dev->devtype->product == CODA_DX6) { coda_write(dev, value, CODADX6_CMD_ENC_SEQ_FMO); } else { - coda_write(dev, dev->iram_paddr, CODA7_CMD_ENC_SEQ_SEARCH_BASE); - coda_write(dev, 48 * 1024, CODA7_CMD_ENC_SEQ_SEARCH_SIZE); + coda_write(dev, ctx->iram_info.search_ram_paddr, + CODA7_CMD_ENC_SEQ_SEARCH_BASE); + coda_write(dev, ctx->iram_info.search_ram_size, + CODA7_CMD_ENC_SEQ_SEARCH_SIZE); } } @@ -1231,6 +2096,7 @@ static int coda_start_streaming(struct vb2_queue *q, unsigned int count) goto out; } + ctx->num_internal_frames = 2; ret = coda_alloc_framebuffers(ctx, q_data_src, dst_fourcc); if (ret < 0) { v4l2_err(v4l2_dev, "failed to allocate framebuffers\n"); @@ -1239,13 +2105,20 @@ static int coda_start_streaming(struct vb2_queue *q, unsigned int count) coda_write(dev, ctx->num_internal_frames, CODA_CMD_SET_FRAME_BUF_NUM); coda_write(dev, round_up(q_data_src->width, 8), CODA_CMD_SET_FRAME_BUF_STRIDE); + if (dev->devtype->product == CODA_7541) + coda_write(dev, round_up(q_data_src->width, 8), + CODA7_CMD_SET_FRAME_SOURCE_BUF_STRIDE); if (dev->devtype->product != CODA_DX6) { - coda_write(dev, round_up(q_data_src->width, 8), CODA7_CMD_SET_FRAME_SOURCE_BUF_STRIDE); - coda_write(dev, dev->iram_paddr + 48 * 1024, CODA7_CMD_SET_FRAME_AXI_DBKY_ADDR); - coda_write(dev, dev->iram_paddr + 53 * 1024, CODA7_CMD_SET_FRAME_AXI_DBKC_ADDR); - coda_write(dev, dev->iram_paddr + 58 * 1024, CODA7_CMD_SET_FRAME_AXI_BIT_ADDR); - coda_write(dev, dev->iram_paddr + 68 * 1024, CODA7_CMD_SET_FRAME_AXI_IPACDC_ADDR); - coda_write(dev, 0x0, CODA7_CMD_SET_FRAME_AXI_OVL_ADDR); + coda_write(dev, ctx->iram_info.buf_bit_use, + CODA7_CMD_SET_FRAME_AXI_BIT_ADDR); + coda_write(dev, ctx->iram_info.buf_ip_ac_dc_use, + CODA7_CMD_SET_FRAME_AXI_IPACDC_ADDR); + coda_write(dev, ctx->iram_info.buf_dbk_y_use, + CODA7_CMD_SET_FRAME_AXI_DBKY_ADDR); + coda_write(dev, ctx->iram_info.buf_dbk_c_use, + CODA7_CMD_SET_FRAME_AXI_DBKC_ADDR); + coda_write(dev, ctx->iram_info.buf_ovl_use, + CODA7_CMD_SET_FRAME_AXI_OVL_ADDR); } ret = coda_command_sync(ctx, CODA_COMMAND_SET_FRAME_BUF); if (ret < 0) { @@ -1326,32 +2199,26 @@ static int coda_stop_streaming(struct vb2_queue *q) struct coda_dev *dev = ctx->dev; if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { - v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, + v4l2_dbg(1, coda_debug, &dev->v4l2_dev, "%s: output\n", __func__); ctx->streamon_out = 0; + + ctx->bit_stream_param |= CODA_BIT_STREAM_END_FLAG; + + ctx->isequence = 0; } else { - v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, + v4l2_dbg(1, coda_debug, &dev->v4l2_dev, "%s: capture\n", __func__); ctx->streamon_cap = 0; - } - - /* Don't stop the coda unless both queues are off */ - if (ctx->streamon_out || ctx->streamon_cap) - return 0; - cancel_delayed_work(&dev->timeout); - - mutex_lock(&dev->coda_mutex); - v4l2_dbg(1, coda_debug, &dev->v4l2_dev, - "%s: sent command 'SEQ_END' to coda\n", __func__); - if (coda_command_sync(ctx, CODA_COMMAND_SEQ_END)) { - v4l2_err(&dev->v4l2_dev, - "CODA_COMMAND_SEQ_END failed\n"); - return -ETIMEDOUT; + ctx->osequence = 0; } - mutex_unlock(&dev->coda_mutex); - coda_free_framebuffers(ctx); + if (!ctx->streamon_out && !ctx->streamon_cap) { + kfifo_init(&ctx->bitstream_fifo, + ctx->bitstream.vaddr, ctx->bitstream.size); + ctx->runcounter = 0; + } return 0; } @@ -1511,23 +2378,41 @@ static int coda_open(struct file *file) { struct coda_dev *dev = video_drvdata(file); struct coda_ctx *ctx = NULL; - int ret = 0; + int ret; int idx; - idx = coda_next_free_instance(dev); - if (idx >= CODA_MAX_INSTANCES) - return -EBUSY; - set_bit(idx, &dev->instance_mask); - ctx = kzalloc(sizeof *ctx, GFP_KERNEL); if (!ctx) return -ENOMEM; + idx = coda_next_free_instance(dev); + if (idx >= CODA_MAX_INSTANCES) { + ret = -EBUSY; + goto err_coda_max; + } + set_bit(idx, &dev->instance_mask); + + INIT_WORK(&ctx->skip_run, coda_skip_run); v4l2_fh_init(&ctx->fh, video_devdata(file)); file->private_data = &ctx->fh; v4l2_fh_add(&ctx->fh); ctx->dev = dev; ctx->idx = idx; + switch (dev->devtype->product) { + case CODA_7541: + ctx->reg_idx = 0; + break; + default: + ctx->reg_idx = idx; + } + + ret = clk_prepare_enable(dev->clk_per); + if (ret) + goto err_clk_per; + + ret = clk_prepare_enable(dev->clk_ahb); + if (ret) + goto err_clk_ahb; set_default_params(ctx); ctx->m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, @@ -1537,39 +2422,62 @@ static int coda_open(struct file *file) v4l2_err(&dev->v4l2_dev, "%s return error (%d)\n", __func__, ret); - goto err; + goto err_ctx_init; } ret = coda_ctrls_setup(ctx); if (ret) { v4l2_err(&dev->v4l2_dev, "failed to setup coda controls\n"); - goto err; + goto err_ctrls_setup; } ctx->fh.ctrl_handler = &ctx->ctrls; - ctx->parabuf.vaddr = dma_alloc_coherent(&dev->plat_dev->dev, - CODA_PARA_BUF_SIZE, &ctx->parabuf.paddr, GFP_KERNEL); - if (!ctx->parabuf.vaddr) { + ret = coda_alloc_context_buf(ctx, &ctx->parabuf, CODA_PARA_BUF_SIZE); + if (ret < 0) { v4l2_err(&dev->v4l2_dev, "failed to allocate parabuf"); + goto err_dma_alloc; + } + + ctx->bitstream.size = CODA_MAX_FRAME_SIZE; + ctx->bitstream.vaddr = dma_alloc_writecombine(&dev->plat_dev->dev, + ctx->bitstream.size, &ctx->bitstream.paddr, GFP_KERNEL); + if (!ctx->bitstream.vaddr) { + v4l2_err(&dev->v4l2_dev, "failed to allocate bitstream ringbuffer"); ret = -ENOMEM; - goto err; + goto err_dma_writecombine; } + kfifo_init(&ctx->bitstream_fifo, + ctx->bitstream.vaddr, ctx->bitstream.size); + mutex_init(&ctx->bitstream_mutex); + mutex_init(&ctx->buffer_mutex); coda_lock(ctx); list_add(&ctx->list, &dev->instances); coda_unlock(ctx); - clk_prepare_enable(dev->clk_per); - clk_prepare_enable(dev->clk_ahb); - v4l2_dbg(1, coda_debug, &dev->v4l2_dev, "Created instance %d (%p)\n", ctx->idx, ctx); return 0; -err: +err_dma_writecombine: + coda_free_context_buffers(ctx); + if (ctx->dev->devtype->product == CODA_DX6) + coda_free_aux_buf(dev, &ctx->workbuf); + coda_free_aux_buf(dev, &ctx->parabuf); +err_dma_alloc: + v4l2_ctrl_handler_free(&ctx->ctrls); +err_ctrls_setup: + v4l2_m2m_ctx_release(ctx->m2m_ctx); +err_ctx_init: + clk_disable_unprepare(dev->clk_ahb); +err_clk_ahb: + clk_disable_unprepare(dev->clk_per); +err_clk_per: v4l2_fh_del(&ctx->fh); v4l2_fh_exit(&ctx->fh); + clear_bit(ctx->idx, &dev->instance_mask); +err_coda_max: kfree(ctx); return ret; } @@ -1582,16 +2490,37 @@ static int coda_release(struct file *file) v4l2_dbg(1, coda_debug, &dev->v4l2_dev, "Releasing instance %p\n", ctx); + /* If this instance is running, call .job_abort and wait for it to end */ + v4l2_m2m_ctx_release(ctx->m2m_ctx); + + /* In case the instance was not running, we still need to call SEQ_END */ + mutex_lock(&dev->coda_mutex); + v4l2_dbg(1, coda_debug, &dev->v4l2_dev, + "%s: sent command 'SEQ_END' to coda\n", __func__); + if (coda_command_sync(ctx, CODA_COMMAND_SEQ_END)) { + v4l2_err(&dev->v4l2_dev, + "CODA_COMMAND_SEQ_END failed\n"); + mutex_unlock(&dev->coda_mutex); + return -ETIMEDOUT; + } + mutex_unlock(&dev->coda_mutex); + + coda_free_framebuffers(ctx); + coda_lock(ctx); list_del(&ctx->list); coda_unlock(ctx); - dma_free_coherent(&dev->plat_dev->dev, CODA_PARA_BUF_SIZE, - ctx->parabuf.vaddr, ctx->parabuf.paddr); - v4l2_m2m_ctx_release(ctx->m2m_ctx); + dma_free_writecombine(&dev->plat_dev->dev, ctx->bitstream.size, + ctx->bitstream.vaddr, ctx->bitstream.paddr); + coda_free_context_buffers(ctx); + if (ctx->dev->devtype->product == CODA_DX6) + coda_free_aux_buf(dev, &ctx->workbuf); + + coda_free_aux_buf(dev, &ctx->parabuf); v4l2_ctrl_handler_free(&ctx->ctrls); - clk_disable_unprepare(dev->clk_per); clk_disable_unprepare(dev->clk_ahb); + clk_disable_unprepare(dev->clk_per); v4l2_fh_del(&ctx->fh); v4l2_fh_exit(&ctx->fh); clear_bit(ctx->idx, &dev->instance_mask); @@ -1628,55 +2557,180 @@ static const struct v4l2_file_operations coda_fops = { .mmap = coda_mmap, }; -static irqreturn_t coda_irq_handler(int irq, void *data) +static void coda_finish_decode(struct coda_ctx *ctx) { - struct vb2_buffer *src_buf, *dst_buf; - struct coda_dev *dev = data; - u32 wr_ptr, start_ptr; - struct coda_ctx *ctx; + struct coda_dev *dev = ctx->dev; + struct coda_q_data *q_data_src; + struct coda_q_data *q_data_dst; + struct vb2_buffer *dst_buf; + int width, height; + int decoded_idx; + int display_idx; + u32 src_fourcc; + int success; + u32 val; - cancel_delayed_work(&dev->timeout); + dst_buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); - /* read status register to attend the IRQ */ - coda_read(dev, CODA_REG_BIT_INT_STATUS); - coda_write(dev, CODA_REG_BIT_INT_CLEAR_SET, - CODA_REG_BIT_INT_CLEAR); + /* Update kfifo out pointer from coda bitstream read pointer */ + coda_kfifo_sync_from_device(ctx); - ctx = v4l2_m2m_get_curr_priv(dev->m2m_dev); - if (ctx == NULL) { - v4l2_err(&dev->v4l2_dev, "Instance released before the end of transaction\n"); - mutex_unlock(&dev->coda_mutex); - return IRQ_HANDLED; + /* + * in stream-end mode, the read pointer can overshoot the write pointer + * by up to 512 bytes + */ + if (ctx->bit_stream_param & CODA_BIT_STREAM_END_FLAG) { + if (coda_get_bitstream_payload(ctx) >= 0x100000 - 512) + kfifo_init(&ctx->bitstream_fifo, + ctx->bitstream.vaddr, ctx->bitstream.size); } - if (ctx->aborting) { - v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, - "task has been aborted\n"); - mutex_unlock(&dev->coda_mutex); - return IRQ_HANDLED; + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + src_fourcc = q_data_src->fourcc; + + val = coda_read(dev, CODA_RET_DEC_PIC_SUCCESS); + if (val != 1) + pr_err("DEC_PIC_SUCCESS = %d\n", val); + + success = val & 0x1; + if (!success) + v4l2_err(&dev->v4l2_dev, "decode failed\n"); + + if (src_fourcc == V4L2_PIX_FMT_H264) { + if (val & (1 << 3)) + v4l2_err(&dev->v4l2_dev, + "insufficient PS buffer space (%d bytes)\n", + ctx->psbuf.size); + if (val & (1 << 2)) + v4l2_err(&dev->v4l2_dev, + "insufficient slice buffer space (%d bytes)\n", + ctx->slicebuf.size); } - if (coda_isbusy(ctx->dev)) { - v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, - "coda is still busy!!!!\n"); - return IRQ_NONE; + val = coda_read(dev, CODA_RET_DEC_PIC_SIZE); + width = (val >> 16) & 0xffff; + height = val & 0xffff; + + q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + + val = coda_read(dev, CODA_RET_DEC_PIC_TYPE); + if ((val & 0x7) == 0) { + dst_buf->v4l2_buf.flags |= V4L2_BUF_FLAG_KEYFRAME; + dst_buf->v4l2_buf.flags &= ~V4L2_BUF_FLAG_PFRAME; + } else { + dst_buf->v4l2_buf.flags |= V4L2_BUF_FLAG_PFRAME; + dst_buf->v4l2_buf.flags &= ~V4L2_BUF_FLAG_KEYFRAME; + } + + val = coda_read(dev, CODA_RET_DEC_PIC_ERR_MB); + if (val > 0) + v4l2_err(&dev->v4l2_dev, + "errors in %d macroblocks\n", val); + + if (dev->devtype->product == CODA_7541) { + val = coda_read(dev, CODA_RET_DEC_PIC_OPTION); + if (val == 0) { + /* not enough bitstream data */ + v4l2_dbg(1, coda_debug, &dev->v4l2_dev, + "prescan failed: %d\n", val); + ctx->prescan_failed = true; + return; + } + } + + ctx->frm_dis_flg = coda_read(dev, CODA_REG_BIT_FRM_DIS_FLG(ctx->reg_idx)); + + /* + * The previous display frame was copied out by the rotator, + * now it can be overwritten again + */ + if (ctx->display_idx >= 0 && + ctx->display_idx < ctx->num_internal_frames) { + ctx->frm_dis_flg &= ~(1 << ctx->display_idx); + coda_write(dev, ctx->frm_dis_flg, + CODA_REG_BIT_FRM_DIS_FLG(ctx->reg_idx)); + } + + /* + * The index of the last decoded frame, not necessarily in + * display order, and the index of the next display frame. + * The latter could have been decoded in a previous run. + */ + decoded_idx = coda_read(dev, CODA_RET_DEC_PIC_CUR_IDX); + display_idx = coda_read(dev, CODA_RET_DEC_PIC_FRAME_IDX); + + if (decoded_idx == -1) { + /* no frame was decoded, but we might have a display frame */ + if (display_idx < 0 && ctx->display_idx < 0) + ctx->prescan_failed = true; + } else if (decoded_idx == -2) { + /* no frame was decoded, we still return the remaining buffers */ + } else if (decoded_idx < 0 || decoded_idx >= ctx->num_internal_frames) { + v4l2_err(&dev->v4l2_dev, + "decoded frame index out of range: %d\n", decoded_idx); } + if (display_idx == -1) { + /* + * no more frames to be decoded, but there could still + * be rotator output to dequeue + */ + ctx->prescan_failed = true; + } else if (display_idx == -3) { + /* possibly prescan failure */ + } else if (display_idx < 0 || display_idx >= ctx->num_internal_frames) { + v4l2_err(&dev->v4l2_dev, + "presentation frame index out of range: %d\n", + display_idx); + } + + /* If a frame was copied out, return it */ + if (ctx->display_idx >= 0 && + ctx->display_idx < ctx->num_internal_frames) { + dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); + dst_buf->v4l2_buf.sequence = ctx->osequence++; + + vb2_set_plane_payload(dst_buf, 0, width * height * 3 / 2); + + v4l2_m2m_buf_done(dst_buf, success ? VB2_BUF_STATE_DONE : + VB2_BUF_STATE_ERROR); + + v4l2_dbg(1, coda_debug, &dev->v4l2_dev, + "job finished: decoding frame (%d) (%s)\n", + dst_buf->v4l2_buf.sequence, + (dst_buf->v4l2_buf.flags & V4L2_BUF_FLAG_KEYFRAME) ? + "KEYFRAME" : "PFRAME"); + } else { + v4l2_dbg(1, coda_debug, &dev->v4l2_dev, + "job finished: no frame decoded\n"); + } + + /* The rotator will copy the current display frame next time */ + ctx->display_idx = display_idx; +} + +static void coda_finish_encode(struct coda_ctx *ctx) +{ + struct vb2_buffer *src_buf, *dst_buf; + struct coda_dev *dev = ctx->dev; + u32 wr_ptr, start_ptr; + src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); /* Get results from the coda */ coda_read(dev, CODA_RET_ENC_PIC_TYPE); start_ptr = coda_read(dev, CODA_CMD_ENC_PIC_BB_START); - wr_ptr = coda_read(dev, CODA_REG_BIT_WR_PTR(ctx->idx)); + wr_ptr = coda_read(dev, CODA_REG_BIT_WR_PTR(ctx->reg_idx)); + /* Calculate bytesused field */ if (dst_buf->v4l2_buf.sequence == 0) { - dst_buf->v4l2_planes[0].bytesused = (wr_ptr - start_ptr) + - ctx->vpu_header_size[0] + - ctx->vpu_header_size[1] + - ctx->vpu_header_size[2]; + vb2_set_plane_payload(dst_buf, 0, wr_ptr - start_ptr + + ctx->vpu_header_size[0] + + ctx->vpu_header_size[1] + + ctx->vpu_header_size[2]); } else { - dst_buf->v4l2_planes[0].bytesused = (wr_ptr - start_ptr); + vb2_set_plane_payload(dst_buf, 0, wr_ptr - start_ptr); } v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, "frame size = %u\n", @@ -1708,8 +2762,62 @@ static irqreturn_t coda_irq_handler(int irq, void *data) dst_buf->v4l2_buf.sequence, (dst_buf->v4l2_buf.flags & V4L2_BUF_FLAG_KEYFRAME) ? "KEYFRAME" : "PFRAME"); +} + +static irqreturn_t coda_irq_handler(int irq, void *data) +{ + struct coda_dev *dev = data; + struct coda_ctx *ctx; + + cancel_delayed_work(&dev->timeout); + + /* read status register to attend the IRQ */ + coda_read(dev, CODA_REG_BIT_INT_STATUS); + coda_write(dev, CODA_REG_BIT_INT_CLEAR_SET, + CODA_REG_BIT_INT_CLEAR); + + ctx = v4l2_m2m_get_curr_priv(dev->m2m_dev); + if (ctx == NULL) { + v4l2_err(&dev->v4l2_dev, "Instance released before the end of transaction\n"); + mutex_unlock(&dev->coda_mutex); + return IRQ_HANDLED; + } + + if (ctx->aborting) { + v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, + "task has been aborted\n"); + goto out; + } + + if (coda_isbusy(ctx->dev)) { + v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, + "coda is still busy!!!!\n"); + return IRQ_NONE; + } + + if (ctx->inst_type == CODA_INST_DECODER) + coda_finish_decode(ctx); + else + coda_finish_encode(ctx); + +out: + if (ctx->aborting || (!ctx->streamon_cap && !ctx->streamon_out)) { + v4l2_dbg(1, coda_debug, &dev->v4l2_dev, + "%s: sent command 'SEQ_END' to coda\n", __func__); + if (coda_command_sync(ctx, CODA_COMMAND_SEQ_END)) { + v4l2_err(&dev->v4l2_dev, + "CODA_COMMAND_SEQ_END failed\n"); + } + + kfifo_init(&ctx->bitstream_fifo, + ctx->bitstream.vaddr, ctx->bitstream.size); + + coda_free_framebuffers(ctx); + coda_free_context_buffers(ctx); + } mutex_unlock(&dev->coda_mutex); + mutex_unlock(&ctx->buffer_mutex); v4l2_m2m_job_finish(ctx->dev->m2m_dev, ctx->m2m_ctx); @@ -1726,6 +2834,8 @@ static void coda_timeout(struct work_struct *work) mutex_lock(&dev->dev_mutex); list_for_each_entry(ctx, &dev->instances, list) { + if (mutex_is_locked(&ctx->buffer_mutex)) + mutex_unlock(&ctx->buffer_mutex); v4l2_m2m_streamoff(NULL, ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); v4l2_m2m_streamoff(NULL, ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); } @@ -1738,7 +2848,7 @@ static void coda_timeout(struct work_struct *work) static u32 coda_supported_firmwares[] = { CODA_FIRMWARE_VERNUM(CODA_DX6, 2, 2, 5), - CODA_FIRMWARE_VERNUM(CODA_7541, 13, 4, 29), + CODA_FIRMWARE_VERNUM(CODA_7541, 1, 4, 50), }; static bool coda_firmware_supported(u32 vernum) @@ -1771,10 +2881,15 @@ static int coda_hw_init(struct coda_dev *dev) u16 product, major, minor, release; u32 data; u16 *p; - int i; + int i, ret; - clk_prepare_enable(dev->clk_per); - clk_prepare_enable(dev->clk_ahb); + ret = clk_prepare_enable(dev->clk_per); + if (ret) + return ret; + + ret = clk_prepare_enable(dev->clk_ahb); + if (ret) + goto err_clk_ahb; /* * Copy the first CODA_ISRAM_SIZE in the internal SRAM. @@ -1803,8 +2918,14 @@ static int coda_hw_init(struct coda_dev *dev) coda_write(dev, 0, CODA_REG_BIT_CODE_BUF_ADDR + i * 4); /* Tell the BIT where to find everything it needs */ - coda_write(dev, dev->workbuf.paddr, - CODA_REG_BIT_WORK_BUF_ADDR); + if (dev->devtype->product == CODA_7541) { + coda_write(dev, dev->tempbuf.paddr, + CODA_REG_BIT_TEMP_BUF_ADDR); + coda_write(dev, 0, CODA_REG_BIT_BIT_STREAM_PARAM); + } else { + coda_write(dev, dev->workbuf.paddr, + CODA_REG_BIT_WORK_BUF_ADDR); + } coda_write(dev, dev->codebuf.paddr, CODA_REG_BIT_CODE_BUF_ADDR); coda_write(dev, 0, CODA_REG_BIT_CODE_RUN); @@ -1877,6 +2998,10 @@ static int coda_hw_init(struct coda_dev *dev) } return 0; + +err_clk_ahb: + clk_disable_unprepare(dev->clk_per); + return ret; } static void coda_fw_callback(const struct firmware *fw, void *context) @@ -1891,11 +3016,8 @@ static void coda_fw_callback(const struct firmware *fw, void *context) } /* allocate auxiliary per-device code buffer for the BIT processor */ - dev->codebuf.size = fw->size; - dev->codebuf.vaddr = dma_alloc_coherent(&pdev->dev, fw->size, - &dev->codebuf.paddr, - GFP_KERNEL); - if (!dev->codebuf.vaddr) { + ret = coda_alloc_aux_buf(dev, &dev->codebuf, fw->size); + if (ret < 0) { dev_err(&pdev->dev, "failed to allocate code buffer\n"); return; } @@ -2032,11 +3154,6 @@ static int coda_probe(struct platform_device *pdev) /* Get memory for physical registers */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (res == NULL) { - dev_err(&pdev->dev, "failed to get memory region resource\n"); - return -ENOENT; - } - dev->regs_base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(dev->regs_base)) return PTR_ERR(dev->regs_base); @@ -2048,8 +3165,8 @@ static int coda_probe(struct platform_device *pdev) return -ENOENT; } - if (devm_request_irq(&pdev->dev, irq, coda_irq_handler, - 0, CODA_NAME, dev) < 0) { + if (devm_request_threaded_irq(&pdev->dev, irq, NULL, coda_irq_handler, + IRQF_ONESHOT, CODA_NAME, dev) < 0) { dev_err(&pdev->dev, "failed to request irq\n"); return -ENOENT; } @@ -2085,24 +3202,36 @@ static int coda_probe(struct platform_device *pdev) /* allocate auxiliary per-device buffers for the BIT processor */ switch (dev->devtype->product) { case CODA_DX6: - dev->workbuf.size = CODADX6_WORK_BUF_SIZE; + ret = coda_alloc_aux_buf(dev, &dev->workbuf, + CODADX6_WORK_BUF_SIZE); + if (ret < 0) { + dev_err(&pdev->dev, "failed to allocate work buffer\n"); + v4l2_device_unregister(&dev->v4l2_dev); + return ret; + } + break; + case CODA_7541: + dev->tempbuf.size = CODA7_TEMP_BUF_SIZE; break; - default: - dev->workbuf.size = CODA7_WORK_BUF_SIZE; } - dev->workbuf.vaddr = dma_alloc_coherent(&pdev->dev, dev->workbuf.size, - &dev->workbuf.paddr, - GFP_KERNEL); - if (!dev->workbuf.vaddr) { - dev_err(&pdev->dev, "failed to allocate work buffer\n"); - v4l2_device_unregister(&dev->v4l2_dev); - return -ENOMEM; + if (dev->tempbuf.size) { + ret = coda_alloc_aux_buf(dev, &dev->tempbuf, + dev->tempbuf.size); + if (ret < 0) { + dev_err(&pdev->dev, "failed to allocate temp buffer\n"); + v4l2_device_unregister(&dev->v4l2_dev); + return ret; + } } - if (dev->devtype->product == CODA_DX6) + switch (dev->devtype->product) { + case CODA_DX6: dev->iram_size = CODADX6_IRAM_SIZE; - else + break; + case CODA_7541: dev->iram_size = CODA7_IRAM_SIZE; + break; + } dev->iram_vaddr = gen_pool_alloc(dev->iram_pool, dev->iram_size); if (!dev->iram_vaddr) { dev_err(&pdev->dev, "unable to alloc iram\n"); @@ -2128,12 +3257,9 @@ static int coda_remove(struct platform_device *pdev) v4l2_device_unregister(&dev->v4l2_dev); if (dev->iram_vaddr) gen_pool_free(dev->iram_pool, dev->iram_vaddr, dev->iram_size); - if (dev->codebuf.vaddr) - dma_free_coherent(&pdev->dev, dev->codebuf.size, - &dev->codebuf.vaddr, dev->codebuf.paddr); - if (dev->workbuf.vaddr) - dma_free_coherent(&pdev->dev, dev->workbuf.size, &dev->workbuf.vaddr, - dev->workbuf.paddr); + coda_free_aux_buf(dev, &dev->codebuf); + coda_free_aux_buf(dev, &dev->tempbuf); + coda_free_aux_buf(dev, &dev->workbuf); return 0; } diff --git a/drivers/media/platform/coda.h b/drivers/media/platform/coda.h index ace0bf0a3b9c..4e32e2edea62 100644 --- a/drivers/media/platform/coda.h +++ b/drivers/media/platform/coda.h @@ -43,14 +43,26 @@ #define CODA_STREAM_ENDIAN_SELECT (1 << 0) #define CODA_REG_BIT_FRAME_MEM_CTRL 0x110 #define CODA_IMAGE_ENDIAN_SELECT (1 << 0) +#define CODA_REG_BIT_BIT_STREAM_PARAM 0x114 +#define CODA_BIT_STREAM_END_FLAG (1 << 2) +#define CODA_BIT_DEC_SEQ_INIT_ESCAPE (1 << 0) +#define CODA_REG_BIT_TEMP_BUF_ADDR 0x118 #define CODA_REG_BIT_RD_PTR(x) (0x120 + 8 * (x)) #define CODA_REG_BIT_WR_PTR(x) (0x124 + 8 * (x)) +#define CODA_REG_BIT_FRM_DIS_FLG(x) (0x150 + 4 * (x)) #define CODADX6_REG_BIT_SEARCH_RAM_BASE_ADDR 0x140 #define CODA7_REG_BIT_AXI_SRAM_USE 0x140 -#define CODA7_USE_BIT_ENABLE (1 << 0) +#define CODA7_USE_HOST_ME_ENABLE (1 << 11) +#define CODA7_USE_HOST_OVL_ENABLE (1 << 10) +#define CODA7_USE_HOST_DBK_ENABLE (1 << 9) +#define CODA7_USE_HOST_IP_ENABLE (1 << 8) #define CODA7_USE_HOST_BIT_ENABLE (1 << 7) #define CODA7_USE_ME_ENABLE (1 << 4) -#define CODA7_USE_HOST_ME_ENABLE (1 << 11) +#define CODA7_USE_OVL_ENABLE (1 << 3) +#define CODA7_USE_DBK_ENABLE (1 << 2) +#define CODA7_USE_IP_ENABLE (1 << 1) +#define CODA7_USE_BIT_ENABLE (1 << 0) + #define CODA_REG_BIT_BUSY 0x160 #define CODA_REG_BIT_BUSY_FLAG 1 #define CODA_REG_BIT_RUN_COMMAND 0x164 @@ -84,6 +96,15 @@ #define CODA_MODE_INVALID 0xffff #define CODA_REG_BIT_INT_ENABLE 0x170 #define CODA_INT_INTERRUPT_ENABLE (1 << 3) +#define CODA_REG_BIT_INT_REASON 0x174 +#define CODA7_REG_BIT_RUN_AUX_STD 0x178 +#define CODA_MP4_AUX_MPEG4 0 +#define CODA_MP4_AUX_DIVX3 1 +#define CODA_VPX_AUX_THO 0 +#define CODA_VPX_AUX_VP6 1 +#define CODA_VPX_AUX_VP8 2 +#define CODA_H264_AUX_AVC 0 +#define CODA_H264_AUX_MVC 1 /* * Commands' mailbox: @@ -92,15 +113,89 @@ * issued. */ +/* Decoder Sequence Initialization */ +#define CODA_CMD_DEC_SEQ_BB_START 0x180 +#define CODA_CMD_DEC_SEQ_BB_SIZE 0x184 +#define CODA_CMD_DEC_SEQ_OPTION 0x188 +#define CODA_REORDER_ENABLE (1 << 1) +#define CODADX6_QP_REPORT (1 << 0) +#define CODA7_MP4_DEBLK_ENABLE (1 << 0) +#define CODA_CMD_DEC_SEQ_SRC_SIZE 0x18c +#define CODA_CMD_DEC_SEQ_START_BYTE 0x190 +#define CODA_CMD_DEC_SEQ_PS_BB_START 0x194 +#define CODA_CMD_DEC_SEQ_PS_BB_SIZE 0x198 +#define CODA_CMD_DEC_SEQ_MP4_ASP_CLASS 0x19c +#define CODA_CMD_DEC_SEQ_X264_MV_EN 0x19c +#define CODA_CMD_DEC_SEQ_SPP_CHUNK_SIZE 0x1a0 + +#define CODA7_RET_DEC_SEQ_ASPECT 0x1b0 +#define CODA_RET_DEC_SEQ_SUCCESS 0x1c0 +#define CODA_RET_DEC_SEQ_SRC_FMT 0x1c4 /* SRC_SIZE on CODA7 */ +#define CODA_RET_DEC_SEQ_SRC_SIZE 0x1c4 +#define CODA_RET_DEC_SEQ_SRC_F_RATE 0x1c8 +#define CODA9_RET_DEC_SEQ_ASPECT 0x1c8 +#define CODA_RET_DEC_SEQ_FRAME_NEED 0x1cc +#define CODA_RET_DEC_SEQ_FRAME_DELAY 0x1d0 +#define CODA_RET_DEC_SEQ_INFO 0x1d4 +#define CODA_RET_DEC_SEQ_CROP_LEFT_RIGHT 0x1d8 +#define CODA_RET_DEC_SEQ_CROP_TOP_BOTTOM 0x1dc +#define CODA_RET_DEC_SEQ_NEXT_FRAME_NUM 0x1e0 +#define CODA_RET_DEC_SEQ_ERR_REASON 0x1e0 +#define CODA_RET_DEC_SEQ_FRATE_NR 0x1e4 +#define CODA_RET_DEC_SEQ_FRATE_DR 0x1e8 +#define CODA_RET_DEC_SEQ_JPG_PARA 0x1e4 +#define CODA_RET_DEC_SEQ_JPG_THUMB_IND 0x1e8 + +/* Decoder Picture Run */ +#define CODA_CMD_DEC_PIC_ROT_MODE 0x180 +#define CODA_CMD_DEC_PIC_ROT_ADDR_Y 0x184 +#define CODA_CMD_DEC_PIC_ROT_ADDR_CB 0x188 +#define CODA_CMD_DEC_PIC_ROT_ADDR_CR 0x18c +#define CODA_CMD_DEC_PIC_ROT_STRIDE 0x190 + +#define CODA_CMD_DEC_PIC_OPTION 0x194 +#define CODA_PRE_SCAN_EN (1 << 0) +#define CODA_PRE_SCAN_MODE_DECODE (0 << 1) +#define CODA_PRE_SCAN_MODE_RETURN (1 << 1) +#define CODA_IFRAME_SEARCH_EN (1 << 2) +#define CODA_SKIP_FRAME_MODE (0x3 << 3) +#define CODA_CMD_DEC_PIC_SKIP_NUM 0x198 +#define CODA_CMD_DEC_PIC_CHUNK_SIZE 0x19c +#define CODA_CMD_DEC_PIC_BB_START 0x1a0 +#define CODA_CMD_DEC_PIC_START_BYTE 0x1a4 +#define CODA_RET_DEC_PIC_SIZE 0x1bc +#define CODA_RET_DEC_PIC_FRAME_NUM 0x1c0 +#define CODA_RET_DEC_PIC_FRAME_IDX 0x1c4 +#define CODA_RET_DEC_PIC_ERR_MB 0x1c8 +#define CODA_RET_DEC_PIC_TYPE 0x1cc +#define CODA_PIC_TYPE_MASK 0x7 +#define CODA_PIC_TYPE_MASK_VC1 0x3f +#define CODA9_PIC_TYPE_FIRST_MASK (0x7 << 3) +#define CODA9_PIC_TYPE_IDR_MASK (0x3 << 6) +#define CODA7_PIC_TYPE_H264_NPF_MASK (0x3 << 16) +#define CODA7_PIC_TYPE_INTERLACED (1 << 18) +#define CODA_RET_DEC_PIC_POST 0x1d0 +#define CODA_RET_DEC_PIC_MVC_REPORT 0x1d0 +#define CODA_RET_DEC_PIC_OPTION 0x1d4 +#define CODA_RET_DEC_PIC_SUCCESS 0x1d8 +#define CODA_RET_DEC_PIC_CUR_IDX 0x1dc +#define CODA_RET_DEC_PIC_CROP_LEFT_RIGHT 0x1e0 +#define CODA_RET_DEC_PIC_CROP_TOP_BOTTOM 0x1e4 +#define CODA_RET_DEC_PIC_FRAME_NEED 0x1ec + /* Encoder Sequence Initialization */ #define CODA_CMD_ENC_SEQ_BB_START 0x180 #define CODA_CMD_ENC_SEQ_BB_SIZE 0x184 #define CODA_CMD_ENC_SEQ_OPTION 0x188 +#define CODA7_OPTION_AVCINTRA16X16ONLY_OFFSET 9 #define CODA7_OPTION_GAMMA_OFFSET 8 +#define CODA7_OPTION_RCQPMAX_OFFSET 7 #define CODADX6_OPTION_GAMMA_OFFSET 7 +#define CODA7_OPTION_RCQPMIN_OFFSET 6 #define CODA_OPTION_LIMITQP_OFFSET 6 #define CODA_OPTION_RCINTRAQP_OFFSET 5 #define CODA_OPTION_FMO_OFFSET 4 +#define CODA_OPTION_AVC_AUD_OFFSET 2 #define CODA_OPTION_SLICEREPORT_OFFSET 1 #define CODA_CMD_ENC_SEQ_COD_STD 0x18c #define CODA_STD_MPEG4 0 @@ -169,8 +264,10 @@ #define CODA_FMOPARAM_TYPE_MASK 1 #define CODA_FMOPARAM_SLICENUM_OFFSET 0 #define CODA_FMOPARAM_SLICENUM_MASK 0x0f +#define CODADX6_CMD_ENC_SEQ_INTRA_QP 0x1bc #define CODA7_CMD_ENC_SEQ_SEARCH_BASE 0x1b8 #define CODA7_CMD_ENC_SEQ_SEARCH_SIZE 0x1bc +#define CODA7_CMD_ENC_SEQ_INTRA_QP 0x1c4 #define CODA_CMD_ENC_SEQ_RC_QP_MAX 0x1c8 #define CODA_QPMAX_OFFSET 0 #define CODA_QPMAX_MASK 0x3f @@ -197,18 +294,24 @@ #define CODA_CMD_ENC_PIC_OPTION 0x194 #define CODA_CMD_ENC_PIC_BB_START 0x198 #define CODA_CMD_ENC_PIC_BB_SIZE 0x19c +#define CODA_RET_ENC_FRAME_NUM 0x1c0 #define CODA_RET_ENC_PIC_TYPE 0x1c4 +#define CODA_RET_ENC_PIC_FRAME_IDX 0x1c8 #define CODA_RET_ENC_PIC_SLICE_NUM 0x1cc #define CODA_RET_ENC_PIC_FLAG 0x1d0 +#define CODA_RET_ENC_PIC_SUCCESS 0x1d8 /* Set Frame Buffer */ #define CODA_CMD_SET_FRAME_BUF_NUM 0x180 #define CODA_CMD_SET_FRAME_BUF_STRIDE 0x184 +#define CODA_CMD_SET_FRAME_SLICE_BB_START 0x188 +#define CODA_CMD_SET_FRAME_SLICE_BB_SIZE 0x18c #define CODA7_CMD_SET_FRAME_AXI_BIT_ADDR 0x190 #define CODA7_CMD_SET_FRAME_AXI_IPACDC_ADDR 0x194 #define CODA7_CMD_SET_FRAME_AXI_DBKY_ADDR 0x198 #define CODA7_CMD_SET_FRAME_AXI_DBKC_ADDR 0x19c #define CODA7_CMD_SET_FRAME_AXI_OVL_ADDR 0x1a0 +#define CODA7_CMD_SET_FRAME_MAX_DEC_SIZE 0x1a4 #define CODA7_CMD_SET_FRAME_SOURCE_BUF_STRIDE 0x1a8 /* Encoder Header */ diff --git a/drivers/media/platform/davinci/vpbe_display.c b/drivers/media/platform/davinci/vpbe_display.c index e180ff7282d9..04609cc6eba7 100644 --- a/drivers/media/platform/davinci/vpbe_display.c +++ b/drivers/media/platform/davinci/vpbe_display.c @@ -1743,11 +1743,10 @@ static int vpbe_display_probe(struct platform_device *pdev) printk(KERN_DEBUG "vpbe_display_probe\n"); /* Allocate memory for vpbe_display */ - disp_dev = kzalloc(sizeof(struct vpbe_display), GFP_KERNEL); - if (!disp_dev) { - printk(KERN_ERR "ran out of memory\n"); + disp_dev = devm_kzalloc(&pdev->dev, sizeof(struct vpbe_display), + GFP_KERNEL); + if (!disp_dev) return -ENOMEM; - } spin_lock_init(&disp_dev->dma_queue_lock); /* @@ -1786,26 +1785,24 @@ static int vpbe_display_probe(struct platform_device *pdev) } irq = res->start; - if (request_irq(irq, venc_isr, IRQF_DISABLED, VPBE_DISPLAY_DRIVER, - disp_dev)) { + err = devm_request_irq(&pdev->dev, irq, venc_isr, IRQF_DISABLED, + VPBE_DISPLAY_DRIVER, disp_dev); + if (err) { v4l2_err(&disp_dev->vpbe_dev->v4l2_dev, "Unable to request interrupt\n"); - err = -ENODEV; goto probe_out; } for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) { if (register_device(disp_dev->dev[i], disp_dev, pdev)) { err = -ENODEV; - goto probe_out_irq; + goto probe_out; } } printk(KERN_DEBUG "Successfully completed the probing of vpbe v4l2 device\n"); return 0; -probe_out_irq: - free_irq(res->start, disp_dev); probe_out: for (k = 0; k < VPBE_DISPLAY_MAX_DEVICES; k++) { /* Get the pointer to the layer object */ @@ -1817,7 +1814,6 @@ probe_out: kfree(disp_dev->dev[k]); } } - kfree(disp_dev); return err; } @@ -1830,15 +1826,10 @@ static int vpbe_display_remove(struct platform_device *pdev) struct vpbe_layer *vpbe_display_layer; struct vpbe_display *disp_dev = platform_get_drvdata(pdev); struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; - struct resource *res; int i; v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "vpbe_display_remove\n"); - /* unregister irq */ - res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - free_irq(res->start, disp_dev); - /* deinitialize the vpbe display controller */ if (NULL != vpbe_dev->ops.deinitialize) vpbe_dev->ops.deinitialize(&pdev->dev, vpbe_dev); diff --git a/drivers/media/platform/davinci/vpbe_osd.c b/drivers/media/platform/davinci/vpbe_osd.c index 6ed82e8b297b..d053c2669c1f 100644 --- a/drivers/media/platform/davinci/vpbe_osd.c +++ b/drivers/media/platform/davinci/vpbe_osd.c @@ -1547,61 +1547,36 @@ static int osd_probe(struct platform_device *pdev) const struct platform_device_id *pdev_id; struct osd_state *osd; struct resource *res; - int ret = 0; - osd = kzalloc(sizeof(struct osd_state), GFP_KERNEL); + pdev_id = platform_get_device_id(pdev); + if (!pdev_id) + return -EINVAL; + + osd = devm_kzalloc(&pdev->dev, sizeof(struct osd_state), GFP_KERNEL); if (osd == NULL) return -ENOMEM; - pdev_id = platform_get_device_id(pdev); - if (!pdev_id) { - ret = -EINVAL; - goto free_mem; - } osd->dev = &pdev->dev; osd->vpbe_type = pdev_id->driver_data; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(osd->dev, "Unable to get OSD register address map\n"); - ret = -ENODEV; - goto free_mem; - } + osd->osd_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(osd->osd_base)) + return PTR_ERR(osd->osd_base); + osd->osd_base_phys = res->start; osd->osd_size = resource_size(res); - if (!request_mem_region(osd->osd_base_phys, osd->osd_size, - MODULE_NAME)) { - dev_err(osd->dev, "Unable to reserve OSD MMIO region\n"); - ret = -ENODEV; - goto free_mem; - } - osd->osd_base = ioremap_nocache(res->start, osd->osd_size); - if (!osd->osd_base) { - dev_err(osd->dev, "Unable to map the OSD region\n"); - ret = -ENODEV; - goto release_mem_region; - } spin_lock_init(&osd->lock); osd->ops = osd_ops; platform_set_drvdata(pdev, osd); dev_notice(osd->dev, "OSD sub device probe success\n"); - return ret; -release_mem_region: - release_mem_region(osd->osd_base_phys, osd->osd_size); -free_mem: - kfree(osd); - return ret; + return 0; } static int osd_remove(struct platform_device *pdev) { - struct osd_state *osd = platform_get_drvdata(pdev); - - iounmap((void *)osd->osd_base); - release_mem_region(osd->osd_base_phys, osd->osd_size); - kfree(osd); return 0; } diff --git a/drivers/media/platform/davinci/vpbe_venc.c b/drivers/media/platform/davinci/vpbe_venc.c index 87eef9be08ed..14a023a75d2d 100644 --- a/drivers/media/platform/davinci/vpbe_venc.c +++ b/drivers/media/platform/davinci/vpbe_venc.c @@ -639,105 +639,46 @@ static int venc_probe(struct platform_device *pdev) const struct platform_device_id *pdev_id; struct venc_state *venc; struct resource *res; - int ret; - venc = kzalloc(sizeof(struct venc_state), GFP_KERNEL); + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "No platform data for VENC sub device"); + return -EINVAL; + } + + pdev_id = platform_get_device_id(pdev); + if (!pdev_id) + return -EINVAL; + + venc = devm_kzalloc(&pdev->dev, sizeof(struct venc_state), GFP_KERNEL); if (venc == NULL) return -ENOMEM; - pdev_id = platform_get_device_id(pdev); - if (!pdev_id) { - ret = -EINVAL; - goto free_mem; - } venc->venc_type = pdev_id->driver_data; venc->pdev = &pdev->dev; venc->pdata = pdev->dev.platform_data; - if (NULL == venc->pdata) { - dev_err(venc->pdev, "Unable to get platform data for" - " VENC sub device"); - ret = -ENOENT; - goto free_mem; - } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(venc->pdev, - "Unable to get VENC register address map\n"); - ret = -ENODEV; - goto free_mem; - } - if (!request_mem_region(res->start, resource_size(res), "venc")) { - dev_err(venc->pdev, "Unable to reserve VENC MMIO region\n"); - ret = -ENODEV; - goto free_mem; - } - - venc->venc_base = ioremap_nocache(res->start, resource_size(res)); - if (!venc->venc_base) { - dev_err(venc->pdev, "Unable to map VENC IO space\n"); - ret = -ENODEV; - goto release_venc_mem_region; - } + venc->venc_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(venc->venc_base)) + return PTR_ERR(venc->venc_base); if (venc->venc_type != VPBE_VERSION_1) { res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - if (!res) { - dev_err(venc->pdev, - "Unable to get VDAC_CONFIG address map\n"); - ret = -ENODEV; - goto unmap_venc_io; - } - - if (!request_mem_region(res->start, - resource_size(res), "venc")) { - dev_err(venc->pdev, - "Unable to reserve VDAC_CONFIG MMIO region\n"); - ret = -ENODEV; - goto unmap_venc_io; - } - - venc->vdaccfg_reg = ioremap_nocache(res->start, - resource_size(res)); - if (!venc->vdaccfg_reg) { - dev_err(venc->pdev, - "Unable to map VDAC_CONFIG IO space\n"); - ret = -ENODEV; - goto release_vdaccfg_mem_region; - } + + venc->vdaccfg_reg = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(venc->vdaccfg_reg)) + return PTR_ERR(venc->vdaccfg_reg); } spin_lock_init(&venc->lock); platform_set_drvdata(pdev, venc); dev_notice(venc->pdev, "VENC sub device probe success\n"); - return 0; -release_vdaccfg_mem_region: - release_mem_region(res->start, resource_size(res)); -unmap_venc_io: - iounmap(venc->venc_base); -release_venc_mem_region: - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - release_mem_region(res->start, resource_size(res)); -free_mem: - kfree(venc); - return ret; + return 0; } static int venc_remove(struct platform_device *pdev) { - struct venc_state *venc = platform_get_drvdata(pdev); - struct resource *res; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - iounmap((void *)venc->venc_base); - release_mem_region(res->start, resource_size(res)); - if (venc->venc_type != VPBE_VERSION_1) { - res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - iounmap((void *)venc->vdaccfg_reg); - release_mem_region(res->start, resource_size(res)); - } - kfree(venc); - return 0; } diff --git a/drivers/media/platform/davinci/vpif_capture.c b/drivers/media/platform/davinci/vpif_capture.c index 5514175bbd07..1089834a4efe 100644 --- a/drivers/media/platform/davinci/vpif_capture.c +++ b/drivers/media/platform/davinci/vpif_capture.c @@ -1799,19 +1799,15 @@ static int vpif_s_dv_timings(struct file *file, void *priv, /* Configure video port timings */ - std_info->eav2sav = bt->hbackporch + bt->hfrontporch + - bt->hsync - 8; + std_info->eav2sav = V4L2_DV_BT_BLANKING_WIDTH(bt) - 8; std_info->sav2eav = bt->width; std_info->l1 = 1; std_info->l3 = bt->vsync + bt->vbackporch + 1; + std_info->vsize = V4L2_DV_BT_FRAME_HEIGHT(bt); if (bt->interlaced) { if (bt->il_vbackporch || bt->il_vfrontporch || bt->il_vsync) { - std_info->vsize = bt->height * 2 + - bt->vfrontporch + bt->vsync + bt->vbackporch + - bt->il_vfrontporch + bt->il_vsync + - bt->il_vbackporch; std_info->l5 = std_info->vsize/2 - (bt->vfrontporch - 1); std_info->l7 = std_info->vsize/2 + 1; @@ -1825,8 +1821,6 @@ static int vpif_s_dv_timings(struct file *file, void *priv, return -EINVAL; } } else { - std_info->vsize = bt->height + bt->vfrontporch + - bt->vsync + bt->vbackporch; std_info->l5 = std_info->vsize - (bt->vfrontporch - 1); } strncpy(std_info->name, "Custom timings BT656/1120", VPIF_MAX_NAME); @@ -1979,6 +1973,76 @@ vpif_init_free_channel_objects: return err; } +static int vpif_async_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + int i; + + for (i = 0; i < vpif_obj.config->subdev_count; i++) + if (!strcmp(vpif_obj.config->subdev_info[i].name, + subdev->name)) { + vpif_obj.sd[i] = subdev; + return 0; + } + + return -EINVAL; +} + +static int vpif_probe_complete(void) +{ + struct common_obj *common; + struct channel_obj *ch; + int i, j, err, k; + + for (j = 0; j < VPIF_CAPTURE_MAX_DEVICES; j++) { + ch = vpif_obj.dev[j]; + ch->channel_id = j; + common = &(ch->common[VPIF_VIDEO_INDEX]); + spin_lock_init(&common->irqlock); + mutex_init(&common->lock); + ch->video_dev->lock = &common->lock; + /* Initialize prio member of channel object */ + v4l2_prio_init(&ch->prio); + video_set_drvdata(ch->video_dev, ch); + + /* select input 0 */ + err = vpif_set_input(vpif_obj.config, ch, 0); + if (err) + goto probe_out; + + err = video_register_device(ch->video_dev, + VFL_TYPE_GRABBER, (j ? 1 : 0)); + if (err) + goto probe_out; + } + + v4l2_info(&vpif_obj.v4l2_dev, "VPIF capture driver initialized\n"); + return 0; + +probe_out: + for (k = 0; k < j; k++) { + /* Get the pointer to the channel object */ + ch = vpif_obj.dev[k]; + /* Unregister video device */ + video_unregister_device(ch->video_dev); + } + kfree(vpif_obj.sd); + for (i = 0; i < VPIF_CAPTURE_MAX_DEVICES; i++) { + ch = vpif_obj.dev[i]; + /* Note: does nothing if ch->video_dev == NULL */ + video_device_release(ch->video_dev); + } + v4l2_device_unregister(&vpif_obj.v4l2_dev); + + return err; +} + +static int vpif_async_complete(struct v4l2_async_notifier *notifier) +{ + return vpif_probe_complete(); +} + /** * vpif_probe : This function probes the vpif capture driver * @pdev: platform device pointer @@ -1989,12 +2053,10 @@ vpif_init_free_channel_objects: static __init int vpif_probe(struct platform_device *pdev) { struct vpif_subdev_info *subdevdata; - struct vpif_capture_config *config; - int i, j, k, err; + int i, j, err; int res_idx = 0; struct i2c_adapter *i2c_adap; struct channel_obj *ch; - struct common_obj *common; struct video_device *vfd; struct resource *res; int subdev_count; @@ -2068,10 +2130,9 @@ static __init int vpif_probe(struct platform_device *pdev) } } - i2c_adap = i2c_get_adapter(1); - config = pdev->dev.platform_data; + vpif_obj.config = pdev->dev.platform_data; - subdev_count = config->subdev_count; + subdev_count = vpif_obj.config->subdev_count; vpif_obj.sd = kzalloc(sizeof(struct v4l2_subdev *) * subdev_count, GFP_KERNEL); if (vpif_obj.sd == NULL) { @@ -2080,54 +2141,43 @@ static __init int vpif_probe(struct platform_device *pdev) goto vpif_sd_error; } - for (i = 0; i < subdev_count; i++) { - subdevdata = &config->subdev_info[i]; - vpif_obj.sd[i] = - v4l2_i2c_new_subdev_board(&vpif_obj.v4l2_dev, - i2c_adap, - &subdevdata->board_info, - NULL); - - if (!vpif_obj.sd[i]) { - vpif_err("Error registering v4l2 subdevice\n"); - err = -ENODEV; + if (!vpif_obj.config->asd_sizes) { + i2c_adap = i2c_get_adapter(1); + for (i = 0; i < subdev_count; i++) { + subdevdata = &vpif_obj.config->subdev_info[i]; + vpif_obj.sd[i] = + v4l2_i2c_new_subdev_board(&vpif_obj.v4l2_dev, + i2c_adap, + &subdevdata-> + board_info, + NULL); + + if (!vpif_obj.sd[i]) { + vpif_err("Error registering v4l2 subdevice\n"); + err = -ENOMEM; + goto probe_subdev_out; + } + v4l2_info(&vpif_obj.v4l2_dev, + "registered sub device %s\n", + subdevdata->name); + } + vpif_probe_complete(); + } else { + vpif_obj.notifier.subdevs = vpif_obj.config->asd; + vpif_obj.notifier.num_subdevs = vpif_obj.config->asd_sizes[0]; + vpif_obj.notifier.bound = vpif_async_bound; + vpif_obj.notifier.complete = vpif_async_complete; + err = v4l2_async_notifier_register(&vpif_obj.v4l2_dev, + &vpif_obj.notifier); + if (err) { + vpif_err("Error registering async notifier\n"); + err = -EINVAL; goto probe_subdev_out; } - v4l2_info(&vpif_obj.v4l2_dev, "registered sub device %s\n", - subdevdata->name); } - for (j = 0; j < VPIF_CAPTURE_MAX_DEVICES; j++) { - ch = vpif_obj.dev[j]; - ch->channel_id = j; - common = &(ch->common[VPIF_VIDEO_INDEX]); - spin_lock_init(&common->irqlock); - mutex_init(&common->lock); - ch->video_dev->lock = &common->lock; - /* Initialize prio member of channel object */ - v4l2_prio_init(&ch->prio); - video_set_drvdata(ch->video_dev, ch); - - /* select input 0 */ - err = vpif_set_input(config, ch, 0); - if (err) - goto probe_out; - - err = video_register_device(ch->video_dev, - VFL_TYPE_GRABBER, (j ? 1 : 0)); - if (err) - goto probe_out; - } - v4l2_info(&vpif_obj.v4l2_dev, "VPIF capture driver initialized\n"); return 0; -probe_out: - for (k = 0; k < j; k++) { - /* Get the pointer to the channel object */ - ch = vpif_obj.dev[k]; - /* Unregister video device */ - video_unregister_device(ch->video_dev); - } probe_subdev_out: /* free sub devices memory */ kfree(vpif_obj.sd); diff --git a/drivers/media/platform/davinci/vpif_capture.h b/drivers/media/platform/davinci/vpif_capture.h index 0ebb31260369..5a29d9a0cae1 100644 --- a/drivers/media/platform/davinci/vpif_capture.h +++ b/drivers/media/platform/davinci/vpif_capture.h @@ -142,6 +142,8 @@ struct vpif_device { struct v4l2_device v4l2_dev; struct channel_obj *dev[VPIF_CAPTURE_NUM_CHANNELS]; struct v4l2_subdev **sd; + struct v4l2_async_notifier notifier; + struct vpif_capture_config *config; }; struct vpif_config_params { diff --git a/drivers/media/platform/davinci/vpif_display.c b/drivers/media/platform/davinci/vpif_display.c index e6e573650250..c31bcf129a5d 100644 --- a/drivers/media/platform/davinci/vpif_display.c +++ b/drivers/media/platform/davinci/vpif_display.c @@ -1436,19 +1436,15 @@ static int vpif_s_dv_timings(struct file *file, void *priv, /* Configure video port timings */ - std_info->eav2sav = bt->hbackporch + bt->hfrontporch + - bt->hsync - 8; + std_info->eav2sav = V4L2_DV_BT_BLANKING_WIDTH(bt) - 8; std_info->sav2eav = bt->width; std_info->l1 = 1; std_info->l3 = bt->vsync + bt->vbackporch + 1; + std_info->vsize = V4L2_DV_BT_FRAME_HEIGHT(bt); if (bt->interlaced) { if (bt->il_vbackporch || bt->il_vfrontporch || bt->il_vsync) { - std_info->vsize = bt->height * 2 + - bt->vfrontporch + bt->vsync + bt->vbackporch + - bt->il_vfrontporch + bt->il_vsync + - bt->il_vbackporch; std_info->l5 = std_info->vsize/2 - (bt->vfrontporch - 1); std_info->l7 = std_info->vsize/2 + 1; @@ -1462,8 +1458,6 @@ static int vpif_s_dv_timings(struct file *file, void *priv, return -EINVAL; } } else { - std_info->vsize = bt->height + bt->vfrontporch + - bt->vsync + bt->vbackporch; std_info->l5 = std_info->vsize - (bt->vfrontporch - 1); } strncpy(std_info->name, "Custom timings BT656/1120", @@ -1618,6 +1612,102 @@ vpif_init_free_channel_objects: return err; } +static int vpif_async_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + int i; + + for (i = 0; i < vpif_obj.config->subdev_count; i++) + if (!strcmp(vpif_obj.config->subdevinfo[i].name, + subdev->name)) { + vpif_obj.sd[i] = subdev; + vpif_obj.sd[i]->grp_id = 1 << i; + return 0; + } + + return -EINVAL; +} + +static int vpif_probe_complete(void) +{ + struct common_obj *common; + struct channel_obj *ch; + int j, err, k; + + for (j = 0; j < VPIF_DISPLAY_MAX_DEVICES; j++) { + ch = vpif_obj.dev[j]; + /* Initialize field of the channel objects */ + atomic_set(&ch->usrs, 0); + for (k = 0; k < VPIF_NUMOBJECTS; k++) { + ch->common[k].numbuffers = 0; + common = &ch->common[k]; + common->io_usrs = 0; + common->started = 0; + spin_lock_init(&common->irqlock); + mutex_init(&common->lock); + common->numbuffers = 0; + common->set_addr = NULL; + common->ytop_off = 0; + common->ybtm_off = 0; + common->ctop_off = 0; + common->cbtm_off = 0; + common->cur_frm = NULL; + common->next_frm = NULL; + memset(&common->fmt, 0, sizeof(common->fmt)); + common->numbuffers = config_params.numbuffers[k]; + } + ch->initialized = 0; + if (vpif_obj.config->subdev_count) + ch->sd = vpif_obj.sd[0]; + ch->channel_id = j; + if (j < 2) + ch->common[VPIF_VIDEO_INDEX].numbuffers = + config_params.numbuffers[ch->channel_id]; + else + ch->common[VPIF_VIDEO_INDEX].numbuffers = 0; + + memset(&ch->vpifparams, 0, sizeof(ch->vpifparams)); + + /* Initialize prio member of channel object */ + v4l2_prio_init(&ch->prio); + ch->common[VPIF_VIDEO_INDEX].fmt.type = + V4L2_BUF_TYPE_VIDEO_OUTPUT; + ch->video_dev->lock = &common->lock; + video_set_drvdata(ch->video_dev, ch); + + /* select output 0 */ + err = vpif_set_output(vpif_obj.config, ch, 0); + if (err) + goto probe_out; + + /* register video device */ + vpif_dbg(1, debug, "channel=%x,channel->video_dev=%x\n", + (int)ch, (int)&ch->video_dev); + + err = video_register_device(ch->video_dev, + VFL_TYPE_GRABBER, (j ? 3 : 2)); + if (err < 0) + goto probe_out; + } + + return 0; + +probe_out: + for (k = 0; k < j; k++) { + ch = vpif_obj.dev[k]; + video_unregister_device(ch->video_dev); + video_device_release(ch->video_dev); + ch->video_dev = NULL; + } + return err; +} + +static int vpif_async_complete(struct v4l2_async_notifier *notifier) +{ + return vpif_probe_complete(); +} + /* * vpif_probe: This function creates device entries by register itself to the * V4L2 driver and initializes fields of each channel objects @@ -1625,11 +1715,9 @@ vpif_init_free_channel_objects: static __init int vpif_probe(struct platform_device *pdev) { struct vpif_subdev_info *subdevdata; - struct vpif_display_config *config; - int i, j = 0, k, err = 0; + int i, j = 0, err = 0; int res_idx = 0; struct i2c_adapter *i2c_adap; - struct common_obj *common; struct channel_obj *ch; struct video_device *vfd; struct resource *res; @@ -1708,11 +1796,9 @@ static __init int vpif_probe(struct platform_device *pdev) size/2; } } - - i2c_adap = i2c_get_adapter(1); - config = pdev->dev.platform_data; - subdev_count = config->subdev_count; - subdevdata = config->subdevinfo; + vpif_obj.config = pdev->dev.platform_data; + subdev_count = vpif_obj.config->subdev_count; + subdevdata = vpif_obj.config->subdevinfo; vpif_obj.sd = kzalloc(sizeof(struct v4l2_subdev *) * subdev_count, GFP_KERNEL); if (vpif_obj.sd == NULL) { @@ -1721,86 +1807,41 @@ static __init int vpif_probe(struct platform_device *pdev) goto vpif_sd_error; } - for (i = 0; i < subdev_count; i++) { - vpif_obj.sd[i] = v4l2_i2c_new_subdev_board(&vpif_obj.v4l2_dev, - i2c_adap, - &subdevdata[i].board_info, - NULL); - if (!vpif_obj.sd[i]) { - vpif_err("Error registering v4l2 subdevice\n"); - err = -ENODEV; - goto probe_subdev_out; - } - - if (vpif_obj.sd[i]) - vpif_obj.sd[i]->grp_id = 1 << i; - } - - for (j = 0; j < VPIF_DISPLAY_MAX_DEVICES; j++) { - ch = vpif_obj.dev[j]; - /* Initialize field of the channel objects */ - atomic_set(&ch->usrs, 0); - for (k = 0; k < VPIF_NUMOBJECTS; k++) { - ch->common[k].numbuffers = 0; - common = &ch->common[k]; - common->io_usrs = 0; - common->started = 0; - spin_lock_init(&common->irqlock); - mutex_init(&common->lock); - common->numbuffers = 0; - common->set_addr = NULL; - common->ytop_off = common->ybtm_off = 0; - common->ctop_off = common->cbtm_off = 0; - common->cur_frm = common->next_frm = NULL; - memset(&common->fmt, 0, sizeof(common->fmt)); - common->numbuffers = config_params.numbuffers[k]; + if (!vpif_obj.config->asd_sizes) { + i2c_adap = i2c_get_adapter(1); + for (i = 0; i < subdev_count; i++) { + vpif_obj.sd[i] = + v4l2_i2c_new_subdev_board(&vpif_obj.v4l2_dev, + i2c_adap, + &subdevdata[i]. + board_info, + NULL); + if (!vpif_obj.sd[i]) { + vpif_err("Error registering v4l2 subdevice\n"); + err = -ENODEV; + goto probe_subdev_out; + } + if (vpif_obj.sd[i]) + vpif_obj.sd[i]->grp_id = 1 << i; + } + vpif_probe_complete(); + } else { + vpif_obj.notifier.subdevs = vpif_obj.config->asd; + vpif_obj.notifier.num_subdevs = vpif_obj.config->asd_sizes[0]; + vpif_obj.notifier.bound = vpif_async_bound; + vpif_obj.notifier.complete = vpif_async_complete; + err = v4l2_async_notifier_register(&vpif_obj.v4l2_dev, + &vpif_obj.notifier); + if (err) { + vpif_err("Error registering async notifier\n"); + err = -EINVAL; + goto probe_subdev_out; } - ch->initialized = 0; - if (subdev_count) - ch->sd = vpif_obj.sd[0]; - ch->channel_id = j; - if (j < 2) - ch->common[VPIF_VIDEO_INDEX].numbuffers = - config_params.numbuffers[ch->channel_id]; - else - ch->common[VPIF_VIDEO_INDEX].numbuffers = 0; - - memset(&ch->vpifparams, 0, sizeof(ch->vpifparams)); - - /* Initialize prio member of channel object */ - v4l2_prio_init(&ch->prio); - ch->common[VPIF_VIDEO_INDEX].fmt.type = - V4L2_BUF_TYPE_VIDEO_OUTPUT; - ch->video_dev->lock = &common->lock; - video_set_drvdata(ch->video_dev, ch); - - /* select output 0 */ - err = vpif_set_output(config, ch, 0); - if (err) - goto probe_out; - - /* register video device */ - vpif_dbg(1, debug, "channel=%x,channel->video_dev=%x\n", - (int)ch, (int)&ch->video_dev); - - err = video_register_device(ch->video_dev, - VFL_TYPE_GRABBER, (j ? 3 : 2)); - if (err < 0) - goto probe_out; } - v4l2_info(&vpif_obj.v4l2_dev, - " VPIF display driver initialized\n"); return 0; -probe_out: - for (k = 0; k < j; k++) { - ch = vpif_obj.dev[k]; - video_unregister_device(ch->video_dev); - video_device_release(ch->video_dev); - ch->video_dev = NULL; - } probe_subdev_out: kfree(vpif_obj.sd); vpif_sd_error: diff --git a/drivers/media/platform/davinci/vpif_display.h b/drivers/media/platform/davinci/vpif_display.h index 5d87fc86e580..4d0485b99a80 100644 --- a/drivers/media/platform/davinci/vpif_display.h +++ b/drivers/media/platform/davinci/vpif_display.h @@ -148,7 +148,8 @@ struct vpif_device { struct v4l2_device v4l2_dev; struct channel_obj *dev[VPIF_DISPLAY_NUM_CHANNELS]; struct v4l2_subdev **sd; - + struct v4l2_async_notifier notifier; + struct vpif_display_config *config; }; struct vpif_config_params { diff --git a/drivers/media/platform/davinci/vpss.c b/drivers/media/platform/davinci/vpss.c index 8a2f01e344ee..31120b4a4a33 100644 --- a/drivers/media/platform/davinci/vpss.c +++ b/drivers/media/platform/davinci/vpss.c @@ -21,6 +21,7 @@ #include <linux/platform_device.h> #include <linux/io.h> #include <linux/pm_runtime.h> +#include <linux/err.h> #include <media/davinci/vpss.h> @@ -404,9 +405,8 @@ EXPORT_SYMBOL(dm365_vpss_set_pg_frame_size); static int vpss_probe(struct platform_device *pdev) { - struct resource *r1, *r2; + struct resource *res; char *platform_name; - int status; if (!pdev->dev.platform_data) { dev_err(&pdev->dev, "no platform data\n"); @@ -427,38 +427,19 @@ static int vpss_probe(struct platform_device *pdev) } dev_info(&pdev->dev, "%s vpss probed\n", platform_name); - r1 = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!r1) - return -ENOENT; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - r1 = request_mem_region(r1->start, resource_size(r1), r1->name); - if (!r1) - return -EBUSY; - - oper_cfg.vpss_regs_base0 = ioremap(r1->start, resource_size(r1)); - if (!oper_cfg.vpss_regs_base0) { - status = -EBUSY; - goto fail1; - } + oper_cfg.vpss_regs_base0 = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(oper_cfg.vpss_regs_base0)) + return PTR_ERR(oper_cfg.vpss_regs_base0); if (oper_cfg.platform == DM355 || oper_cfg.platform == DM365) { - r2 = platform_get_resource(pdev, IORESOURCE_MEM, 1); - if (!r2) { - status = -ENOENT; - goto fail2; - } - r2 = request_mem_region(r2->start, resource_size(r2), r2->name); - if (!r2) { - status = -EBUSY; - goto fail2; - } - - oper_cfg.vpss_regs_base1 = ioremap(r2->start, - resource_size(r2)); - if (!oper_cfg.vpss_regs_base1) { - status = -EBUSY; - goto fail3; - } + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + + oper_cfg.vpss_regs_base1 = devm_ioremap_resource(&pdev->dev, + res); + if (IS_ERR(oper_cfg.vpss_regs_base1)) + return PTR_ERR(oper_cfg.vpss_regs_base1); } if (oper_cfg.platform == DM355) { @@ -493,30 +474,13 @@ static int vpss_probe(struct platform_device *pdev) spin_lock_init(&oper_cfg.vpss_lock); dev_info(&pdev->dev, "%s vpss probe success\n", platform_name); - return 0; -fail3: - release_mem_region(r2->start, resource_size(r2)); -fail2: - iounmap(oper_cfg.vpss_regs_base0); -fail1: - release_mem_region(r1->start, resource_size(r1)); - return status; + return 0; } static int vpss_remove(struct platform_device *pdev) { - struct resource *res; - pm_runtime_disable(&pdev->dev); - iounmap(oper_cfg.vpss_regs_base0); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - release_mem_region(res->start, resource_size(res)); - if (oper_cfg.platform == DM355 || oper_cfg.platform == DM365) { - iounmap(oper_cfg.vpss_regs_base1); - res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - release_mem_region(res->start, resource_size(res)); - } return 0; } diff --git a/drivers/media/platform/exynos-gsc/gsc-core.c b/drivers/media/platform/exynos-gsc/gsc-core.c index 559fab2a2d67..9d0cc04d7ab7 100644 --- a/drivers/media/platform/exynos-gsc/gsc-core.c +++ b/drivers/media/platform/exynos-gsc/gsc-core.c @@ -1122,10 +1122,14 @@ static int gsc_probe(struct platform_device *pdev) goto err_clk; } - ret = gsc_register_m2m_device(gsc); + ret = v4l2_device_register(dev, &gsc->v4l2_dev); if (ret) goto err_clk; + ret = gsc_register_m2m_device(gsc); + if (ret) + goto err_v4l2; + platform_set_drvdata(pdev, gsc); pm_runtime_enable(dev); ret = pm_runtime_get_sync(&pdev->dev); @@ -1147,6 +1151,8 @@ err_pm: pm_runtime_put(dev); err_m2m: gsc_unregister_m2m_device(gsc); +err_v4l2: + v4l2_device_unregister(&gsc->v4l2_dev); err_clk: gsc_clk_put(gsc); return ret; @@ -1157,6 +1163,7 @@ static int gsc_remove(struct platform_device *pdev) struct gsc_dev *gsc = platform_get_drvdata(pdev); gsc_unregister_m2m_device(gsc); + v4l2_device_unregister(&gsc->v4l2_dev); vb2_dma_contig_cleanup_ctx(gsc->alloc_ctx); pm_runtime_disable(&pdev->dev); @@ -1210,12 +1217,12 @@ static int gsc_resume(struct device *dev) spin_unlock_irqrestore(&gsc->slock, flags); return 0; } - gsc_hw_set_sw_reset(gsc); - gsc_wait_reset(gsc); - spin_unlock_irqrestore(&gsc->slock, flags); - return gsc_m2m_resume(gsc); + if (!pm_runtime_suspended(dev)) + return gsc_runtime_resume(dev); + + return 0; } static int gsc_suspend(struct device *dev) @@ -1227,7 +1234,10 @@ static int gsc_suspend(struct device *dev) if (test_and_set_bit(ST_SUSPEND, &gsc->state)) return 0; - return gsc_m2m_suspend(gsc); + if (!pm_runtime_suspended(dev)) + return gsc_runtime_suspend(dev); + + return 0; } static const struct dev_pm_ops gsc_pm_ops = { diff --git a/drivers/media/platform/exynos-gsc/gsc-core.h b/drivers/media/platform/exynos-gsc/gsc-core.h index cc19bba09bd1..76435d3bf62d 100644 --- a/drivers/media/platform/exynos-gsc/gsc-core.h +++ b/drivers/media/platform/exynos-gsc/gsc-core.h @@ -343,6 +343,7 @@ struct gsc_dev { unsigned long state; struct vb2_alloc_ctx *alloc_ctx; struct video_device vdev; + struct v4l2_device v4l2_dev; }; /** diff --git a/drivers/media/platform/exynos-gsc/gsc-m2m.c b/drivers/media/platform/exynos-gsc/gsc-m2m.c index 40a73f7d20da..e576ff2de3de 100644 --- a/drivers/media/platform/exynos-gsc/gsc-m2m.c +++ b/drivers/media/platform/exynos-gsc/gsc-m2m.c @@ -751,6 +751,7 @@ int gsc_register_m2m_device(struct gsc_dev *gsc) gsc->vdev.release = video_device_release_empty; gsc->vdev.lock = &gsc->lock; gsc->vdev.vfl_dir = VFL_DIR_M2M; + gsc->vdev.v4l2_dev = &gsc->v4l2_dev; snprintf(gsc->vdev.name, sizeof(gsc->vdev.name), "%s.%d:m2m", GSC_MODULE_NAME, gsc->id); diff --git a/drivers/media/platform/exynos4-is/fimc-core.c b/drivers/media/platform/exynos4-is/fimc-core.c index 6489c5160ee8..3d66d88ea3a1 100644 --- a/drivers/media/platform/exynos4-is/fimc-core.c +++ b/drivers/media/platform/exynos4-is/fimc-core.c @@ -1110,6 +1110,8 @@ static int fimc_remove(struct platform_device *pdev) struct fimc_dev *fimc = platform_get_drvdata(pdev); pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + clk_disable(fimc->clock[CLK_GATE]); pm_runtime_set_suspended(&pdev->dev); fimc_unregister_capture_subdev(fimc); diff --git a/drivers/media/platform/exynos4-is/fimc-is-i2c.c b/drivers/media/platform/exynos4-is/fimc-is-i2c.c index 993055605214..371cad4fcce9 100644 --- a/drivers/media/platform/exynos4-is/fimc-is-i2c.c +++ b/drivers/media/platform/exynos4-is/fimc-is-i2c.c @@ -81,21 +81,46 @@ static int fimc_is_i2c_remove(struct platform_device *pdev) return 0; } -static int fimc_is_i2c_suspend(struct device *dev) +#if defined(CONFIG_PM_RUNTIME) || defined(CONFIG_PM_SLEEP) +static int fimc_is_i2c_runtime_suspend(struct device *dev) { struct fimc_is_i2c *isp_i2c = dev_get_drvdata(dev); + clk_disable_unprepare(isp_i2c->clock); return 0; } -static int fimc_is_i2c_resume(struct device *dev) +static int fimc_is_i2c_runtime_resume(struct device *dev) { struct fimc_is_i2c *isp_i2c = dev_get_drvdata(dev); + return clk_prepare_enable(isp_i2c->clock); } +#endif -static UNIVERSAL_DEV_PM_OPS(fimc_is_i2c_pm_ops, fimc_is_i2c_suspend, - fimc_is_i2c_resume, NULL); +#ifdef CONFIG_PM_SLEEP +static int fimc_is_i2c_suspend(struct device *dev) +{ + if (pm_runtime_suspended(dev)) + return 0; + + return fimc_is_i2c_runtime_suspend(dev); +} + +static int fimc_is_i2c_resume(struct device *dev) +{ + if (pm_runtime_suspended(dev)) + return 0; + + return fimc_is_i2c_runtime_resume(dev); +} +#endif + +static struct dev_pm_ops fimc_is_i2c_pm_ops = { + SET_RUNTIME_PM_OPS(fimc_is_i2c_runtime_suspend, + fimc_is_i2c_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(fimc_is_i2c_suspend, fimc_is_i2c_resume) +}; static const struct of_device_id fimc_is_i2c_of_match[] = { { .compatible = FIMC_IS_I2C_COMPATIBLE }, diff --git a/drivers/media/platform/exynos4-is/fimc-is-param.c b/drivers/media/platform/exynos4-is/fimc-is-param.c index c7e7f694c6ed..9bf3ddd9e028 100644 --- a/drivers/media/platform/exynos4-is/fimc-is-param.c +++ b/drivers/media/platform/exynos4-is/fimc-is-param.c @@ -56,7 +56,7 @@ static void __fimc_is_hw_update_param_sensor_framerate(struct fimc_is *is) __hw_param_copy(dst, src); } -int __fimc_is_hw_update_param(struct fimc_is *is, u32 offset) +static int __fimc_is_hw_update_param(struct fimc_is *is, u32 offset) { struct is_param_region *par = &is->is_p_region->parameter; struct chain_config *cfg = &is->config[is->config_index]; @@ -287,7 +287,7 @@ void __is_set_sensor(struct fimc_is *is, int fps) fimc_is_set_param_bit(is, PARAM_ISP_OTF_INPUT); } -void __is_set_init_isp_aa(struct fimc_is *is) +static void __maybe_unused __is_set_init_isp_aa(struct fimc_is *is) { struct isp_param *isp; diff --git a/drivers/media/platform/exynos4-is/fimc-is-regs.c b/drivers/media/platform/exynos4-is/fimc-is-regs.c index 63c68ec7cfa4..f758e2694fa3 100644 --- a/drivers/media/platform/exynos4-is/fimc-is-regs.c +++ b/drivers/media/platform/exynos4-is/fimc-is-regs.c @@ -96,7 +96,7 @@ int fimc_is_hw_set_param(struct fimc_is *is) return 0; } -int fimc_is_hw_set_tune(struct fimc_is *is) +static int __maybe_unused fimc_is_hw_set_tune(struct fimc_is *is) { fimc_is_hw_wait_intmsr0_intmsd0(is); @@ -236,7 +236,7 @@ int fimc_is_itf_mode_change(struct fimc_is *is) fimc_is_hw_change_mode(is); ret = fimc_is_wait_event(is, IS_ST_CHANGE_MODE, 1, FIMC_IS_CONFIG_TIMEOUT); - if (!ret < 0) + if (ret < 0) dev_err(&is->pdev->dev, "%s(): mode change (%d) timeout\n", __func__, is->config_index); return ret; diff --git a/drivers/media/platform/exynos4-is/fimc-is.c b/drivers/media/platform/exynos4-is/fimc-is.c index 2276fdc213c5..9770fa98d6a1 100644 --- a/drivers/media/platform/exynos4-is/fimc-is.c +++ b/drivers/media/platform/exynos4-is/fimc-is.c @@ -993,3 +993,4 @@ module_exit(fimc_is_module_exit); MODULE_ALIAS("platform:" FIMC_IS_DRV_NAME); MODULE_AUTHOR("Younghwan Joo <yhwan.joo@samsung.com>"); MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/exynos4-is/fimc-isp.c b/drivers/media/platform/exynos4-is/fimc-isp.c index cf520a7d7f71..d2e6cba3566d 100644 --- a/drivers/media/platform/exynos4-is/fimc-isp.c +++ b/drivers/media/platform/exynos4-is/fimc-isp.c @@ -672,6 +672,8 @@ int fimc_isp_subdev_create(struct fimc_isp *isp) mutex_init(&isp->subdev_lock); v4l2_subdev_init(sd, &fimc_is_subdev_ops); + + sd->owner = THIS_MODULE; sd->grp_id = GRP_ID_FIMC_IS; sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; snprintf(sd->name, sizeof(sd->name), "FIMC-IS-ISP"); diff --git a/drivers/media/platform/exynos4-is/fimc-lite.c b/drivers/media/platform/exynos4-is/fimc-lite.c index 08fbfedea90f..e5798f70d149 100644 --- a/drivers/media/platform/exynos4-is/fimc-lite.c +++ b/drivers/media/platform/exynos4-is/fimc-lite.c @@ -90,7 +90,7 @@ static const struct fimc_fmt fimc_lite_formats[] = { .name = "RAW10 (GRBG)", .fourcc = V4L2_PIX_FMT_SGRBG10, .colorspace = V4L2_COLORSPACE_SRGB, - .depth = { 10 }, + .depth = { 16 }, .color = FIMC_FMT_RAW10, .memplanes = 1, .mbus_code = V4L2_MBUS_FMT_SGRBG10_1X10, @@ -99,7 +99,7 @@ static const struct fimc_fmt fimc_lite_formats[] = { .name = "RAW12 (GRBG)", .fourcc = V4L2_PIX_FMT_SGRBG12, .colorspace = V4L2_COLORSPACE_SRGB, - .depth = { 12 }, + .depth = { 16 }, .color = FIMC_FMT_RAW12, .memplanes = 1, .mbus_code = V4L2_MBUS_FMT_SGRBG12_1X12, @@ -1504,16 +1504,17 @@ static int fimc_lite_probe(struct platform_device *pdev) struct resource *res; int ret; + if (!dev->of_node) + return -ENODEV; + fimc = devm_kzalloc(dev, sizeof(*fimc), GFP_KERNEL); if (!fimc) return -ENOMEM; - if (dev->of_node) { - of_id = of_match_node(flite_of_match, dev->of_node); - if (of_id) - drv_data = (struct flite_drvdata *)of_id->data; - fimc->index = of_alias_get_id(dev->of_node, "fimc-lite"); - } + of_id = of_match_node(flite_of_match, dev->of_node); + if (of_id) + drv_data = (struct flite_drvdata *)of_id->data; + fimc->index = of_alias_get_id(dev->of_node, "fimc-lite"); if (!drv_data || fimc->index >= drv_data->num_instances || fimc->index < 0) { diff --git a/drivers/media/platform/exynos4-is/media-dev.c b/drivers/media/platform/exynos4-is/media-dev.c index f8c66b434fd6..a83511278317 100644 --- a/drivers/media/platform/exynos4-is/media-dev.c +++ b/drivers/media/platform/exynos4-is/media-dev.c @@ -1149,7 +1149,6 @@ static void fimc_md_put_clocks(struct fimc_md *fmd) while (--i >= 0) { if (IS_ERR(fmd->camclk[i].clock)) continue; - clk_unprepare(fmd->camclk[i].clock); clk_put(fmd->camclk[i].clock); fmd->camclk[i].clock = ERR_PTR(-EINVAL); } @@ -1168,7 +1167,7 @@ static int fimc_md_get_clocks(struct fimc_md *fmd) struct device *dev = NULL; char clk_name[32]; struct clk *clock; - int ret, i; + int i, ret = 0; for (i = 0; i < FIMC_MAX_CAMCLKS; i++) fmd->camclk[i].clock = ERR_PTR(-EINVAL); @@ -1186,12 +1185,6 @@ static int fimc_md_get_clocks(struct fimc_md *fmd) ret = PTR_ERR(clock); break; } - ret = clk_prepare(clock); - if (ret < 0) { - clk_put(clock); - fmd->camclk[i].clock = ERR_PTR(-EINVAL); - break; - } fmd->camclk[i].clock = clock; } if (ret) @@ -1248,7 +1241,7 @@ static int __fimc_md_set_camclk(struct fimc_md *fmd, ret = pm_runtime_get_sync(fmd->pmf); if (ret < 0) return ret; - ret = clk_enable(camclk->clock); + ret = clk_prepare_enable(camclk->clock); dbg("Enabled camclk %d: f: %lu", si->clk_id, clk_get_rate(camclk->clock)); } @@ -1259,7 +1252,7 @@ static int __fimc_md_set_camclk(struct fimc_md *fmd, return 0; if (--camclk->use_count == 0) { - clk_disable(camclk->clock); + clk_disable_unprepare(camclk->clock); pm_runtime_put(fmd->pmf); dbg("Disabled camclk %d", si->clk_id); } @@ -1529,9 +1522,9 @@ static int fimc_md_probe(struct platform_device *pdev) err_unlock: mutex_unlock(&fmd->media_dev.graph_mutex); err_clk: - media_device_unregister(&fmd->media_dev); fimc_md_put_clocks(fmd); fimc_md_unregister_entities(fmd); + media_device_unregister(&fmd->media_dev); err_md: v4l2_device_unregister(&fmd->v4l2_dev); return ret; @@ -1543,6 +1536,8 @@ static int fimc_md_remove(struct platform_device *pdev) if (!fmd) return 0; + + v4l2_device_unregister(&fmd->v4l2_dev); device_remove_file(&pdev->dev, &dev_attr_subdev_conf_mode); fimc_md_unregister_entities(fmd); fimc_md_pipelines_free(fmd); diff --git a/drivers/media/platform/marvell-ccic/cafe-driver.c b/drivers/media/platform/marvell-ccic/cafe-driver.c index 1f079ff33d4b..562845361246 100644 --- a/drivers/media/platform/marvell-ccic/cafe-driver.c +++ b/drivers/media/platform/marvell-ccic/cafe-driver.c @@ -399,7 +399,7 @@ static void cafe_ctlr_init(struct mcam_camera *mcam) } -static void cafe_ctlr_power_up(struct mcam_camera *mcam) +static int cafe_ctlr_power_up(struct mcam_camera *mcam) { /* * Part one of the sensor dance: turn the global @@ -414,6 +414,8 @@ static void cafe_ctlr_power_up(struct mcam_camera *mcam) */ mcam_reg_write(mcam, REG_GPR, GPR_C1EN|GPR_C0EN); /* pwr up, reset */ mcam_reg_write(mcam, REG_GPR, GPR_C1EN|GPR_C0EN|GPR_C0); + + return 0; } static void cafe_ctlr_power_down(struct mcam_camera *mcam) diff --git a/drivers/media/platform/marvell-ccic/mcam-core.c b/drivers/media/platform/marvell-ccic/mcam-core.c index 0821ed08c122..5184887b155c 100644 --- a/drivers/media/platform/marvell-ccic/mcam-core.c +++ b/drivers/media/platform/marvell-ccic/mcam-core.c @@ -19,6 +19,7 @@ #include <linux/delay.h> #include <linux/vmalloc.h> #include <linux/io.h> +#include <linux/clk.h> #include <linux/videodev2.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> @@ -93,6 +94,9 @@ MODULE_PARM_DESC(buffer_mode, #define CF_CONFIG_NEEDED 4 /* Must configure hardware */ #define CF_SINGLE_BUFFER 5 /* Running with a single buffer */ #define CF_SG_RESTART 6 /* SG restart needed */ +#define CF_FRAME_SOF0 7 /* Frame 0 started */ +#define CF_FRAME_SOF1 8 +#define CF_FRAME_SOF2 9 #define sensor_call(cam, o, f, args...) \ v4l2_subdev_call(cam->sensor, o, f, ##args) @@ -101,6 +105,7 @@ static struct mcam_format_struct { __u8 *desc; __u32 pixelformat; int bpp; /* Bytes per pixel */ + bool planar; enum v4l2_mbus_pixelcode mbus_code; } mcam_formats[] = { { @@ -108,24 +113,56 @@ static struct mcam_format_struct { .pixelformat = V4L2_PIX_FMT_YUYV, .mbus_code = V4L2_MBUS_FMT_YUYV8_2X8, .bpp = 2, + .planar = false, + }, + { + .desc = "UYVY 4:2:2", + .pixelformat = V4L2_PIX_FMT_UYVY, + .mbus_code = V4L2_MBUS_FMT_YUYV8_2X8, + .bpp = 2, + .planar = false, + }, + { + .desc = "YUV 4:2:2 PLANAR", + .pixelformat = V4L2_PIX_FMT_YUV422P, + .mbus_code = V4L2_MBUS_FMT_YUYV8_2X8, + .bpp = 2, + .planar = true, + }, + { + .desc = "YUV 4:2:0 PLANAR", + .pixelformat = V4L2_PIX_FMT_YUV420, + .mbus_code = V4L2_MBUS_FMT_YUYV8_2X8, + .bpp = 2, + .planar = true, + }, + { + .desc = "YVU 4:2:0 PLANAR", + .pixelformat = V4L2_PIX_FMT_YVU420, + .mbus_code = V4L2_MBUS_FMT_YUYV8_2X8, + .bpp = 2, + .planar = true, }, { .desc = "RGB 444", .pixelformat = V4L2_PIX_FMT_RGB444, .mbus_code = V4L2_MBUS_FMT_RGB444_2X8_PADHI_LE, .bpp = 2, + .planar = false, }, { .desc = "RGB 565", .pixelformat = V4L2_PIX_FMT_RGB565, .mbus_code = V4L2_MBUS_FMT_RGB565_2X8_LE, .bpp = 2, + .planar = false, }, { .desc = "Raw RGB Bayer", .pixelformat = V4L2_PIX_FMT_SBGGR8, .mbus_code = V4L2_MBUS_FMT_SBGGR8_1X8, - .bpp = 1 + .bpp = 1, + .planar = false, }, }; #define N_MCAM_FMTS ARRAY_SIZE(mcam_formats) @@ -168,6 +205,12 @@ struct mcam_dma_desc { u32 segment_len; }; +struct yuv_pointer_t { + dma_addr_t y; + dma_addr_t u; + dma_addr_t v; +}; + /* * Our buffer type for working with videobuf2. Note that the vb2 * developers have decreed that struct vb2_buffer must be at the @@ -179,6 +222,7 @@ struct mcam_vb_buffer { struct mcam_dma_desc *dma_desc; /* Descriptor virtual address */ dma_addr_t dma_desc_pa; /* Descriptor physical address */ int dma_desc_nent; /* Number of mapped descriptors */ + struct yuv_pointer_t yuv_p; }; static inline struct mcam_vb_buffer *vb_to_mvb(struct vb2_buffer *vb) @@ -219,8 +263,10 @@ static void mcam_reset_buffers(struct mcam_camera *cam) int i; cam->next_buf = -1; - for (i = 0; i < cam->nbufs; i++) + for (i = 0; i < cam->nbufs; i++) { clear_bit(i, &cam->flags); + clear_bit(CF_FRAME_SOF0 + i, &cam->flags); + } } static inline int mcam_needs_config(struct mcam_camera *cam) @@ -253,6 +299,45 @@ static void mcam_ctlr_stop(struct mcam_camera *cam) mcam_reg_clear_bit(cam, REG_CTRL0, C0_ENABLE); } +static void mcam_enable_mipi(struct mcam_camera *mcam) +{ + /* Using MIPI mode and enable MIPI */ + cam_dbg(mcam, "camera: DPHY3=0x%x, DPHY5=0x%x, DPHY6=0x%x\n", + mcam->dphy[0], mcam->dphy[1], mcam->dphy[2]); + mcam_reg_write(mcam, REG_CSI2_DPHY3, mcam->dphy[0]); + mcam_reg_write(mcam, REG_CSI2_DPHY5, mcam->dphy[1]); + mcam_reg_write(mcam, REG_CSI2_DPHY6, mcam->dphy[2]); + + if (!mcam->mipi_enabled) { + if (mcam->lane > 4 || mcam->lane <= 0) { + cam_warn(mcam, "lane number error\n"); + mcam->lane = 1; /* set the default value */ + } + /* + * 0x41 actives 1 lane + * 0x43 actives 2 lanes + * 0x45 actives 3 lanes (never happen) + * 0x47 actives 4 lanes + */ + mcam_reg_write(mcam, REG_CSI2_CTRL0, + CSI2_C0_MIPI_EN | CSI2_C0_ACT_LANE(mcam->lane)); + mcam_reg_write(mcam, REG_CLKCTRL, + (mcam->mclk_src << 29) | mcam->mclk_div); + + mcam->mipi_enabled = true; + } +} + +static void mcam_disable_mipi(struct mcam_camera *mcam) +{ + /* Using Parallel mode or disable MIPI */ + mcam_reg_write(mcam, REG_CSI2_CTRL0, 0x0); + mcam_reg_write(mcam, REG_CSI2_DPHY3, 0x0); + mcam_reg_write(mcam, REG_CSI2_DPHY5, 0x0); + mcam_reg_write(mcam, REG_CSI2_DPHY6, 0x0); + mcam->mipi_enabled = false; +} + /* ------------------------------------------------------------------- */ #ifdef MCAM_MODE_VMALLOC @@ -425,6 +510,15 @@ static inline int mcam_check_dma_buffers(struct mcam_camera *cam) /* * DMA-contiguous code. */ + +static bool mcam_fmt_is_planar(__u32 pfmt) +{ + struct mcam_format_struct *f; + + f = mcam_find_format(pfmt); + return f->planar; +} + /* * Set up a contiguous buffer for the given frame. Here also is where * the underrun strategy is set: if there is no buffer available, reuse @@ -436,27 +530,58 @@ static inline int mcam_check_dma_buffers(struct mcam_camera *cam) static void mcam_set_contig_buffer(struct mcam_camera *cam, int frame) { struct mcam_vb_buffer *buf; + struct v4l2_pix_format *fmt = &cam->pix_format; + dma_addr_t dma_handle; + u32 pixel_count = fmt->width * fmt->height; + struct vb2_buffer *vb; + /* * If there are no available buffers, go into single mode */ if (list_empty(&cam->buffers)) { buf = cam->vb_bufs[frame ^ 0x1]; - cam->vb_bufs[frame] = buf; - mcam_reg_write(cam, frame == 0 ? REG_Y0BAR : REG_Y1BAR, - vb2_dma_contig_plane_dma_addr(&buf->vb_buf, 0)); set_bit(CF_SINGLE_BUFFER, &cam->flags); cam->frame_state.singles++; - return; + } else { + /* + * OK, we have a buffer we can use. + */ + buf = list_first_entry(&cam->buffers, struct mcam_vb_buffer, + queue); + list_del_init(&buf->queue); + clear_bit(CF_SINGLE_BUFFER, &cam->flags); } - /* - * OK, we have a buffer we can use. - */ - buf = list_first_entry(&cam->buffers, struct mcam_vb_buffer, queue); - list_del_init(&buf->queue); - mcam_reg_write(cam, frame == 0 ? REG_Y0BAR : REG_Y1BAR, - vb2_dma_contig_plane_dma_addr(&buf->vb_buf, 0)); + cam->vb_bufs[frame] = buf; - clear_bit(CF_SINGLE_BUFFER, &cam->flags); + vb = &buf->vb_buf; + + dma_handle = vb2_dma_contig_plane_dma_addr(vb, 0); + buf->yuv_p.y = dma_handle; + + switch (cam->pix_format.pixelformat) { + case V4L2_PIX_FMT_YUV422P: + buf->yuv_p.u = buf->yuv_p.y + pixel_count; + buf->yuv_p.v = buf->yuv_p.u + pixel_count / 2; + break; + case V4L2_PIX_FMT_YUV420: + buf->yuv_p.u = buf->yuv_p.y + pixel_count; + buf->yuv_p.v = buf->yuv_p.u + pixel_count / 4; + break; + case V4L2_PIX_FMT_YVU420: + buf->yuv_p.v = buf->yuv_p.y + pixel_count; + buf->yuv_p.u = buf->yuv_p.v + pixel_count / 4; + break; + default: + break; + } + + mcam_reg_write(cam, frame == 0 ? REG_Y0BAR : REG_Y1BAR, buf->yuv_p.y); + if (mcam_fmt_is_planar(fmt->pixelformat)) { + mcam_reg_write(cam, frame == 0 ? + REG_U0BAR : REG_U1BAR, buf->yuv_p.u); + mcam_reg_write(cam, frame == 0 ? + REG_V0BAR : REG_V1BAR, buf->yuv_p.v); + } } /* @@ -614,48 +739,90 @@ static inline void mcam_sg_restart(struct mcam_camera *cam) */ static void mcam_ctlr_image(struct mcam_camera *cam) { - int imgsz; struct v4l2_pix_format *fmt = &cam->pix_format; + u32 widthy = 0, widthuv = 0, imgsz_h, imgsz_w; + + cam_dbg(cam, "camera: bytesperline = %d; height = %d\n", + fmt->bytesperline, fmt->sizeimage / fmt->bytesperline); + imgsz_h = (fmt->height << IMGSZ_V_SHIFT) & IMGSZ_V_MASK; + imgsz_w = (fmt->width * 2) & IMGSZ_H_MASK; + + switch (fmt->pixelformat) { + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_UYVY: + widthy = fmt->width * 2; + widthuv = 0; + break; + case V4L2_PIX_FMT_JPEG: + imgsz_h = (fmt->sizeimage / fmt->bytesperline) << IMGSZ_V_SHIFT; + widthy = fmt->bytesperline; + widthuv = 0; + break; + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + widthy = fmt->width; + widthuv = fmt->width / 2; + break; + default: + widthy = fmt->bytesperline; + widthuv = 0; + } + + mcam_reg_write_mask(cam, REG_IMGPITCH, widthuv << 16 | widthy, + IMGP_YP_MASK | IMGP_UVP_MASK); + mcam_reg_write(cam, REG_IMGSIZE, imgsz_h | imgsz_w); + mcam_reg_write(cam, REG_IMGOFFSET, 0x0); - imgsz = ((fmt->height << IMGSZ_V_SHIFT) & IMGSZ_V_MASK) | - (fmt->bytesperline & IMGSZ_H_MASK); - mcam_reg_write(cam, REG_IMGSIZE, imgsz); - mcam_reg_write(cam, REG_IMGOFFSET, 0); - /* YPITCH just drops the last two bits */ - mcam_reg_write_mask(cam, REG_IMGPITCH, fmt->bytesperline, - IMGP_YP_MASK); /* * Tell the controller about the image format we are using. */ - switch (cam->pix_format.pixelformat) { + switch (fmt->pixelformat) { + case V4L2_PIX_FMT_YUV422P: + mcam_reg_write_mask(cam, REG_CTRL0, + C0_DF_YUV | C0_YUV_PLANAR | C0_YUVE_YVYU, C0_DF_MASK); + break; + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + mcam_reg_write_mask(cam, REG_CTRL0, + C0_DF_YUV | C0_YUV_420PL | C0_YUVE_YVYU, C0_DF_MASK); + break; case V4L2_PIX_FMT_YUYV: - mcam_reg_write_mask(cam, REG_CTRL0, - C0_DF_YUV|C0_YUV_PACKED|C0_YUVE_YUYV, - C0_DF_MASK); - break; - + mcam_reg_write_mask(cam, REG_CTRL0, + C0_DF_YUV | C0_YUV_PACKED | C0_YUVE_UYVY, C0_DF_MASK); + break; + case V4L2_PIX_FMT_UYVY: + mcam_reg_write_mask(cam, REG_CTRL0, + C0_DF_YUV | C0_YUV_PACKED | C0_YUVE_YUYV, C0_DF_MASK); + break; + case V4L2_PIX_FMT_JPEG: + mcam_reg_write_mask(cam, REG_CTRL0, + C0_DF_YUV | C0_YUV_PACKED | C0_YUVE_YUYV, C0_DF_MASK); + break; case V4L2_PIX_FMT_RGB444: - mcam_reg_write_mask(cam, REG_CTRL0, - C0_DF_RGB|C0_RGBF_444|C0_RGB4_XRGB, - C0_DF_MASK); + mcam_reg_write_mask(cam, REG_CTRL0, + C0_DF_RGB | C0_RGBF_444 | C0_RGB4_XRGB, C0_DF_MASK); /* Alpha value? */ - break; - + break; case V4L2_PIX_FMT_RGB565: - mcam_reg_write_mask(cam, REG_CTRL0, - C0_DF_RGB|C0_RGBF_565|C0_RGB5_BGGR, - C0_DF_MASK); - break; - + mcam_reg_write_mask(cam, REG_CTRL0, + C0_DF_RGB | C0_RGBF_565 | C0_RGB5_BGGR, C0_DF_MASK); + break; default: - cam_err(cam, "Unknown format %x\n", cam->pix_format.pixelformat); - break; + cam_err(cam, "camera: unknown format: %#x\n", fmt->pixelformat); + break; } + /* * Make sure it knows we want to use hsync/vsync. */ - mcam_reg_write_mask(cam, REG_CTRL0, C0_SIF_HVSYNC, - C0_SIFM_MASK); + mcam_reg_write_mask(cam, REG_CTRL0, C0_SIF_HVSYNC, C0_SIFM_MASK); + /* + * This field controls the generation of EOF(DVP only) + */ + if (cam->bus_type != V4L2_MBUS_CSI2) + mcam_reg_set_bit(cam, REG_CTRL0, + C0_EOF_VSYNC | C0_VEDGE_CTRL); } @@ -753,15 +920,21 @@ static void mcam_ctlr_stop_dma(struct mcam_camera *cam) /* * Power up and down. */ -static void mcam_ctlr_power_up(struct mcam_camera *cam) +static int mcam_ctlr_power_up(struct mcam_camera *cam) { unsigned long flags; + int ret; spin_lock_irqsave(&cam->dev_lock, flags); - cam->plat_power_up(cam); + ret = cam->plat_power_up(cam); + if (ret) { + spin_unlock_irqrestore(&cam->dev_lock, flags); + return ret; + } mcam_reg_clear_bit(cam, REG_CTRL1, C1_PWRDWN); spin_unlock_irqrestore(&cam->dev_lock, flags); msleep(5); /* Just to be sure */ + return 0; } static void mcam_ctlr_power_down(struct mcam_camera *cam) @@ -869,6 +1042,17 @@ static int mcam_read_setup(struct mcam_camera *cam) spin_lock_irqsave(&cam->dev_lock, flags); clear_bit(CF_DMA_ACTIVE, &cam->flags); mcam_reset_buffers(cam); + /* + * Update CSI2_DPHY value + */ + if (cam->calc_dphy) + cam->calc_dphy(cam); + cam_dbg(cam, "camera: DPHY sets: dphy3=0x%x, dphy5=0x%x, dphy6=0x%x\n", + cam->dphy[0], cam->dphy[1], cam->dphy[2]); + if (cam->bus_type == V4L2_MBUS_CSI2) + mcam_enable_mipi(cam); + else + mcam_disable_mipi(cam); mcam_ctlr_irq_enable(cam); cam->state = S_STREAMING; if (!test_bit(CF_SG_RESTART, &cam->flags)) @@ -943,6 +1127,7 @@ static void mcam_vb_wait_finish(struct vb2_queue *vq) static int mcam_vb_start_streaming(struct vb2_queue *vq, unsigned int count) { struct mcam_camera *cam = vb2_get_drv_priv(vq); + unsigned int frame; if (cam->state != S_IDLE) { INIT_LIST_HEAD(&cam->buffers); @@ -960,6 +1145,14 @@ static int mcam_vb_start_streaming(struct vb2_queue *vq, unsigned int count) cam->state = S_BUFWAIT; return 0; } + + /* + * Ensure clear the left over frame flags + * before every really start streaming + */ + for (frame = 0; frame < cam->nbufs; frame++) + clear_bit(CF_FRAME_SOF0 + frame, &cam->flags); + return mcam_read_setup(cam); } @@ -977,6 +1170,12 @@ static int mcam_vb_stop_streaming(struct vb2_queue *vq) return -EINVAL; mcam_ctlr_stop_dma(cam); /* + * Reset the CCIC PHY after stopping streaming, + * otherwise, the CCIC may be unstable. + */ + if (cam->ctlr_reset) + cam->ctlr_reset(cam); + /* * VB2 reclaims the buffers, so we need to forget * about them. */ @@ -1087,6 +1286,7 @@ static int mcam_setup_vb2(struct mcam_camera *cam) #ifdef MCAM_MODE_DMA_CONTIG vq->ops = &mcam_vb2_ops; vq->mem_ops = &vb2_dma_contig_memops; + vq->buf_struct_size = sizeof(struct mcam_vb_buffer); cam->vb_alloc_ctx = vb2_dma_contig_init_ctx(cam->dev); vq->io_modes = VB2_MMAP | VB2_USERPTR; cam->dma_setup = mcam_ctlr_dma_contig; @@ -1097,6 +1297,7 @@ static int mcam_setup_vb2(struct mcam_camera *cam) #ifdef MCAM_MODE_DMA_SG vq->ops = &mcam_vb2_sg_ops; vq->mem_ops = &vb2_dma_sg_memops; + vq->buf_struct_size = sizeof(struct mcam_vb_buffer); vq->io_modes = VB2_MMAP | VB2_USERPTR; cam->dma_setup = mcam_ctlr_dma_sg; cam->frame_complete = mcam_dma_sg_done; @@ -1247,7 +1448,15 @@ static int mcam_vidioc_try_fmt_vid_cap(struct file *filp, void *priv, ret = sensor_call(cam, video, try_mbus_fmt, &mbus_fmt); mutex_unlock(&cam->s_mutex); v4l2_fill_pix_format(pix, &mbus_fmt); - pix->bytesperline = pix->width * f->bpp; + switch (f->pixelformat) { + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + pix->bytesperline = pix->width * 3 / 2; + break; + default: + pix->bytesperline = pix->width * f->bpp; + break; + } pix->sizeimage = pix->height * pix->bytesperline; return ret; } @@ -1475,7 +1684,9 @@ static int mcam_v4l_open(struct file *filp) ret = mcam_setup_vb2(cam); if (ret) goto out; - mcam_ctlr_power_up(cam); + ret = mcam_ctlr_power_up(cam); + if (ret) + goto out; __mcam_cam_reset(cam); mcam_set_config_needed(cam, 1); } @@ -1498,10 +1709,12 @@ static int mcam_v4l_release(struct file *filp) if (cam->users == 0) { mcam_ctlr_stop_dma(cam); mcam_cleanup_vb2(cam); + mcam_disable_mipi(cam); mcam_ctlr_power_down(cam); if (cam->buffer_mode == B_vmalloc && alloc_bufs_at_read) mcam_free_dma_bufs(cam); } + mutex_unlock(&cam->s_mutex); return 0; } @@ -1617,9 +1830,11 @@ int mccic_irq(struct mcam_camera *cam, unsigned int irqs) * each time. */ for (frame = 0; frame < cam->nbufs; frame++) - if (irqs & (IRQ_EOF0 << frame)) { + if (irqs & (IRQ_EOF0 << frame) && + test_bit(CF_FRAME_SOF0 + frame, &cam->flags)) { mcam_frame_complete(cam, frame); handled = 1; + clear_bit(CF_FRAME_SOF0 + frame, &cam->flags); if (cam->buffer_mode == B_DMA_sg) break; } @@ -1628,9 +1843,15 @@ int mccic_irq(struct mcam_camera *cam, unsigned int irqs) * code assumes that we won't get multiple frame interrupts * at once; may want to rethink that. */ - if (irqs & (IRQ_SOF0 | IRQ_SOF1 | IRQ_SOF2)) { + for (frame = 0; frame < cam->nbufs; frame++) { + if (irqs & (IRQ_SOF0 << frame)) { + set_bit(CF_FRAME_SOF0 + frame, &cam->flags); + handled = IRQ_HANDLED; + } + } + + if (handled == IRQ_HANDLED) { set_bit(CF_DMA_ACTIVE, &cam->flags); - handled = 1; if (cam->buffer_mode == B_DMA_sg) mcam_ctlr_stop(cam); } @@ -1787,7 +2008,11 @@ int mccic_resume(struct mcam_camera *cam) mutex_lock(&cam->s_mutex); if (cam->users > 0) { - mcam_ctlr_power_up(cam); + ret = mcam_ctlr_power_up(cam); + if (ret) { + mutex_unlock(&cam->s_mutex); + return ret; + } __mcam_cam_reset(cam); } else { mcam_ctlr_power_down(cam); diff --git a/drivers/media/platform/marvell-ccic/mcam-core.h b/drivers/media/platform/marvell-ccic/mcam-core.h index 520c8ded9443..e0e628cb98f9 100644 --- a/drivers/media/platform/marvell-ccic/mcam-core.h +++ b/drivers/media/platform/marvell-ccic/mcam-core.h @@ -88,6 +88,8 @@ struct mcam_frame_state { unsigned int delivered; }; +#define NR_MCAM_CLK 3 + /* * A description of one of our devices. * Locking: controlled by s_mutex. Certain fields, however, require @@ -108,11 +110,33 @@ struct mcam_camera { short int clock_speed; /* Sensor clock speed, default 30 */ short int use_smbus; /* SMBUS or straight I2c? */ enum mcam_buffer_mode buffer_mode; + + int mclk_min; /* The minimal value of mclk */ + int mclk_src; /* which clock source the mclk derives from */ + int mclk_div; /* Clock Divider Value for MCLK */ + + int ccic_id; + enum v4l2_mbus_type bus_type; + /* MIPI support */ + /* The dphy config value, allocated in board file + * dphy[0]: DPHY3 + * dphy[1]: DPHY5 + * dphy[2]: DPHY6 + */ + int *dphy; + bool mipi_enabled; /* flag whether mipi is enabled already */ + int lane; /* lane number */ + + /* clock tree support */ + struct clk *clk[NR_MCAM_CLK]; + /* * Callbacks from the core to the platform code. */ - void (*plat_power_up) (struct mcam_camera *cam); + int (*plat_power_up) (struct mcam_camera *cam); void (*plat_power_down) (struct mcam_camera *cam); + void (*calc_dphy) (struct mcam_camera *cam); + void (*ctlr_reset) (struct mcam_camera *cam); /* * Everything below here is private to the mcam core and @@ -225,6 +249,23 @@ int mccic_resume(struct mcam_camera *cam); #define REG_Y0BAR 0x00 #define REG_Y1BAR 0x04 #define REG_Y2BAR 0x08 +#define REG_U0BAR 0x0c +#define REG_U1BAR 0x10 +#define REG_U2BAR 0x14 +#define REG_V0BAR 0x18 +#define REG_V1BAR 0x1C +#define REG_V2BAR 0x20 + +/* + * register definitions for MIPI support + */ +#define REG_CSI2_CTRL0 0x100 +#define CSI2_C0_MIPI_EN (0x1 << 0) +#define CSI2_C0_ACT_LANE(n) ((n-1) << 1) +#define REG_CSI2_DPHY3 0x12c +#define REG_CSI2_DPHY5 0x134 +#define REG_CSI2_DPHY6 0x138 + /* ... */ #define REG_IMGPITCH 0x24 /* Image pitch register */ @@ -293,13 +334,16 @@ int mccic_resume(struct mcam_camera *cam); #define C0_YUVE_XUVY 0x00020000 /* 420: .UVY */ #define C0_YUVE_XVUY 0x00030000 /* 420: .VUY */ /* Bayer bits 18,19 if needed */ +#define C0_EOF_VSYNC 0x00400000 /* Generate EOF by VSYNC */ +#define C0_VEDGE_CTRL 0x00800000 /* Detect falling edge of VSYNC */ #define C0_HPOL_LOW 0x01000000 /* HSYNC polarity active low */ #define C0_VPOL_LOW 0x02000000 /* VSYNC polarity active low */ #define C0_VCLK_LOW 0x04000000 /* VCLK on falling edge */ #define C0_DOWNSCALE 0x08000000 /* Enable downscaler */ -#define C0_SIFM_MASK 0xc0000000 /* SIF mode bits */ +/* SIFMODE */ #define C0_SIF_HVSYNC 0x00000000 /* Use H/VSYNC */ -#define CO_SOF_NOSYNC 0x40000000 /* Use inband active signaling */ +#define C0_SOF_NOSYNC 0x40000000 /* Use inband active signaling */ +#define C0_SIFM_MASK 0xc0000000 /* SIF mode bits */ /* Bits below C1_444ALPHA are not present in Cafe */ #define REG_CTRL1 0x40 /* Control 1 */ diff --git a/drivers/media/platform/marvell-ccic/mmp-driver.c b/drivers/media/platform/marvell-ccic/mmp-driver.c index a634888271cd..b5a19af5c587 100644 --- a/drivers/media/platform/marvell-ccic/mmp-driver.c +++ b/drivers/media/platform/marvell-ccic/mmp-driver.c @@ -26,6 +26,7 @@ #include <linux/delay.h> #include <linux/list.h> #include <linux/pm.h> +#include <linux/clk.h> #include "mcam-core.h" @@ -33,11 +34,14 @@ MODULE_ALIAS("platform:mmp-camera"); MODULE_AUTHOR("Jonathan Corbet <corbet@lwn.net>"); MODULE_LICENSE("GPL"); +static char *mcam_clks[] = {"CCICAXICLK", "CCICFUNCLK", "CCICPHYCLK"}; + struct mmp_camera { void *power_regs; struct platform_device *pdev; struct mcam_camera mcam; struct list_head devlist; + struct clk *mipi_clk; int irq; }; @@ -101,6 +105,27 @@ static struct mmp_camera *mmpcam_find_device(struct platform_device *pdev) #define CPU_SUBSYS_PMU_BASE 0xd4282800 #define REG_CCIC_DCGCR 0x28 /* CCIC dyn clock gate ctrl reg */ #define REG_CCIC_CRCR 0x50 /* CCIC clk reset ctrl reg */ +#define REG_CCIC2_CRCR 0xf4 /* CCIC2 clk reset ctrl reg */ + +static void mcam_clk_enable(struct mcam_camera *mcam) +{ + unsigned int i; + + for (i = 0; i < NR_MCAM_CLK; i++) { + if (!IS_ERR(mcam->clk[i])) + clk_prepare_enable(mcam->clk[i]); + } +} + +static void mcam_clk_disable(struct mcam_camera *mcam) +{ + int i; + + for (i = NR_MCAM_CLK - 1; i >= 0; i--) { + if (!IS_ERR(mcam->clk[i])) + clk_disable_unprepare(mcam->clk[i]); + } +} /* * Power control. @@ -112,10 +137,17 @@ static void mmpcam_power_up_ctlr(struct mmp_camera *cam) mdelay(1); } -static void mmpcam_power_up(struct mcam_camera *mcam) +static int mmpcam_power_up(struct mcam_camera *mcam) { struct mmp_camera *cam = mcam_to_cam(mcam); struct mmp_camera_platform_data *pdata; + + if (mcam->bus_type == V4L2_MBUS_CSI2) { + cam->mipi_clk = devm_clk_get(mcam->dev, "mipi"); + if ((IS_ERR(cam->mipi_clk) && mcam->dphy[2] == 0)) + return PTR_ERR(cam->mipi_clk); + } + /* * Turn on power and clocks to the controller. */ @@ -132,6 +164,10 @@ static void mmpcam_power_up(struct mcam_camera *mcam) mdelay(5); gpio_set_value(pdata->sensor_reset_gpio, 1); /* reset is active low */ mdelay(5); + + mcam_clk_enable(mcam); + + return 0; } static void mmpcam_power_down(struct mcam_camera *mcam) @@ -149,8 +185,133 @@ static void mmpcam_power_down(struct mcam_camera *mcam) pdata = cam->pdev->dev.platform_data; gpio_set_value(pdata->sensor_power_gpio, 0); gpio_set_value(pdata->sensor_reset_gpio, 0); + + if (mcam->bus_type == V4L2_MBUS_CSI2 && !IS_ERR(cam->mipi_clk)) { + if (cam->mipi_clk) + devm_clk_put(mcam->dev, cam->mipi_clk); + cam->mipi_clk = NULL; + } + + mcam_clk_disable(mcam); } +void mcam_ctlr_reset(struct mcam_camera *mcam) +{ + unsigned long val; + struct mmp_camera *cam = mcam_to_cam(mcam); + + if (mcam->ccic_id) { + /* + * Using CCIC2 + */ + val = ioread32(cam->power_regs + REG_CCIC2_CRCR); + iowrite32(val & ~0x2, cam->power_regs + REG_CCIC2_CRCR); + iowrite32(val | 0x2, cam->power_regs + REG_CCIC2_CRCR); + } else { + /* + * Using CCIC1 + */ + val = ioread32(cam->power_regs + REG_CCIC_CRCR); + iowrite32(val & ~0x2, cam->power_regs + REG_CCIC_CRCR); + iowrite32(val | 0x2, cam->power_regs + REG_CCIC_CRCR); + } +} + +/* + * calc the dphy register values + * There are three dphy registers being used. + * dphy[0] - CSI2_DPHY3 + * dphy[1] - CSI2_DPHY5 + * dphy[2] - CSI2_DPHY6 + * CSI2_DPHY3 and CSI2_DPHY6 can be set with a default value + * or be calculated dynamically + */ +void mmpcam_calc_dphy(struct mcam_camera *mcam) +{ + struct mmp_camera *cam = mcam_to_cam(mcam); + struct mmp_camera_platform_data *pdata = cam->pdev->dev.platform_data; + struct device *dev = &cam->pdev->dev; + unsigned long tx_clk_esc; + + /* + * If CSI2_DPHY3 is calculated dynamically, + * pdata->lane_clk should be already set + * either in the board driver statically + * or in the sensor driver dynamically. + */ + /* + * dphy[0] - CSI2_DPHY3: + * bit 0 ~ bit 7: HS Term Enable. + * defines the time that the DPHY + * wait before enabling the data + * lane termination after detecting + * that the sensor has driven the data + * lanes to the LP00 bridge state. + * The value is calculated by: + * (Max T(D_TERM_EN)/Period(DDR)) - 1 + * bit 8 ~ bit 15: HS_SETTLE + * Time interval during which the HS + * receiver shall ignore any Data Lane + * HS transistions. + * The vaule has been calibrated on + * different boards. It seems to work well. + * + * More detail please refer + * MIPI Alliance Spectification for D-PHY + * document for explanation of HS-SETTLE + * and D-TERM-EN. + */ + switch (pdata->dphy3_algo) { + case DPHY3_ALGO_PXA910: + /* + * Calculate CSI2_DPHY3 algo for PXA910 + */ + pdata->dphy[0] = + (((1 + (pdata->lane_clk * 80) / 1000) & 0xff) << 8) + | (1 + pdata->lane_clk * 35 / 1000); + break; + case DPHY3_ALGO_PXA2128: + /* + * Calculate CSI2_DPHY3 algo for PXA2128 + */ + pdata->dphy[0] = + (((2 + (pdata->lane_clk * 110) / 1000) & 0xff) << 8) + | (1 + pdata->lane_clk * 35 / 1000); + break; + default: + /* + * Use default CSI2_DPHY3 value for PXA688/PXA988 + */ + dev_dbg(dev, "camera: use the default CSI2_DPHY3 value\n"); + } + + /* + * mipi_clk will never be changed, it is a fixed value on MMP + */ + if (IS_ERR(cam->mipi_clk)) + return; + + /* get the escape clk, this is hard coded */ + tx_clk_esc = (clk_get_rate(cam->mipi_clk) / 1000000) / 12; + + /* + * dphy[2] - CSI2_DPHY6: + * bit 0 ~ bit 7: CK Term Enable + * Time for the Clock Lane receiver to enable the HS line + * termination. The value is calculated similarly with + * HS Term Enable + * bit 8 ~ bit 15: CK Settle + * Time interval during which the HS receiver shall ignore + * any Clock Lane HS transitions. + * The value is calibrated on the boards. + */ + pdata->dphy[2] = + ((((534 * tx_clk_esc) / 2000 - 1) & 0xff) << 8) + | (((38 * tx_clk_esc) / 1000 - 1) & 0xff); + + dev_dbg(dev, "camera: DPHY sets: dphy3=0x%x, dphy5=0x%x, dphy6=0x%x\n", + pdata->dphy[0], pdata->dphy[1], pdata->dphy[2]); +} static irqreturn_t mmpcam_irq(int irq, void *data) { @@ -164,6 +325,35 @@ static irqreturn_t mmpcam_irq(int irq, void *data) return IRQ_RETVAL(handled); } +static void mcam_deinit_clk(struct mcam_camera *mcam) +{ + unsigned int i; + + for (i = 0; i < NR_MCAM_CLK; i++) { + if (!IS_ERR(mcam->clk[i])) { + if (mcam->clk[i]) + devm_clk_put(mcam->dev, mcam->clk[i]); + } + mcam->clk[i] = NULL; + } +} + +static void mcam_init_clk(struct mcam_camera *mcam) +{ + unsigned int i; + + for (i = 0; i < NR_MCAM_CLK; i++) { + if (mcam_clks[i] != NULL) { + /* Some clks are not necessary on some boards + * We still try to run even it fails getting clk + */ + mcam->clk[i] = devm_clk_get(mcam->dev, mcam_clks[i]); + if (IS_ERR(mcam->clk[i])) + dev_warn(mcam->dev, "Could not get clk: %s\n", + mcam_clks[i]); + } + } +} static int mmpcam_probe(struct platform_device *pdev) { @@ -173,17 +363,32 @@ static int mmpcam_probe(struct platform_device *pdev) struct mmp_camera_platform_data *pdata; int ret; - cam = kzalloc(sizeof(*cam), GFP_KERNEL); + pdata = pdev->dev.platform_data; + if (!pdata) + return -ENODEV; + + cam = devm_kzalloc(&pdev->dev, sizeof(*cam), GFP_KERNEL); if (cam == NULL) return -ENOMEM; cam->pdev = pdev; + cam->mipi_clk = NULL; INIT_LIST_HEAD(&cam->devlist); mcam = &cam->mcam; mcam->plat_power_up = mmpcam_power_up; mcam->plat_power_down = mmpcam_power_down; + mcam->ctlr_reset = mcam_ctlr_reset; + mcam->calc_dphy = mmpcam_calc_dphy; mcam->dev = &pdev->dev; mcam->use_smbus = 0; + mcam->ccic_id = pdev->id; + mcam->mclk_min = pdata->mclk_min; + mcam->mclk_src = pdata->mclk_src; + mcam->mclk_div = pdata->mclk_div; + mcam->bus_type = pdata->bus_type; + mcam->dphy = pdata->dphy; + mcam->mipi_enabled = false; + mcam->lane = pdata->lane; mcam->chip_id = MCAM_ARMADA610; mcam->buffer_mode = B_DMA_sg; spin_lock_init(&mcam->dev_lock); @@ -191,69 +396,58 @@ static int mmpcam_probe(struct platform_device *pdev) * Get our I/O memory. */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (res == NULL) { - dev_err(&pdev->dev, "no iomem resource!\n"); - ret = -ENODEV; - goto out_free; - } - mcam->regs = ioremap(res->start, resource_size(res)); - if (mcam->regs == NULL) { - dev_err(&pdev->dev, "MMIO ioremap fail\n"); - ret = -ENODEV; - goto out_free; - } + mcam->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mcam->regs)) + return PTR_ERR(mcam->regs); mcam->regs_size = resource_size(res); /* * Power/clock memory is elsewhere; get it too. Perhaps this * should really be managed outside of this driver? */ res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - if (res == NULL) { - dev_err(&pdev->dev, "no power resource!\n"); - ret = -ENODEV; - goto out_unmap1; - } - cam->power_regs = ioremap(res->start, resource_size(res)); - if (cam->power_regs == NULL) { - dev_err(&pdev->dev, "power MMIO ioremap fail\n"); - ret = -ENODEV; - goto out_unmap1; - } + cam->power_regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(cam->power_regs)) + return PTR_ERR(cam->power_regs); /* * Find the i2c adapter. This assumes, of course, that the * i2c bus is already up and functioning. */ - pdata = pdev->dev.platform_data; mcam->i2c_adapter = platform_get_drvdata(pdata->i2c_device); if (mcam->i2c_adapter == NULL) { - ret = -ENODEV; dev_err(&pdev->dev, "No i2c adapter\n"); - goto out_unmap2; + return -ENODEV; } /* * Sensor GPIO pins. */ - ret = gpio_request(pdata->sensor_power_gpio, "cam-power"); + ret = devm_gpio_request(&pdev->dev, pdata->sensor_power_gpio, + "cam-power"); if (ret) { dev_err(&pdev->dev, "Can't get sensor power gpio %d", pdata->sensor_power_gpio); - goto out_unmap2; + return ret; } gpio_direction_output(pdata->sensor_power_gpio, 0); - ret = gpio_request(pdata->sensor_reset_gpio, "cam-reset"); + ret = devm_gpio_request(&pdev->dev, pdata->sensor_reset_gpio, + "cam-reset"); if (ret) { dev_err(&pdev->dev, "Can't get sensor reset gpio %d", pdata->sensor_reset_gpio); - goto out_gpio; + return ret; } gpio_direction_output(pdata->sensor_reset_gpio, 0); + + mcam_init_clk(mcam); + /* * Power the device up and hand it off to the core. */ - mmpcam_power_up(mcam); + ret = mmpcam_power_up(mcam); + if (ret) + goto out_deinit_clk; ret = mccic_register(mcam); if (ret) - goto out_gpio2; + goto out_power_down; /* * Finally, set up our IRQ now that the core is ready to * deal with it. @@ -264,8 +458,8 @@ static int mmpcam_probe(struct platform_device *pdev) goto out_unregister; } cam->irq = res->start; - ret = request_irq(cam->irq, mmpcam_irq, IRQF_SHARED, - "mmp-camera", mcam); + ret = devm_request_irq(&pdev->dev, cam->irq, mmpcam_irq, IRQF_SHARED, + "mmp-camera", mcam); if (ret == 0) { mmpcam_add_device(cam); return 0; @@ -273,17 +467,10 @@ static int mmpcam_probe(struct platform_device *pdev) out_unregister: mccic_shutdown(mcam); -out_gpio2: +out_power_down: mmpcam_power_down(mcam); - gpio_free(pdata->sensor_reset_gpio); -out_gpio: - gpio_free(pdata->sensor_power_gpio); -out_unmap2: - iounmap(cam->power_regs); -out_unmap1: - iounmap(mcam->regs); -out_free: - kfree(cam); +out_deinit_clk: + mcam_deinit_clk(mcam); return ret; } @@ -300,6 +487,7 @@ static int mmpcam_remove(struct mmp_camera *cam) pdata = cam->pdev->dev.platform_data; gpio_free(pdata->sensor_reset_gpio); gpio_free(pdata->sensor_power_gpio); + mcam_deinit_clk(mcam); iounmap(cam->power_regs); iounmap(mcam->regs); kfree(cam); diff --git a/drivers/media/platform/s3c-camif/camif-regs.c b/drivers/media/platform/s3c-camif/camif-regs.c index a9e3b16460b8..ebf5b184cce4 100644 --- a/drivers/media/platform/s3c-camif/camif-regs.c +++ b/drivers/media/platform/s3c-camif/camif-regs.c @@ -106,15 +106,15 @@ static const u32 src_pixfmt_map[8][2] = { void camif_hw_set_source_format(struct camif_dev *camif) { struct v4l2_mbus_framefmt *mf = &camif->mbus_fmt; - unsigned int i = ARRAY_SIZE(src_pixfmt_map); + int i; u32 cfg; - while (i-- >= 0) { + for (i = ARRAY_SIZE(src_pixfmt_map) - 1; i >= 0; i--) { if (src_pixfmt_map[i][0] == mf->code) break; } - - if (i == 0 && src_pixfmt_map[i][0] != mf->code) { + if (i < 0) { + i = 0; dev_err(camif->dev, "Unsupported pixel code, falling back to %#08x\n", src_pixfmt_map[i][0]); diff --git a/drivers/media/platform/s5p-mfc/regs-mfc-v6.h b/drivers/media/platform/s5p-mfc/regs-mfc-v6.h index 363a97cc7681..2398cdf61341 100644 --- a/drivers/media/platform/s5p-mfc/regs-mfc-v6.h +++ b/drivers/media/platform/s5p-mfc/regs-mfc-v6.h @@ -374,9 +374,9 @@ #define S5P_FIMV_NUM_PIXELS_IN_MB_COL_V6 16 /* Buffer size requirements defined by hardware */ -#define S5P_FIMV_TMV_BUFFER_SIZE_V6(w, h) (((w) + 1) * ((h) + 1) * 8) +#define S5P_FIMV_TMV_BUFFER_SIZE_V6(w, h) (((w) + 1) * ((h) + 3) * 8) #define S5P_FIMV_ME_BUFFER_SIZE_V6(imw, imh, mbw, mbh) \ - ((DIV_ROUND_UP(imw, 64) * DIV_ROUND_UP(imh, 64) * 256) + \ + (((((imw + 127) / 64) * 16) * DIV_ROUND_UP(imh, 64) * 256) + \ (DIV_ROUND_UP((mbw) * (mbh), 32) * 16)) #define S5P_FIMV_SCRATCH_BUF_SIZE_H264_DEC_V6(w, h) (((w) * 192) + 64) #define S5P_FIMV_SCRATCH_BUF_SIZE_MPEG4_DEC_V6(w, h) \ diff --git a/drivers/media/platform/s5p-mfc/regs-mfc-v7.h b/drivers/media/platform/s5p-mfc/regs-mfc-v7.h new file mode 100644 index 000000000000..ea5ec2a711af --- /dev/null +++ b/drivers/media/platform/s5p-mfc/regs-mfc-v7.h @@ -0,0 +1,61 @@ +/* + * Register definition file for Samsung MFC V7.x Interface (FIMV) driver + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _REGS_MFC_V7_H +#define _REGS_MFC_V7_H + +#include "regs-mfc-v6.h" + +/* Additional features of v7 */ +#define S5P_FIMV_CODEC_VP8_ENC_V7 25 + +/* Additional registers for v7 */ +#define S5P_FIMV_D_INIT_BUFFER_OPTIONS_V7 0xf47c + +#define S5P_FIMV_E_SOURCE_FIRST_ADDR_V7 0xf9e0 +#define S5P_FIMV_E_SOURCE_SECOND_ADDR_V7 0xf9e4 +#define S5P_FIMV_E_SOURCE_THIRD_ADDR_V7 0xf9e8 +#define S5P_FIMV_E_SOURCE_FIRST_STRIDE_V7 0xf9ec +#define S5P_FIMV_E_SOURCE_SECOND_STRIDE_V7 0xf9f0 +#define S5P_FIMV_E_SOURCE_THIRD_STRIDE_V7 0xf9f4 + +#define S5P_FIMV_E_ENCODED_SOURCE_FIRST_ADDR_V7 0xfa70 +#define S5P_FIMV_E_ENCODED_SOURCE_SECOND_ADDR_V7 0xfa74 + +#define S5P_FIMV_E_VP8_OPTIONS_V7 0xfdb0 +#define S5P_FIMV_E_VP8_FILTER_OPTIONS_V7 0xfdb4 +#define S5P_FIMV_E_VP8_GOLDEN_FRAME_OPTION_V7 0xfdb8 +#define S5P_FIMV_E_VP8_NUM_T_LAYER_V7 0xfdc4 + +/* MFCv7 variant defines */ +#define MAX_FW_SIZE_V7 (SZ_1M) /* 1MB */ +#define MAX_CPB_SIZE_V7 (3 * SZ_1M) /* 3MB */ +#define MFC_VERSION_V7 0x72 +#define MFC_NUM_PORTS_V7 1 + +#define MFC_LUMA_PAD_BYTES_V7 256 +#define MFC_CHROMA_PAD_BYTES_V7 128 + +/* MFCv7 Context buffer sizes */ +#define MFC_CTX_BUF_SIZE_V7 (30 * SZ_1K) /* 30KB */ +#define MFC_H264_DEC_CTX_BUF_SIZE_V7 (2 * SZ_1M) /* 2MB */ +#define MFC_OTHER_DEC_CTX_BUF_SIZE_V7 (20 * SZ_1K) /* 20KB */ +#define MFC_H264_ENC_CTX_BUF_SIZE_V7 (100 * SZ_1K) /* 100KB */ +#define MFC_OTHER_ENC_CTX_BUF_SIZE_V7 (10 * SZ_1K) /* 10KB */ + +/* Buffer size defines */ +#define S5P_FIMV_SCRATCH_BUF_SIZE_MPEG4_DEC_V7(w, h) \ + (SZ_1M + ((w) * 144) + (8192 * (h)) + 49216) + +#define S5P_FIMV_SCRATCH_BUF_SIZE_VP8_ENC_V7(w, h) \ + (((w) * 48) + (((w) + 1) / 2 * 128) + 144 + 8192) + +#endif /*_REGS_MFC_V7_H*/ diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc.c b/drivers/media/platform/s5p-mfc/s5p_mfc.c index a130dcdb7206..084263dd126f 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc.c @@ -1391,6 +1391,32 @@ static struct s5p_mfc_variant mfc_drvdata_v6 = { .fw_name = "s5p-mfc-v6.fw", }; +struct s5p_mfc_buf_size_v6 mfc_buf_size_v7 = { + .dev_ctx = MFC_CTX_BUF_SIZE_V7, + .h264_dec_ctx = MFC_H264_DEC_CTX_BUF_SIZE_V7, + .other_dec_ctx = MFC_OTHER_DEC_CTX_BUF_SIZE_V7, + .h264_enc_ctx = MFC_H264_ENC_CTX_BUF_SIZE_V7, + .other_enc_ctx = MFC_OTHER_ENC_CTX_BUF_SIZE_V7, +}; + +struct s5p_mfc_buf_size buf_size_v7 = { + .fw = MAX_FW_SIZE_V7, + .cpb = MAX_CPB_SIZE_V7, + .priv = &mfc_buf_size_v7, +}; + +struct s5p_mfc_buf_align mfc_buf_align_v7 = { + .base = 0, +}; + +static struct s5p_mfc_variant mfc_drvdata_v7 = { + .version = MFC_VERSION_V7, + .port_num = MFC_NUM_PORTS_V7, + .buf_size = &buf_size_v7, + .buf_align = &mfc_buf_align_v7, + .fw_name = "s5p-mfc-v7.fw", +}; + static struct platform_device_id mfc_driver_ids[] = { { .name = "s5p-mfc", @@ -1401,6 +1427,9 @@ static struct platform_device_id mfc_driver_ids[] = { }, { .name = "s5p-mfc-v6", .driver_data = (unsigned long)&mfc_drvdata_v6, + }, { + .name = "s5p-mfc-v7", + .driver_data = (unsigned long)&mfc_drvdata_v7, }, {}, }; @@ -1413,6 +1442,9 @@ static const struct of_device_id exynos_mfc_match[] = { }, { .compatible = "samsung,mfc-v6", .data = &mfc_drvdata_v6, + }, { + .compatible = "samsung,mfc-v7", + .data = &mfc_drvdata_v7, }, {}, }; diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_cmd.c b/drivers/media/platform/s5p-mfc/s5p_mfc_cmd.c index f0a41c95df84..242c033cf8bb 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_cmd.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_cmd.c @@ -20,7 +20,7 @@ static struct s5p_mfc_hw_cmds *s5p_mfc_cmds; void s5p_mfc_init_hw_cmds(struct s5p_mfc_dev *dev) { - if (IS_MFCV6(dev)) + if (IS_MFCV6_PLUS(dev)) s5p_mfc_cmds = s5p_mfc_init_hw_cmds_v6(); else s5p_mfc_cmds = s5p_mfc_init_hw_cmds_v5(); diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_cmd_v6.c b/drivers/media/platform/s5p-mfc/s5p_mfc_cmd_v6.c index 5708fc3d9b4d..db796c8e7874 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_cmd_v6.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_cmd_v6.c @@ -108,6 +108,9 @@ static int s5p_mfc_open_inst_cmd_v6(struct s5p_mfc_ctx *ctx) case S5P_MFC_CODEC_H263_ENC: codec_type = S5P_FIMV_CODEC_H263_ENC_V6; break; + case S5P_MFC_CODEC_VP8_ENC: + codec_type = S5P_FIMV_CODEC_VP8_ENC_V7; + break; default: codec_type = S5P_FIMV_CODEC_NONE_V6; }; diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_common.h b/drivers/media/platform/s5p-mfc/s5p_mfc_common.h index ef4074cd5316..6920b546181a 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_common.h +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_common.h @@ -24,6 +24,7 @@ #include <media/videobuf2-core.h> #include "regs-mfc.h" #include "regs-mfc-v6.h" +#include "regs-mfc-v7.h" /* Definitions related to MFC memory */ @@ -64,7 +65,7 @@ static inline dma_addr_t s5p_mfc_mem_cookie(void *a, void *b) #define MFC_ENC_CAP_PLANE_COUNT 1 #define MFC_ENC_OUT_PLANE_COUNT 2 #define STUFF_BYTE 4 -#define MFC_MAX_CTRLS 70 +#define MFC_MAX_CTRLS 77 #define S5P_MFC_CODEC_NONE -1 #define S5P_MFC_CODEC_H264_DEC 0 @@ -80,6 +81,7 @@ static inline dma_addr_t s5p_mfc_mem_cookie(void *a, void *b) #define S5P_MFC_CODEC_H264_MVC_ENC 21 #define S5P_MFC_CODEC_MPEG4_ENC 22 #define S5P_MFC_CODEC_H263_ENC 23 +#define S5P_MFC_CODEC_VP8_ENC 24 #define S5P_MFC_R2H_CMD_EMPTY 0 #define S5P_MFC_R2H_CMD_SYS_INIT_RET 1 @@ -408,6 +410,21 @@ struct s5p_mfc_mpeg4_enc_params { }; /** + * struct s5p_mfc_vp8_enc_params - encoding parameters for vp8 + */ +struct s5p_mfc_vp8_enc_params { + u8 imd_4x4; + enum v4l2_vp8_num_partitions num_partitions; + enum v4l2_vp8_num_ref_frames num_ref; + u8 filter_level; + u8 filter_sharpness; + u32 golden_frame_ref_period; + enum v4l2_vp8_golden_frame_sel golden_frame_sel; + u8 hier_layer; + u8 hier_layer_qp[3]; +}; + +/** * struct s5p_mfc_enc_params - general encoding parameters */ struct s5p_mfc_enc_params { @@ -441,6 +458,7 @@ struct s5p_mfc_enc_params { struct { struct s5p_mfc_h264_enc_params h264; struct s5p_mfc_mpeg4_enc_params mpeg4; + struct s5p_mfc_vp8_enc_params vp8; } codec; }; @@ -683,6 +701,7 @@ void set_work_bit_irqsave(struct s5p_mfc_ctx *ctx); #define HAS_PORTNUM(dev) (dev ? (dev->variant ? \ (dev->variant->port_num ? 1 : 0) : 0) : 0) #define IS_TWOPORT(dev) (dev->variant->port_num == 2 ? 1 : 0) -#define IS_MFCV6(dev) (dev->variant->version >= 0x60 ? 1 : 0) +#define IS_MFCV6_PLUS(dev) (dev->variant->version >= 0x60 ? 1 : 0) +#define IS_MFCV7(dev) (dev->variant->version >= 0x70 ? 1 : 0) #endif /* S5P_MFC_COMMON_H_ */ diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_ctrl.c b/drivers/media/platform/s5p-mfc/s5p_mfc_ctrl.c index dc1fc94a488d..7cab6849fb5b 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_ctrl.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_ctrl.c @@ -164,7 +164,7 @@ int s5p_mfc_reset(struct s5p_mfc_dev *dev) mfc_debug_enter(); - if (IS_MFCV6(dev)) { + if (IS_MFCV6_PLUS(dev)) { /* Reset IP */ /* except RISC, reset */ mfc_write(dev, 0xFEE, S5P_FIMV_MFC_RESET_V6); @@ -213,7 +213,7 @@ int s5p_mfc_reset(struct s5p_mfc_dev *dev) static inline void s5p_mfc_init_memctrl(struct s5p_mfc_dev *dev) { - if (IS_MFCV6(dev)) { + if (IS_MFCV6_PLUS(dev)) { mfc_write(dev, dev->bank1, S5P_FIMV_RISC_BASE_ADDRESS_V6); mfc_debug(2, "Base Address : %08x\n", dev->bank1); } else { @@ -226,7 +226,7 @@ static inline void s5p_mfc_init_memctrl(struct s5p_mfc_dev *dev) static inline void s5p_mfc_clear_cmds(struct s5p_mfc_dev *dev) { - if (IS_MFCV6(dev)) { + if (IS_MFCV6_PLUS(dev)) { /* Zero initialization should be done before RESET. * Nothing to do here. */ } else { @@ -264,7 +264,7 @@ int s5p_mfc_init_hw(struct s5p_mfc_dev *dev) s5p_mfc_clear_cmds(dev); /* 3. Release reset signal to the RISC */ s5p_mfc_clean_dev_int_flags(dev); - if (IS_MFCV6(dev)) + if (IS_MFCV6_PLUS(dev)) mfc_write(dev, 0x1, S5P_FIMV_RISC_ON_V6); else mfc_write(dev, 0x3ff, S5P_FIMV_SW_RESET); @@ -301,7 +301,7 @@ int s5p_mfc_init_hw(struct s5p_mfc_dev *dev) s5p_mfc_clock_off(); return -EIO; } - if (IS_MFCV6(dev)) + if (IS_MFCV6_PLUS(dev)) ver = mfc_read(dev, S5P_FIMV_FW_VERSION_V6); else ver = mfc_read(dev, S5P_FIMV_FW_VERSION); @@ -380,7 +380,7 @@ int s5p_mfc_wakeup(struct s5p_mfc_dev *dev) return ret; } /* 4. Release reset signal to the RISC */ - if (IS_MFCV6(dev)) + if (IS_MFCV6_PLUS(dev)) mfc_write(dev, 0x1, S5P_FIMV_RISC_ON_V6); else mfc_write(dev, 0x3ff, S5P_FIMV_SW_RESET); diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_dec.c b/drivers/media/platform/s5p-mfc/s5p_mfc_dec.c index 4f6dd42c9adb..8faf9691712d 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_dec.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_dec.c @@ -386,7 +386,7 @@ static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f) mfc_err("Unknown codec\n"); return -EINVAL; } - if (!IS_MFCV6(dev)) { + if (!IS_MFCV6_PLUS(dev)) { if (fmt->fourcc == V4L2_PIX_FMT_VP8) { mfc_err("Not supported format.\n"); return -EINVAL; @@ -398,10 +398,11 @@ static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f) mfc_err("Unsupported format for destination.\n"); return -EINVAL; } - if (IS_MFCV6(dev) && (fmt->fourcc == V4L2_PIX_FMT_NV12MT)) { + if (IS_MFCV6_PLUS(dev) && + (fmt->fourcc == V4L2_PIX_FMT_NV12MT)) { mfc_err("Not supported format.\n"); return -EINVAL; - } else if (!IS_MFCV6(dev) && + } else if (!IS_MFCV6_PLUS(dev) && (fmt->fourcc != V4L2_PIX_FMT_NV12MT)) { mfc_err("Not supported format.\n"); return -EINVAL; @@ -925,7 +926,7 @@ static int s5p_mfc_queue_setup(struct vb2_queue *vq, psize[0] = ctx->luma_size; psize[1] = ctx->chroma_size; - if (IS_MFCV6(dev)) + if (IS_MFCV6_PLUS(dev)) allocators[0] = ctx->dev->alloc_ctx[MFC_BANK1_ALLOC_CTX]; else @@ -1050,7 +1051,7 @@ static int s5p_mfc_stop_streaming(struct vb2_queue *q) ctx->dpb_flush_flag = 1; ctx->dec_dst_flag = 0; spin_unlock_irqrestore(&dev->irqlock, flags); - if (IS_MFCV6(dev) && (ctx->state == MFCINST_RUNNING)) { + if (IS_MFCV6_PLUS(dev) && (ctx->state == MFCINST_RUNNING)) { ctx->state = MFCINST_FLUSH; set_work_bit_irqsave(ctx); s5p_mfc_clean_ctx_int_flags(ctx); diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c b/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c index 59e56f4c8ce3..41f5a3c10dbd 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c @@ -84,6 +84,13 @@ static struct s5p_mfc_fmt formats[] = { .type = MFC_FMT_ENC, .num_planes = 1, }, + { + .name = "VP8 Encoded Stream", + .fourcc = V4L2_PIX_FMT_VP8, + .codec_mode = S5P_MFC_CODEC_VP8_ENC, + .type = MFC_FMT_ENC, + .num_planes = 1, + }, }; #define NUM_FORMATS ARRAY_SIZE(formats) @@ -557,6 +564,60 @@ static struct mfc_control controls[] = { .step = 1, .default_value = 0, }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_NUM_PARTITIONS, + .type = V4L2_CTRL_TYPE_INTEGER_MENU, + .maximum = V4L2_CID_MPEG_VIDEO_VPX_8_PARTITIONS, + .default_value = V4L2_CID_MPEG_VIDEO_VPX_1_PARTITION, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_IMD_DISABLE_4X4, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_NUM_REF_FRAMES, + .type = V4L2_CTRL_TYPE_INTEGER_MENU, + .maximum = V4L2_CID_MPEG_VIDEO_VPX_2_REF_FRAME, + .default_value = V4L2_CID_MPEG_VIDEO_VPX_1_REF_FRAME, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_FILTER_LEVEL, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 63, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_FILTER_SHARPNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 7, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_REF_PERIOD, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = (1 << 16) - 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_SEL, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_USE_PREV, + .maximum = V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_USE_REF_PERIOD, + .default_value = V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_USE_PREV, + .menu_skip_mask = 0, + }, }; #define NUM_CTRLS ARRAY_SIZE(controls) @@ -663,7 +724,7 @@ static int enc_post_seq_start(struct s5p_mfc_ctx *ctx) spin_unlock_irqrestore(&dev->irqlock, flags); } - if (!IS_MFCV6(dev)) { + if (!IS_MFCV6_PLUS(dev)) { ctx->state = MFCINST_RUNNING; if (s5p_mfc_ctx_ready(ctx)) set_work_bit_irqsave(ctx); @@ -917,6 +978,11 @@ static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f) return -EINVAL; } + if (!IS_MFCV7(dev) && (fmt->fourcc == V4L2_PIX_FMT_VP8)) { + mfc_err("VP8 is supported only in MFC v7\n"); + return -EINVAL; + } + if (pix_fmt_mp->plane_fmt[0].sizeimage == 0) { mfc_err("must be set encoding output size\n"); return -EINVAL; @@ -931,12 +997,12 @@ static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f) return -EINVAL; } - if (!IS_MFCV6(dev)) { + if (!IS_MFCV6_PLUS(dev)) { if (fmt->fourcc == V4L2_PIX_FMT_NV12MT_16X16) { mfc_err("Not supported format.\n"); return -EINVAL; } - } else if (IS_MFCV6(dev)) { + } else if (IS_MFCV6_PLUS(dev)) { if (fmt->fourcc == V4L2_PIX_FMT_NV12MT) { mfc_err("Not supported format.\n"); return -EINVAL; @@ -1060,7 +1126,7 @@ static int vidioc_reqbufs(struct file *file, void *priv, return -EINVAL; } - if (IS_MFCV6(dev)) { + if (IS_MFCV6_PLUS(dev)) { /* Check for min encoder buffers */ if (ctx->pb_count && (reqbufs->count < ctx->pb_count)) { @@ -1341,7 +1407,7 @@ static int s5p_mfc_enc_s_ctrl(struct v4l2_ctrl *ctrl) S5P_FIMV_ENC_PROFILE_H264_BASELINE; break; case V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE: - if (IS_MFCV6(dev)) + if (IS_MFCV6_PLUS(dev)) p->codec.h264.profile = S5P_FIMV_ENC_PROFILE_H264_CONSTRAINED_BASELINE; else @@ -1470,6 +1536,27 @@ static int s5p_mfc_enc_s_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_MPEG_VIDEO_MPEG4_QPEL: p->codec.mpeg4.quarter_pixel = ctrl->val; break; + case V4L2_CID_MPEG_VIDEO_VPX_NUM_PARTITIONS: + p->codec.vp8.num_partitions = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_IMD_DISABLE_4X4: + p->codec.vp8.imd_4x4 = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_NUM_REF_FRAMES: + p->codec.vp8.num_ref = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_FILTER_LEVEL: + p->codec.vp8.filter_level = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_FILTER_SHARPNESS: + p->codec.vp8.filter_sharpness = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_REF_PERIOD: + p->codec.vp8.golden_frame_ref_period = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_SEL: + p->codec.vp8.golden_frame_sel = ctrl->val; + break; default: v4l2_err(&dev->v4l2_dev, "Invalid control, id=%d, val=%d\n", ctrl->id, ctrl->val); @@ -1650,9 +1737,11 @@ static int s5p_mfc_queue_setup(struct vb2_queue *vq, *buf_count = 1; if (*buf_count > MFC_MAX_BUFFERS) *buf_count = MFC_MAX_BUFFERS; + psize[0] = ctx->luma_size; psize[1] = ctx->chroma_size; - if (IS_MFCV6(dev)) { + + if (IS_MFCV6_PLUS(dev)) { allocators[0] = ctx->dev->alloc_ctx[MFC_BANK1_ALLOC_CTX]; allocators[1] = @@ -1761,7 +1850,8 @@ static int s5p_mfc_start_streaming(struct vb2_queue *q, unsigned int count) struct s5p_mfc_ctx *ctx = fh_to_ctx(q->drv_priv); struct s5p_mfc_dev *dev = ctx->dev; - if (IS_MFCV6(dev) && (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) { + if (IS_MFCV6_PLUS(dev) && + (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) { if ((ctx->state == MFCINST_GOT_INST) && (dev->curr_ctx == ctx->num) && dev->hw_lock) { @@ -1915,7 +2005,9 @@ int s5p_mfc_enc_ctrls_setup(struct s5p_mfc_ctx *ctx) ctx->ctrls[i] = v4l2_ctrl_new_custom(&ctx->ctrl_handler, &cfg, NULL); } else { - if (controls[i].type == V4L2_CTRL_TYPE_MENU) { + if ((controls[i].type == V4L2_CTRL_TYPE_MENU) || + (controls[i].type == + V4L2_CTRL_TYPE_INTEGER_MENU)) { ctx->ctrls[i] = v4l2_ctrl_new_std_menu( &ctx->ctrl_handler, &s5p_mfc_enc_ctrl_ops, controls[i].id, diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_opr.c b/drivers/media/platform/s5p-mfc/s5p_mfc_opr.c index 10f8ac37cecd..3c01c339d696 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_opr.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_opr.c @@ -21,7 +21,7 @@ static struct s5p_mfc_hw_ops *s5p_mfc_ops; void s5p_mfc_init_hw_ops(struct s5p_mfc_dev *dev) { - if (IS_MFCV6(dev)) { + if (IS_MFCV6_PLUS(dev)) { s5p_mfc_ops = s5p_mfc_init_hw_ops_v6(); dev->warn_start = S5P_FIMV_ERR_WARNINGS_START_V6; } else { diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v6.c b/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v6.c index 66f0d042357f..461358c4a790 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v6.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v6.c @@ -80,6 +80,7 @@ static int s5p_mfc_alloc_codec_buffers_v6(struct s5p_mfc_ctx *ctx) ctx->tmv_buffer_size = S5P_FIMV_NUM_TMV_BUFFERS_V6 * ALIGN(S5P_FIMV_TMV_BUFFER_SIZE_V6(mb_width, mb_height), S5P_FIMV_TMV_BUFFER_ALIGN_V6); + ctx->luma_dpb_size = ALIGN((mb_width * mb_height) * S5P_FIMV_LUMA_MB_TO_PIXEL_V6, S5P_FIMV_LUMA_DPB_BUFFER_ALIGN_V6); @@ -112,10 +113,18 @@ static int s5p_mfc_alloc_codec_buffers_v6(struct s5p_mfc_ctx *ctx) (ctx->mv_count * ctx->mv_size); break; case S5P_MFC_CODEC_MPEG4_DEC: - ctx->scratch_buf_size = - S5P_FIMV_SCRATCH_BUF_SIZE_MPEG4_DEC_V6( - mb_width, - mb_height); + if (IS_MFCV7(dev)) { + ctx->scratch_buf_size = + S5P_FIMV_SCRATCH_BUF_SIZE_MPEG4_DEC_V7( + mb_width, + mb_height); + } else { + ctx->scratch_buf_size = + S5P_FIMV_SCRATCH_BUF_SIZE_MPEG4_DEC_V6( + mb_width, + mb_height); + } + ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size, S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6); ctx->bank1.size = ctx->scratch_buf_size; @@ -179,6 +188,19 @@ static int s5p_mfc_alloc_codec_buffers_v6(struct s5p_mfc_ctx *ctx) ctx->chroma_dpb_size + ctx->me_buffer_size)); ctx->bank2.size = 0; break; + case S5P_MFC_CODEC_VP8_ENC: + ctx->scratch_buf_size = + S5P_FIMV_SCRATCH_BUF_SIZE_VP8_ENC_V7( + mb_width, + mb_height); + ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size, + S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6); + ctx->bank1.size = + ctx->scratch_buf_size + ctx->tmv_buffer_size + + (ctx->pb_count * (ctx->luma_dpb_size + + ctx->chroma_dpb_size + ctx->me_buffer_size)); + ctx->bank2.size = 0; + break; default: break; } @@ -228,6 +250,7 @@ static int s5p_mfc_alloc_instance_buffer_v6(struct s5p_mfc_ctx *ctx) break; case S5P_MFC_CODEC_MPEG4_ENC: case S5P_MFC_CODEC_H263_ENC: + case S5P_MFC_CODEC_VP8_ENC: ctx->ctx.size = buf_size->other_enc_ctx; break; default: @@ -329,6 +352,12 @@ static void s5p_mfc_enc_calc_src_size_v6(struct s5p_mfc_ctx *ctx) ctx->buf_width = ALIGN(ctx->img_width, S5P_FIMV_NV12M_HALIGN_V6); ctx->luma_size = ALIGN((mb_width * mb_height) * 256, 256); ctx->chroma_size = ALIGN((mb_width * mb_height) * 128, 256); + + /* MFCv7 needs pad bytes for Luma and Chroma */ + if (IS_MFCV7(ctx->dev)) { + ctx->luma_size += MFC_LUMA_PAD_BYTES_V7; + ctx->chroma_size += MFC_CHROMA_PAD_BYTES_V7; + } } /* Set registers for decoding stream buffer */ @@ -453,8 +482,13 @@ static void s5p_mfc_set_enc_frame_buffer_v6(struct s5p_mfc_ctx *ctx, { struct s5p_mfc_dev *dev = ctx->dev; - WRITEL(y_addr, S5P_FIMV_E_SOURCE_LUMA_ADDR_V6); /* 256B align */ - WRITEL(c_addr, S5P_FIMV_E_SOURCE_CHROMA_ADDR_V6); + if (IS_MFCV7(dev)) { + WRITEL(y_addr, S5P_FIMV_E_SOURCE_FIRST_ADDR_V7); + WRITEL(c_addr, S5P_FIMV_E_SOURCE_SECOND_ADDR_V7); + } else { + WRITEL(y_addr, S5P_FIMV_E_SOURCE_LUMA_ADDR_V6); + WRITEL(c_addr, S5P_FIMV_E_SOURCE_CHROMA_ADDR_V6); + } mfc_debug(2, "enc src y buf addr: 0x%08lx\n", y_addr); mfc_debug(2, "enc src c buf addr: 0x%08lx\n", c_addr); @@ -466,8 +500,13 @@ static void s5p_mfc_get_enc_frame_buffer_v6(struct s5p_mfc_ctx *ctx, struct s5p_mfc_dev *dev = ctx->dev; unsigned long enc_recon_y_addr, enc_recon_c_addr; - *y_addr = READL(S5P_FIMV_E_ENCODED_SOURCE_LUMA_ADDR_V6); - *c_addr = READL(S5P_FIMV_E_ENCODED_SOURCE_CHROMA_ADDR_V6); + if (IS_MFCV7(dev)) { + *y_addr = READL(S5P_FIMV_E_ENCODED_SOURCE_FIRST_ADDR_V7); + *c_addr = READL(S5P_FIMV_E_ENCODED_SOURCE_SECOND_ADDR_V7); + } else { + *y_addr = READL(S5P_FIMV_E_ENCODED_SOURCE_LUMA_ADDR_V6); + *c_addr = READL(S5P_FIMV_E_ENCODED_SOURCE_CHROMA_ADDR_V6); + } enc_recon_y_addr = READL(S5P_FIMV_E_RECON_LUMA_DPB_ADDR_V6); enc_recon_c_addr = READL(S5P_FIMV_E_RECON_CHROMA_DPB_ADDR_V6); @@ -1140,6 +1179,80 @@ static int s5p_mfc_set_enc_params_h263(struct s5p_mfc_ctx *ctx) return 0; } +static int s5p_mfc_set_enc_params_vp8(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_enc_params *p = &ctx->enc_params; + struct s5p_mfc_vp8_enc_params *p_vp8 = &p->codec.vp8; + unsigned int reg = 0; + unsigned int val = 0; + + mfc_debug_enter(); + + s5p_mfc_set_enc_params(ctx); + + /* pictype : number of B */ + reg = READL(S5P_FIMV_E_GOP_CONFIG_V6); + reg &= ~(0x3 << 16); + reg |= ((p->num_b_frame & 0x3) << 16); + WRITEL(reg, S5P_FIMV_E_GOP_CONFIG_V6); + + /* profile & level */ + reg = 0; + /** profile */ + reg |= (0x1 << 4); + WRITEL(reg, S5P_FIMV_E_PICTURE_PROFILE_V6); + + /* rate control config. */ + reg = READL(S5P_FIMV_E_RC_CONFIG_V6); + /** macroblock level rate control */ + reg &= ~(0x1 << 8); + reg |= ((p->rc_mb & 0x1) << 8); + WRITEL(reg, S5P_FIMV_E_RC_CONFIG_V6); + + /* frame rate */ + if (p->rc_frame && p->rc_framerate_num && p->rc_framerate_denom) { + reg = 0; + reg |= ((p->rc_framerate_num & 0xFFFF) << 16); + reg |= p->rc_framerate_denom & 0xFFFF; + WRITEL(reg, S5P_FIMV_E_RC_FRAME_RATE_V6); + } + + /* vbv buffer size */ + if (p->frame_skip_mode == + V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT) { + WRITEL(p->vbv_size & 0xFFFF, S5P_FIMV_E_VBV_BUFFER_SIZE_V6); + + if (p->rc_frame) + WRITEL(p->vbv_delay, S5P_FIMV_E_VBV_INIT_DELAY_V6); + } + + /* VP8 specific params */ + reg = 0; + reg |= (p_vp8->imd_4x4 & 0x1) << 10; + switch (p_vp8->num_partitions) { + case V4L2_CID_MPEG_VIDEO_VPX_1_PARTITION: + val = 0; + break; + case V4L2_CID_MPEG_VIDEO_VPX_2_PARTITIONS: + val = 2; + break; + case V4L2_CID_MPEG_VIDEO_VPX_4_PARTITIONS: + val = 4; + break; + case V4L2_CID_MPEG_VIDEO_VPX_8_PARTITIONS: + val = 8; + break; + } + reg |= (val & 0xF) << 3; + reg |= (p_vp8->num_ref & 0x2); + WRITEL(reg, S5P_FIMV_E_VP8_OPTIONS_V7); + + mfc_debug_leave(); + + return 0; +} + /* Initialize decoding */ static int s5p_mfc_init_decode_v6(struct s5p_mfc_ctx *ctx) { @@ -1166,6 +1279,12 @@ static int s5p_mfc_init_decode_v6(struct s5p_mfc_ctx *ctx) reg |= (0x1 << S5P_FIMV_D_OPT_DDELAY_EN_SHIFT_V6); WRITEL(ctx->display_delay, S5P_FIMV_D_DISPLAY_DELAY_V6); } + + if (IS_MFCV7(dev)) { + WRITEL(reg, S5P_FIMV_D_DEC_OPTIONS_V6); + reg = 0; + } + /* Setup loop filter, for decoding this is only valid for MPEG4 */ if (ctx->codec_mode == S5P_MFC_CODEC_MPEG4_DEC) { mfc_debug(2, "Set loop filter to: %d\n", @@ -1176,7 +1295,10 @@ static int s5p_mfc_init_decode_v6(struct s5p_mfc_ctx *ctx) if (ctx->dst_fmt->fourcc == V4L2_PIX_FMT_NV12MT_16X16) reg |= (0x1 << S5P_FIMV_D_OPT_TILE_MODE_SHIFT_V6); - WRITEL(reg, S5P_FIMV_D_DEC_OPTIONS_V6); + if (IS_MFCV7(dev)) + WRITEL(reg, S5P_FIMV_D_INIT_BUFFER_OPTIONS_V7); + else + WRITEL(reg, S5P_FIMV_D_DEC_OPTIONS_V6); /* 0: NV12(CbCr), 1: NV21(CrCb) */ if (ctx->dst_fmt->fourcc == V4L2_PIX_FMT_NV21M) @@ -1184,6 +1306,7 @@ static int s5p_mfc_init_decode_v6(struct s5p_mfc_ctx *ctx) else WRITEL(0x0, S5P_FIMV_PIXEL_FORMAT_V6); + /* sei parse */ WRITEL(ctx->sei_fp_parse & 0x1, S5P_FIMV_D_SEI_ENABLE_V6); @@ -1248,12 +1371,20 @@ static int s5p_mfc_init_encode_v6(struct s5p_mfc_ctx *ctx) s5p_mfc_set_enc_params_mpeg4(ctx); else if (ctx->codec_mode == S5P_MFC_CODEC_H263_ENC) s5p_mfc_set_enc_params_h263(ctx); + else if (ctx->codec_mode == S5P_MFC_CODEC_VP8_ENC) + s5p_mfc_set_enc_params_vp8(ctx); else { mfc_err("Unknown codec for encoding (%x).\n", ctx->codec_mode); return -EINVAL; } + /* Set stride lengths */ + if (IS_MFCV7(dev)) { + WRITEL(ctx->img_width, S5P_FIMV_E_SOURCE_FIRST_STRIDE_V7); + WRITEL(ctx->img_width, S5P_FIMV_E_SOURCE_SECOND_STRIDE_V7); + } + WRITEL(ctx->inst_no, S5P_FIMV_INSTANCE_ID_V6); s5p_mfc_hw_call(dev->mfc_cmds, cmd_host2risc, dev, S5P_FIMV_CH_SEQ_HEADER_V6, NULL); diff --git a/drivers/media/platform/s5p-tv/hdmi_drv.c b/drivers/media/platform/s5p-tv/hdmi_drv.c index 1b34c3629858..534722c04ec5 100644 --- a/drivers/media/platform/s5p-tv/hdmi_drv.c +++ b/drivers/media/platform/s5p-tv/hdmi_drv.c @@ -37,6 +37,7 @@ #include <media/v4l2-common.h> #include <media/v4l2-dev.h> #include <media/v4l2-device.h> +#include <media/v4l2-dv-timings.h> #include "regs-hdmi.h" @@ -625,7 +626,7 @@ static int hdmi_s_dv_timings(struct v4l2_subdev *sd, int i; for (i = 0; i < ARRAY_SIZE(hdmi_timings); i++) - if (v4l_match_dv_timings(&hdmi_timings[i].dv_timings, + if (v4l2_match_dv_timings(&hdmi_timings[i].dv_timings, timings, 0)) break; if (i == ARRAY_SIZE(hdmi_timings)) { diff --git a/drivers/media/platform/soc_camera/Kconfig b/drivers/media/platform/soc_camera/Kconfig index 626dcccc37da..af39c4665554 100644 --- a/drivers/media/platform/soc_camera/Kconfig +++ b/drivers/media/platform/soc_camera/Kconfig @@ -44,6 +44,14 @@ config VIDEO_PXA27x ---help--- This is a v4l2 driver for the PXA27x Quick Capture Interface +config VIDEO_RCAR_VIN + tristate "R-Car Video Input (VIN) support" + depends on VIDEO_DEV && SOC_CAMERA + select VIDEOBUF2_DMA_CONTIG + select SOC_CAMERA_SCALE_CROP + ---help--- + This is a v4l2 driver for the R-Car VIN Interface + config VIDEO_SH_MOBILE_CSI2 tristate "SuperH Mobile MIPI CSI-2 Interface driver" depends on VIDEO_DEV && SOC_CAMERA && HAVE_CLK diff --git a/drivers/media/platform/soc_camera/Makefile b/drivers/media/platform/soc_camera/Makefile index 39186224c16a..8aed26d7a64d 100644 --- a/drivers/media/platform/soc_camera/Makefile +++ b/drivers/media/platform/soc_camera/Makefile @@ -14,3 +14,4 @@ obj-$(CONFIG_VIDEO_OMAP1) += omap1_camera.o obj-$(CONFIG_VIDEO_PXA27x) += pxa_camera.o obj-$(CONFIG_VIDEO_SH_MOBILE_CEU) += sh_mobile_ceu_camera.o obj-$(CONFIG_VIDEO_SH_MOBILE_CSI2) += sh_mobile_csi2.o +obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar_vin.o diff --git a/drivers/media/platform/soc_camera/mx3_camera.c b/drivers/media/platform/soc_camera/mx3_camera.c index 1047e3e8db77..8f9f6211c52e 100644 --- a/drivers/media/platform/soc_camera/mx3_camera.c +++ b/drivers/media/platform/soc_camera/mx3_camera.c @@ -672,7 +672,7 @@ static int mx3_camera_get_formats(struct soc_camera_device *icd, unsigned int id fmt = soc_mbus_get_fmtdesc(code); if (!fmt) { dev_warn(icd->parent, - "Unsupported format code #%u: %d\n", idx, code); + "Unsupported format code #%u: 0x%x\n", idx, code); return 0; } @@ -688,7 +688,7 @@ static int mx3_camera_get_formats(struct soc_camera_device *icd, unsigned int id xlate->host_fmt = &mx3_camera_formats[0]; xlate->code = code; xlate++; - dev_dbg(dev, "Providing format %s using code %d\n", + dev_dbg(dev, "Providing format %s using code 0x%x\n", mx3_camera_formats[0].name, code); } break; @@ -698,7 +698,7 @@ static int mx3_camera_get_formats(struct soc_camera_device *icd, unsigned int id xlate->host_fmt = &mx3_camera_formats[1]; xlate->code = code; xlate++; - dev_dbg(dev, "Providing format %s using code %d\n", + dev_dbg(dev, "Providing format %s using code 0x%x\n", mx3_camera_formats[1].name, code); } break; @@ -1144,6 +1144,7 @@ static struct soc_camera_host_ops mx3_soc_camera_host_ops = { static int mx3_camera_probe(struct platform_device *pdev) { + struct mx3_camera_pdata *pdata = pdev->dev.platform_data; struct mx3_camera_dev *mx3_cam; struct resource *res; void __iomem *base; @@ -1151,26 +1152,25 @@ static int mx3_camera_probe(struct platform_device *pdev) struct soc_camera_host *soc_host; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - err = -ENODEV; - goto egetres; - } + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + if (!pdata) + return -EINVAL; - mx3_cam = vzalloc(sizeof(*mx3_cam)); + mx3_cam = devm_kzalloc(&pdev->dev, sizeof(*mx3_cam), GFP_KERNEL); if (!mx3_cam) { dev_err(&pdev->dev, "Could not allocate mx3 camera object\n"); - err = -ENOMEM; - goto ealloc; + return -ENOMEM; } - mx3_cam->clk = clk_get(&pdev->dev, NULL); - if (IS_ERR(mx3_cam->clk)) { - err = PTR_ERR(mx3_cam->clk); - goto eclkget; - } + mx3_cam->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(mx3_cam->clk)) + return PTR_ERR(mx3_cam->clk); - mx3_cam->pdata = pdev->dev.platform_data; - mx3_cam->platform_flags = mx3_cam->pdata->flags; + mx3_cam->pdata = pdata; + mx3_cam->platform_flags = pdata->flags; if (!(mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_MASK)) { /* * Platform hasn't set available data widths. This is bad. @@ -1189,7 +1189,7 @@ static int mx3_camera_probe(struct platform_device *pdev) if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_15) mx3_cam->width_flags |= 1 << 14; - mx3_cam->mclk = mx3_cam->pdata->mclk_10khz * 10000; + mx3_cam->mclk = pdata->mclk_10khz * 10000; if (!mx3_cam->mclk) { dev_warn(&pdev->dev, "mclk_10khz == 0! Please, fix your platform data. " @@ -1201,13 +1201,6 @@ static int mx3_camera_probe(struct platform_device *pdev) INIT_LIST_HEAD(&mx3_cam->capture); spin_lock_init(&mx3_cam->lock); - base = ioremap(res->start, resource_size(res)); - if (!base) { - pr_err("Couldn't map %x@%x\n", resource_size(res), res->start); - err = -ENOMEM; - goto eioremap; - } - mx3_cam->base = base; soc_host = &mx3_cam->soc_host; @@ -1218,9 +1211,12 @@ static int mx3_camera_probe(struct platform_device *pdev) soc_host->nr = pdev->id; mx3_cam->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); - if (IS_ERR(mx3_cam->alloc_ctx)) { - err = PTR_ERR(mx3_cam->alloc_ctx); - goto eallocctx; + if (IS_ERR(mx3_cam->alloc_ctx)) + return PTR_ERR(mx3_cam->alloc_ctx); + + if (pdata->asd_sizes) { + soc_host->asd = pdata->asd; + soc_host->asd_sizes = pdata->asd_sizes; } err = soc_camera_host_register(soc_host); @@ -1234,14 +1230,6 @@ static int mx3_camera_probe(struct platform_device *pdev) ecamhostreg: vb2_dma_contig_cleanup_ctx(mx3_cam->alloc_ctx); -eallocctx: - iounmap(base); -eioremap: - clk_put(mx3_cam->clk); -eclkget: - vfree(mx3_cam); -ealloc: -egetres: return err; } @@ -1251,12 +1239,8 @@ static int mx3_camera_remove(struct platform_device *pdev) struct mx3_camera_dev *mx3_cam = container_of(soc_host, struct mx3_camera_dev, soc_host); - clk_put(mx3_cam->clk); - soc_camera_host_unregister(soc_host); - iounmap(mx3_cam->base); - /* * The channel has either not been allocated, * or should have been released @@ -1266,8 +1250,6 @@ static int mx3_camera_remove(struct platform_device *pdev) vb2_dma_contig_cleanup_ctx(mx3_cam->alloc_ctx); - vfree(mx3_cam); - dmaengine_put(); return 0; @@ -1276,6 +1258,7 @@ static int mx3_camera_remove(struct platform_device *pdev) static struct platform_driver mx3_camera_driver = { .driver = { .name = MX3_CAM_DRV_NAME, + .owner = THIS_MODULE, }, .probe = mx3_camera_probe, .remove = mx3_camera_remove, diff --git a/drivers/media/platform/soc_camera/rcar_vin.c b/drivers/media/platform/soc_camera/rcar_vin.c new file mode 100644 index 000000000000..d02a7e0b773f --- /dev/null +++ b/drivers/media/platform/soc_camera/rcar_vin.c @@ -0,0 +1,1486 @@ +/* + * SoC-camera host driver for Renesas R-Car VIN unit + * + * Copyright (C) 2011-2013 Renesas Solutions Corp. + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> + * + * Based on V4L2 Driver for SuperH Mobile CEU interface "sh_mobile_ceu_camera.c" + * + * Copyright (C) 2008 Magnus Damm + * + * 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. + */ + +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_data/camera-rcar.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/videodev2.h> + +#include <media/soc_camera.h> +#include <media/soc_mediabus.h> +#include <media/v4l2-common.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-device.h> +#include <media/v4l2-mediabus.h> +#include <media/v4l2-subdev.h> +#include <media/videobuf2-dma-contig.h> + +#include "soc_scale_crop.h" + +#define DRV_NAME "rcar_vin" + +/* Register offsets for R-Car VIN */ +#define VNMC_REG 0x00 /* Video n Main Control Register */ +#define VNMS_REG 0x04 /* Video n Module Status Register */ +#define VNFC_REG 0x08 /* Video n Frame Capture Register */ +#define VNSLPRC_REG 0x0C /* Video n Start Line Pre-Clip Register */ +#define VNELPRC_REG 0x10 /* Video n End Line Pre-Clip Register */ +#define VNSPPRC_REG 0x14 /* Video n Start Pixel Pre-Clip Register */ +#define VNEPPRC_REG 0x18 /* Video n End Pixel Pre-Clip Register */ +#define VNSLPOC_REG 0x1C /* Video n Start Line Post-Clip Register */ +#define VNELPOC_REG 0x20 /* Video n End Line Post-Clip Register */ +#define VNSPPOC_REG 0x24 /* Video n Start Pixel Post-Clip Register */ +#define VNEPPOC_REG 0x28 /* Video n End Pixel Post-Clip Register */ +#define VNIS_REG 0x2C /* Video n Image Stride Register */ +#define VNMB_REG(m) (0x30 + ((m) << 2)) /* Video n Memory Base m Register */ +#define VNIE_REG 0x40 /* Video n Interrupt Enable Register */ +#define VNINTS_REG 0x44 /* Video n Interrupt Status Register */ +#define VNSI_REG 0x48 /* Video n Scanline Interrupt Register */ +#define VNMTC_REG 0x4C /* Video n Memory Transfer Control Register */ +#define VNYS_REG 0x50 /* Video n Y Scale Register */ +#define VNXS_REG 0x54 /* Video n X Scale Register */ +#define VNDMR_REG 0x58 /* Video n Data Mode Register */ +#define VNDMR2_REG 0x5C /* Video n Data Mode Register 2 */ +#define VNUVAOF_REG 0x60 /* Video n UV Address Offset Register */ + +/* Register bit fields for R-Car VIN */ +/* Video n Main Control Register bits */ +#define VNMC_FOC (1 << 21) +#define VNMC_YCAL (1 << 19) +#define VNMC_INF_YUV8_BT656 (0 << 16) +#define VNMC_INF_YUV8_BT601 (1 << 16) +#define VNMC_INF_YUV16 (5 << 16) +#define VNMC_VUP (1 << 10) +#define VNMC_IM_ODD (0 << 3) +#define VNMC_IM_ODD_EVEN (1 << 3) +#define VNMC_IM_EVEN (2 << 3) +#define VNMC_IM_FULL (3 << 3) +#define VNMC_BPS (1 << 1) +#define VNMC_ME (1 << 0) + +/* Video n Module Status Register bits */ +#define VNMS_FBS_MASK (3 << 3) +#define VNMS_FBS_SHIFT 3 +#define VNMS_AV (1 << 1) +#define VNMS_CA (1 << 0) + +/* Video n Frame Capture Register bits */ +#define VNFC_C_FRAME (1 << 1) +#define VNFC_S_FRAME (1 << 0) + +/* Video n Interrupt Enable Register bits */ +#define VNIE_FIE (1 << 4) +#define VNIE_EFE (1 << 1) + +/* Video n Data Mode Register bits */ +#define VNDMR_EXRGB (1 << 8) +#define VNDMR_BPSM (1 << 4) +#define VNDMR_DTMD_YCSEP (1 << 1) +#define VNDMR_DTMD_ARGB1555 (1 << 0) + +/* Video n Data Mode Register 2 bits */ +#define VNDMR2_VPS (1 << 30) +#define VNDMR2_HPS (1 << 29) +#define VNDMR2_FTEV (1 << 17) + +#define VIN_MAX_WIDTH 2048 +#define VIN_MAX_HEIGHT 2048 + +enum chip_id { + RCAR_H1, + RCAR_M1, + RCAR_E1, +}; + +enum rcar_vin_state { + STOPPED = 0, + RUNNING, + STOPPING, +}; + +struct rcar_vin_priv { + void __iomem *base; + spinlock_t lock; + int sequence; + /* State of the VIN module in capturing mode */ + enum rcar_vin_state state; + struct rcar_vin_platform_data *pdata; + struct soc_camera_host ici; + struct list_head capture; +#define MAX_BUFFER_NUM 3 + struct vb2_buffer *queue_buf[MAX_BUFFER_NUM]; + struct vb2_alloc_ctx *alloc_ctx; + enum v4l2_field field; + unsigned int vb_count; + unsigned int nr_hw_slots; + bool request_to_stop; + struct completion capture_stop; + enum chip_id chip; +}; + +#define is_continuous_transfer(priv) (priv->vb_count > MAX_BUFFER_NUM) + +struct rcar_vin_buffer { + struct vb2_buffer vb; + struct list_head list; +}; + +#define to_buf_list(vb2_buffer) (&container_of(vb2_buffer, \ + struct rcar_vin_buffer, \ + vb)->list) + +struct rcar_vin_cam { + /* VIN offsets within the camera output, before the VIN scaler */ + unsigned int vin_left; + unsigned int vin_top; + /* Client output, as seen by the VIN */ + unsigned int width; + unsigned int height; + /* + * User window from S_CROP / G_CROP, produced by client cropping and + * scaling, VIN scaling and VIN cropping, mapped back onto the client + * input window + */ + struct v4l2_rect subrect; + /* Camera cropping rectangle */ + struct v4l2_rect rect; + const struct soc_mbus_pixelfmt *extra_fmt; +}; + +/* + * .queue_setup() is called to check whether the driver can accept the requested + * number of buffers and to fill in plane sizes for the current frame format if + * required + */ +static int rcar_vin_videobuf_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *count, + unsigned int *num_planes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct soc_camera_device *icd = soc_camera_from_vb2q(vq); + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_priv *priv = ici->priv; + + if (fmt) { + const struct soc_camera_format_xlate *xlate; + unsigned int bytes_per_line; + int ret; + + xlate = soc_camera_xlate_by_fourcc(icd, + fmt->fmt.pix.pixelformat); + if (!xlate) + return -EINVAL; + ret = soc_mbus_bytes_per_line(fmt->fmt.pix.width, + xlate->host_fmt); + if (ret < 0) + return ret; + + bytes_per_line = max_t(u32, fmt->fmt.pix.bytesperline, ret); + + ret = soc_mbus_image_size(xlate->host_fmt, bytes_per_line, + fmt->fmt.pix.height); + if (ret < 0) + return ret; + + sizes[0] = max_t(u32, fmt->fmt.pix.sizeimage, ret); + } else { + /* Called from VIDIOC_REQBUFS or in compatibility mode */ + sizes[0] = icd->sizeimage; + } + + alloc_ctxs[0] = priv->alloc_ctx; + + if (!vq->num_buffers) + priv->sequence = 0; + + if (!*count) + *count = 2; + priv->vb_count = *count; + + *num_planes = 1; + + /* Number of hardware slots */ + if (is_continuous_transfer(priv)) + priv->nr_hw_slots = MAX_BUFFER_NUM; + else + priv->nr_hw_slots = 1; + + dev_dbg(icd->parent, "count=%d, size=%u\n", *count, sizes[0]); + + return 0; +} + +static int rcar_vin_setup(struct rcar_vin_priv *priv) +{ + struct soc_camera_device *icd = priv->ici.icd; + struct rcar_vin_cam *cam = icd->host_priv; + u32 vnmc, dmr, interrupts; + bool progressive = false, output_is_yuv = false; + + switch (priv->field) { + case V4L2_FIELD_TOP: + vnmc = VNMC_IM_ODD; + break; + case V4L2_FIELD_BOTTOM: + vnmc = VNMC_IM_EVEN; + break; + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_INTERLACED_TB: + vnmc = VNMC_IM_FULL; + break; + case V4L2_FIELD_INTERLACED_BT: + vnmc = VNMC_IM_FULL | VNMC_FOC; + break; + case V4L2_FIELD_NONE: + if (is_continuous_transfer(priv)) { + vnmc = VNMC_IM_ODD_EVEN; + progressive = true; + } else { + vnmc = VNMC_IM_ODD; + } + break; + default: + vnmc = VNMC_IM_ODD; + break; + } + + /* input interface */ + switch (icd->current_fmt->code) { + case V4L2_MBUS_FMT_YUYV8_1X16: + /* BT.601/BT.1358 16bit YCbCr422 */ + vnmc |= VNMC_INF_YUV16; + break; + case V4L2_MBUS_FMT_YUYV8_2X8: + /* BT.656 8bit YCbCr422 or BT.601 8bit YCbCr422 */ + vnmc |= priv->pdata->flags & RCAR_VIN_BT656 ? + VNMC_INF_YUV8_BT656 : VNMC_INF_YUV8_BT601; + default: + break; + } + + /* output format */ + switch (icd->current_fmt->host_fmt->fourcc) { + case V4L2_PIX_FMT_NV16: + iowrite32(ALIGN(cam->width * cam->height, 0x80), + priv->base + VNUVAOF_REG); + dmr = VNDMR_DTMD_YCSEP; + output_is_yuv = true; + break; + case V4L2_PIX_FMT_YUYV: + dmr = VNDMR_BPSM; + output_is_yuv = true; + break; + case V4L2_PIX_FMT_UYVY: + dmr = 0; + output_is_yuv = true; + break; + case V4L2_PIX_FMT_RGB555X: + dmr = VNDMR_DTMD_ARGB1555; + break; + case V4L2_PIX_FMT_RGB565: + dmr = 0; + break; + case V4L2_PIX_FMT_RGB32: + if (priv->chip == RCAR_H1 || priv->chip == RCAR_E1) { + dmr = VNDMR_EXRGB; + break; + } + default: + dev_warn(icd->parent, "Invalid fourcc format (0x%x)\n", + icd->current_fmt->host_fmt->fourcc); + return -EINVAL; + } + + /* Always update on field change */ + vnmc |= VNMC_VUP; + + /* If input and output use the same colorspace, use bypass mode */ + if (output_is_yuv) + vnmc |= VNMC_BPS; + + /* progressive or interlaced mode */ + interrupts = progressive ? VNIE_FIE | VNIE_EFE : VNIE_EFE; + + /* ack interrupts */ + iowrite32(interrupts, priv->base + VNINTS_REG); + /* enable interrupts */ + iowrite32(interrupts, priv->base + VNIE_REG); + /* start capturing */ + iowrite32(dmr, priv->base + VNDMR_REG); + iowrite32(vnmc | VNMC_ME, priv->base + VNMC_REG); + + return 0; +} + +static void rcar_vin_capture(struct rcar_vin_priv *priv) +{ + if (is_continuous_transfer(priv)) + /* Continuous Frame Capture Mode */ + iowrite32(VNFC_C_FRAME, priv->base + VNFC_REG); + else + /* Single Frame Capture Mode */ + iowrite32(VNFC_S_FRAME, priv->base + VNFC_REG); +} + +static void rcar_vin_request_capture_stop(struct rcar_vin_priv *priv) +{ + priv->state = STOPPING; + + /* set continuous & single transfer off */ + iowrite32(0, priv->base + VNFC_REG); + /* disable capture (release DMA buffer), reset */ + iowrite32(ioread32(priv->base + VNMC_REG) & ~VNMC_ME, + priv->base + VNMC_REG); + + /* update the status if stopped already */ + if (!(ioread32(priv->base + VNMS_REG) & VNMS_CA)) + priv->state = STOPPED; +} + +static int rcar_vin_get_free_hw_slot(struct rcar_vin_priv *priv) +{ + int slot; + + for (slot = 0; slot < priv->nr_hw_slots; slot++) + if (priv->queue_buf[slot] == NULL) + return slot; + + return -1; +} + +static int rcar_vin_hw_ready(struct rcar_vin_priv *priv) +{ + /* Ensure all HW slots are filled */ + return rcar_vin_get_free_hw_slot(priv) < 0 ? 1 : 0; +} + +/* Moves a buffer from the queue to the HW slots */ +static int rcar_vin_fill_hw_slot(struct rcar_vin_priv *priv) +{ + struct vb2_buffer *vb; + dma_addr_t phys_addr_top; + int slot; + + if (list_empty(&priv->capture)) + return 0; + + /* Find a free HW slot */ + slot = rcar_vin_get_free_hw_slot(priv); + if (slot < 0) + return 0; + + vb = &list_entry(priv->capture.next, struct rcar_vin_buffer, list)->vb; + list_del_init(to_buf_list(vb)); + priv->queue_buf[slot] = vb; + phys_addr_top = vb2_dma_contig_plane_dma_addr(vb, 0); + iowrite32(phys_addr_top, priv->base + VNMB_REG(slot)); + + return 1; +} + +static void rcar_vin_videobuf_queue(struct vb2_buffer *vb) +{ + struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue); + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_priv *priv = ici->priv; + unsigned long size; + + size = icd->sizeimage; + + if (vb2_plane_size(vb, 0) < size) { + dev_err(icd->parent, "Buffer #%d too small (%lu < %lu)\n", + vb->v4l2_buf.index, vb2_plane_size(vb, 0), size); + goto error; + } + + vb2_set_plane_payload(vb, 0, size); + + dev_dbg(icd->parent, "%s (vb=0x%p) 0x%p %lu\n", __func__, + vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0)); + + spin_lock_irq(&priv->lock); + + list_add_tail(to_buf_list(vb), &priv->capture); + rcar_vin_fill_hw_slot(priv); + + /* If we weren't running, and have enough buffers, start capturing! */ + if (priv->state != RUNNING && rcar_vin_hw_ready(priv)) { + if (rcar_vin_setup(priv)) { + /* Submit error */ + list_del_init(to_buf_list(vb)); + spin_unlock_irq(&priv->lock); + goto error; + } + priv->request_to_stop = false; + init_completion(&priv->capture_stop); + priv->state = RUNNING; + rcar_vin_capture(priv); + } + + spin_unlock_irq(&priv->lock); + + return; + +error: + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); +} + +static void rcar_vin_videobuf_release(struct vb2_buffer *vb) +{ + struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue); + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_priv *priv = ici->priv; + unsigned int i; + int buf_in_use = 0; + + spin_lock_irq(&priv->lock); + + /* Is the buffer in use by the VIN hardware? */ + for (i = 0; i < MAX_BUFFER_NUM; i++) { + if (priv->queue_buf[i] == vb) { + buf_in_use = 1; + break; + } + } + + if (buf_in_use) { + while (priv->state != STOPPED) { + + /* issue stop if running */ + if (priv->state == RUNNING) + rcar_vin_request_capture_stop(priv); + + /* wait until capturing has been stopped */ + if (priv->state == STOPPING) { + priv->request_to_stop = true; + spin_unlock_irq(&priv->lock); + wait_for_completion(&priv->capture_stop); + spin_lock_irq(&priv->lock); + } + } + /* + * Capturing has now stopped. The buffer we have been asked + * to release could be any of the current buffers in use, so + * release all buffers that are in use by HW + */ + for (i = 0; i < MAX_BUFFER_NUM; i++) { + if (priv->queue_buf[i]) { + vb2_buffer_done(priv->queue_buf[i], + VB2_BUF_STATE_ERROR); + priv->queue_buf[i] = NULL; + } + } + } else { + list_del_init(to_buf_list(vb)); + } + + spin_unlock_irq(&priv->lock); +} + +static int rcar_vin_videobuf_init(struct vb2_buffer *vb) +{ + INIT_LIST_HEAD(to_buf_list(vb)); + return 0; +} + +static int rcar_vin_stop_streaming(struct vb2_queue *vq) +{ + struct soc_camera_device *icd = soc_camera_from_vb2q(vq); + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_priv *priv = ici->priv; + struct list_head *buf_head, *tmp; + + spin_lock_irq(&priv->lock); + list_for_each_safe(buf_head, tmp, &priv->capture) + list_del_init(buf_head); + spin_unlock_irq(&priv->lock); + + return 0; +} + +static struct vb2_ops rcar_vin_vb2_ops = { + .queue_setup = rcar_vin_videobuf_setup, + .buf_init = rcar_vin_videobuf_init, + .buf_cleanup = rcar_vin_videobuf_release, + .buf_queue = rcar_vin_videobuf_queue, + .stop_streaming = rcar_vin_stop_streaming, + .wait_prepare = soc_camera_unlock, + .wait_finish = soc_camera_lock, +}; + +static irqreturn_t rcar_vin_irq(int irq, void *data) +{ + struct rcar_vin_priv *priv = data; + u32 int_status; + bool can_run = false, hw_stopped; + int slot; + unsigned int handled = 0; + + spin_lock(&priv->lock); + + int_status = ioread32(priv->base + VNINTS_REG); + if (!int_status) + goto done; + /* ack interrupts */ + iowrite32(int_status, priv->base + VNINTS_REG); + handled = 1; + + /* nothing to do if capture status is 'STOPPED' */ + if (priv->state == STOPPED) + goto done; + + hw_stopped = !(ioread32(priv->base + VNMS_REG) & VNMS_CA); + + if (!priv->request_to_stop) { + if (is_continuous_transfer(priv)) + slot = (ioread32(priv->base + VNMS_REG) & + VNMS_FBS_MASK) >> VNMS_FBS_SHIFT; + else + slot = 0; + + priv->queue_buf[slot]->v4l2_buf.field = priv->field; + priv->queue_buf[slot]->v4l2_buf.sequence = priv->sequence++; + do_gettimeofday(&priv->queue_buf[slot]->v4l2_buf.timestamp); + vb2_buffer_done(priv->queue_buf[slot], VB2_BUF_STATE_DONE); + priv->queue_buf[slot] = NULL; + + if (priv->state != STOPPING) + can_run = rcar_vin_fill_hw_slot(priv); + + if (hw_stopped || !can_run) { + priv->state = STOPPED; + } else if (is_continuous_transfer(priv) && + list_empty(&priv->capture) && + priv->state == RUNNING) { + /* + * The continuous capturing requires an explicit stop + * operation when there is no buffer to be set into + * the VnMBm registers. + */ + rcar_vin_request_capture_stop(priv); + } else { + rcar_vin_capture(priv); + } + + } else if (hw_stopped) { + priv->state = STOPPED; + priv->request_to_stop = false; + complete(&priv->capture_stop); + } + +done: + spin_unlock(&priv->lock); + + return IRQ_RETVAL(handled); +} + +static int rcar_vin_add_device(struct soc_camera_device *icd) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_priv *priv = ici->priv; + int i; + + for (i = 0; i < MAX_BUFFER_NUM; i++) + priv->queue_buf[i] = NULL; + + pm_runtime_get_sync(ici->v4l2_dev.dev); + + dev_dbg(icd->parent, "R-Car VIN driver attached to camera %d\n", + icd->devnum); + + return 0; +} + +static void rcar_vin_remove_device(struct soc_camera_device *icd) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_priv *priv = ici->priv; + struct vb2_buffer *vb; + int i; + + /* disable capture, disable interrupts */ + iowrite32(ioread32(priv->base + VNMC_REG) & ~VNMC_ME, + priv->base + VNMC_REG); + iowrite32(0, priv->base + VNIE_REG); + + priv->state = STOPPED; + priv->request_to_stop = false; + + /* make sure active buffer is cancelled */ + spin_lock_irq(&priv->lock); + for (i = 0; i < MAX_BUFFER_NUM; i++) { + vb = priv->queue_buf[i]; + if (vb) { + list_del_init(to_buf_list(vb)); + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + } + } + spin_unlock_irq(&priv->lock); + + pm_runtime_put(ici->v4l2_dev.dev); + + dev_dbg(icd->parent, "R-Car VIN driver detached from camera %d\n", + icd->devnum); +} + +/* Called with .host_lock held */ +static int rcar_vin_clock_start(struct soc_camera_host *ici) +{ + /* VIN does not have "mclk" */ + return 0; +} + +/* Called with .host_lock held */ +static void rcar_vin_clock_stop(struct soc_camera_host *ici) +{ + /* VIN does not have "mclk" */ +} + +/* rect is guaranteed to not exceed the scaled camera rectangle */ +static int rcar_vin_set_rect(struct soc_camera_device *icd) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_cam *cam = icd->host_priv; + struct rcar_vin_priv *priv = ici->priv; + unsigned int left_offset, top_offset; + unsigned char dsize = 0; + struct v4l2_rect *cam_subrect = &cam->subrect; + + dev_dbg(icd->parent, "Crop %ux%u@%u:%u\n", + icd->user_width, icd->user_height, cam->vin_left, cam->vin_top); + + left_offset = cam->vin_left; + top_offset = cam->vin_top; + + if (icd->current_fmt->host_fmt->fourcc == V4L2_PIX_FMT_RGB32 && + priv->chip == RCAR_E1) + dsize = 1; + + dev_dbg(icd->parent, "Cam %ux%u@%u:%u\n", + cam->width, cam->height, cam->vin_left, cam->vin_top); + dev_dbg(icd->parent, "Cam subrect %ux%u@%u:%u\n", + cam_subrect->width, cam_subrect->height, + cam_subrect->left, cam_subrect->top); + + /* Set Start/End Pixel/Line Pre-Clip */ + iowrite32(left_offset << dsize, priv->base + VNSPPRC_REG); + iowrite32((left_offset + cam->width - 1) << dsize, + priv->base + VNEPPRC_REG); + switch (priv->field) { + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + iowrite32(top_offset / 2, priv->base + VNSLPRC_REG); + iowrite32((top_offset + cam->height) / 2 - 1, + priv->base + VNELPRC_REG); + break; + default: + iowrite32(top_offset, priv->base + VNSLPRC_REG); + iowrite32(top_offset + cam->height - 1, + priv->base + VNELPRC_REG); + break; + } + + /* Set Start/End Pixel/Line Post-Clip */ + iowrite32(0, priv->base + VNSPPOC_REG); + iowrite32(0, priv->base + VNSLPOC_REG); + iowrite32((cam_subrect->width - 1) << dsize, priv->base + VNEPPOC_REG); + switch (priv->field) { + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + iowrite32(cam_subrect->height / 2 - 1, + priv->base + VNELPOC_REG); + break; + default: + iowrite32(cam_subrect->height - 1, priv->base + VNELPOC_REG); + break; + } + + iowrite32(ALIGN(cam->width, 0x10), priv->base + VNIS_REG); + + return 0; +} + +static void capture_stop_preserve(struct rcar_vin_priv *priv, u32 *vnmc) +{ + *vnmc = ioread32(priv->base + VNMC_REG); + /* module disable */ + iowrite32(*vnmc & ~VNMC_ME, priv->base + VNMC_REG); +} + +static void capture_restore(struct rcar_vin_priv *priv, u32 vnmc) +{ + unsigned long timeout = jiffies + 10 * HZ; + + /* + * Wait until the end of the current frame. It can take a long time, + * but if it has been aborted by a MRST1 reset, it should exit sooner. + */ + while ((ioread32(priv->base + VNMS_REG) & VNMS_AV) && + time_before(jiffies, timeout)) + msleep(1); + + if (time_after(jiffies, timeout)) { + dev_err(priv->ici.v4l2_dev.dev, + "Timeout waiting for frame end! Interface problem?\n"); + return; + } + + iowrite32(vnmc, priv->base + VNMC_REG); +} + +#define VIN_MBUS_FLAGS (V4L2_MBUS_MASTER | \ + V4L2_MBUS_PCLK_SAMPLE_RISING | \ + V4L2_MBUS_HSYNC_ACTIVE_HIGH | \ + V4L2_MBUS_HSYNC_ACTIVE_LOW | \ + V4L2_MBUS_VSYNC_ACTIVE_HIGH | \ + V4L2_MBUS_VSYNC_ACTIVE_LOW | \ + V4L2_MBUS_DATA_ACTIVE_HIGH) + +static int rcar_vin_set_bus_param(struct soc_camera_device *icd) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_priv *priv = ici->priv; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct v4l2_mbus_config cfg; + unsigned long common_flags; + u32 vnmc; + u32 val; + int ret; + + capture_stop_preserve(priv, &vnmc); + + ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg); + if (!ret) { + common_flags = soc_mbus_config_compatible(&cfg, VIN_MBUS_FLAGS); + if (!common_flags) { + dev_warn(icd->parent, + "MBUS flags incompatible: camera 0x%x, host 0x%x\n", + cfg.flags, VIN_MBUS_FLAGS); + return -EINVAL; + } + } else if (ret != -ENOIOCTLCMD) { + return ret; + } else { + common_flags = VIN_MBUS_FLAGS; + } + + /* Make choises, based on platform preferences */ + if ((common_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) && + (common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) { + if (priv->pdata->flags & RCAR_VIN_HSYNC_ACTIVE_LOW) + common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_HIGH; + else + common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_LOW; + } + + if ((common_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) && + (common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) { + if (priv->pdata->flags & RCAR_VIN_VSYNC_ACTIVE_LOW) + common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_HIGH; + else + common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_LOW; + } + + cfg.flags = common_flags; + ret = v4l2_subdev_call(sd, video, s_mbus_config, &cfg); + if (ret < 0 && ret != -ENOIOCTLCMD) + return ret; + + val = priv->field == V4L2_FIELD_NONE ? VNDMR2_FTEV : 0; + if (!(common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) + val |= VNDMR2_VPS; + if (!(common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) + val |= VNDMR2_HPS; + iowrite32(val, priv->base + VNDMR2_REG); + + ret = rcar_vin_set_rect(icd); + if (ret < 0) + return ret; + + capture_restore(priv, vnmc); + + return 0; +} + +static int rcar_vin_try_bus_param(struct soc_camera_device *icd, + unsigned char buswidth) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct v4l2_mbus_config cfg; + int ret; + + ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg); + if (ret == -ENOIOCTLCMD) + return 0; + else if (ret) + return ret; + + if (buswidth > 24) + return -EINVAL; + + /* check is there common mbus flags */ + ret = soc_mbus_config_compatible(&cfg, VIN_MBUS_FLAGS); + if (ret) + return 0; + + dev_warn(icd->parent, + "MBUS flags incompatible: camera 0x%x, host 0x%x\n", + cfg.flags, VIN_MBUS_FLAGS); + + return -EINVAL; +} + +static bool rcar_vin_packing_supported(const struct soc_mbus_pixelfmt *fmt) +{ + return fmt->packing == SOC_MBUS_PACKING_NONE || + (fmt->bits_per_sample > 8 && + fmt->packing == SOC_MBUS_PACKING_EXTEND16); +} + +static const struct soc_mbus_pixelfmt rcar_vin_formats[] = { + { + .fourcc = V4L2_PIX_FMT_NV16, + .name = "NV16", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_2X8_PADHI, + .order = SOC_MBUS_ORDER_LE, + .layout = SOC_MBUS_LAYOUT_PLANAR_Y_C, + }, + { + .fourcc = V4L2_PIX_FMT_UYVY, + .name = "UYVY", + .bits_per_sample = 16, + .packing = SOC_MBUS_PACKING_NONE, + .order = SOC_MBUS_ORDER_LE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, + { + .fourcc = V4L2_PIX_FMT_RGB565, + .name = "RGB565", + .bits_per_sample = 16, + .packing = SOC_MBUS_PACKING_NONE, + .order = SOC_MBUS_ORDER_LE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, + { + .fourcc = V4L2_PIX_FMT_RGB555X, + .name = "ARGB1555", + .bits_per_sample = 16, + .packing = SOC_MBUS_PACKING_NONE, + .order = SOC_MBUS_ORDER_LE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, + { + .fourcc = V4L2_PIX_FMT_RGB32, + .name = "RGB888", + .bits_per_sample = 32, + .packing = SOC_MBUS_PACKING_NONE, + .order = SOC_MBUS_ORDER_LE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, +}; + +static int rcar_vin_get_formats(struct soc_camera_device *icd, unsigned int idx, + struct soc_camera_format_xlate *xlate) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct device *dev = icd->parent; + int ret, k, n; + int formats = 0; + struct rcar_vin_cam *cam; + enum v4l2_mbus_pixelcode code; + const struct soc_mbus_pixelfmt *fmt; + + ret = v4l2_subdev_call(sd, video, enum_mbus_fmt, idx, &code); + if (ret < 0) + return 0; + + fmt = soc_mbus_get_fmtdesc(code); + if (!fmt) { + dev_warn(dev, "unsupported format code #%u: %d\n", idx, code); + return 0; + } + + ret = rcar_vin_try_bus_param(icd, fmt->bits_per_sample); + if (ret < 0) + return 0; + + if (!icd->host_priv) { + struct v4l2_mbus_framefmt mf; + struct v4l2_rect rect; + struct device *dev = icd->parent; + int shift; + + ret = v4l2_subdev_call(sd, video, g_mbus_fmt, &mf); + if (ret < 0) + return ret; + + /* Cache current client geometry */ + ret = soc_camera_client_g_rect(sd, &rect); + if (ret == -ENOIOCTLCMD) { + /* Sensor driver doesn't support cropping */ + rect.left = 0; + rect.top = 0; + rect.width = mf.width; + rect.height = mf.height; + } else if (ret < 0) { + return ret; + } + + /* + * If sensor proposes too large format then try smaller ones: + * 1280x960, 640x480, 320x240 + */ + for (shift = 0; shift < 3; shift++) { + if (mf.width <= VIN_MAX_WIDTH && + mf.height <= VIN_MAX_HEIGHT) + break; + + mf.width = 1280 >> shift; + mf.height = 960 >> shift; + ret = v4l2_device_call_until_err(sd->v4l2_dev, + soc_camera_grp_id(icd), + video, s_mbus_fmt, + &mf); + if (ret < 0) + return ret; + } + + if (shift == 3) { + dev_err(dev, + "Failed to configure the client below %ux%x\n", + mf.width, mf.height); + return -EIO; + } + + dev_dbg(dev, "camera fmt %ux%u\n", mf.width, mf.height); + + cam = kzalloc(sizeof(*cam), GFP_KERNEL); + if (!cam) + return -ENOMEM; + /* + * We are called with current camera crop, + * initialise subrect with it + */ + cam->rect = rect; + cam->subrect = rect; + cam->width = mf.width; + cam->height = mf.height; + + icd->host_priv = cam; + } else { + cam = icd->host_priv; + } + + /* Beginning of a pass */ + if (!idx) + cam->extra_fmt = NULL; + + switch (code) { + case V4L2_MBUS_FMT_YUYV8_1X16: + case V4L2_MBUS_FMT_YUYV8_2X8: + if (cam->extra_fmt) + break; + + /* Add all our formats that can be generated by VIN */ + cam->extra_fmt = rcar_vin_formats; + + n = ARRAY_SIZE(rcar_vin_formats); + formats += n; + for (k = 0; xlate && k < n; k++, xlate++) { + xlate->host_fmt = &rcar_vin_formats[k]; + xlate->code = code; + dev_dbg(dev, "Providing format %s using code %d\n", + rcar_vin_formats[k].name, code); + } + break; + default: + if (!rcar_vin_packing_supported(fmt)) + return 0; + + dev_dbg(dev, "Providing format %s in pass-through mode\n", + fmt->name); + break; + } + + /* Generic pass-through */ + formats++; + if (xlate) { + xlate->host_fmt = fmt; + xlate->code = code; + xlate++; + } + + return formats; +} + +static void rcar_vin_put_formats(struct soc_camera_device *icd) +{ + kfree(icd->host_priv); + icd->host_priv = NULL; +} + +static int rcar_vin_set_crop(struct soc_camera_device *icd, + const struct v4l2_crop *a) +{ + struct v4l2_crop a_writable = *a; + const struct v4l2_rect *rect = &a_writable.c; + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_priv *priv = ici->priv; + struct v4l2_crop cam_crop; + struct rcar_vin_cam *cam = icd->host_priv; + struct v4l2_rect *cam_rect = &cam_crop.c; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct device *dev = icd->parent; + struct v4l2_mbus_framefmt mf; + u32 vnmc; + int ret, i; + + dev_dbg(dev, "S_CROP(%ux%u@%u:%u)\n", rect->width, rect->height, + rect->left, rect->top); + + /* During camera cropping its output window can change too, stop VIN */ + capture_stop_preserve(priv, &vnmc); + dev_dbg(dev, "VNMC_REG 0x%x\n", vnmc); + + /* Apply iterative camera S_CROP for new input window. */ + ret = soc_camera_client_s_crop(sd, &a_writable, &cam_crop, + &cam->rect, &cam->subrect); + if (ret < 0) + return ret; + + dev_dbg(dev, "camera cropped to %ux%u@%u:%u\n", + cam_rect->width, cam_rect->height, + cam_rect->left, cam_rect->top); + + /* On success cam_crop contains current camera crop */ + + /* Retrieve camera output window */ + ret = v4l2_subdev_call(sd, video, g_mbus_fmt, &mf); + if (ret < 0) + return ret; + + if (mf.width > VIN_MAX_WIDTH || mf.height > VIN_MAX_HEIGHT) + return -EINVAL; + + /* Cache camera output window */ + cam->width = mf.width; + cam->height = mf.height; + + icd->user_width = cam->width; + icd->user_height = cam->height; + + cam->vin_left = rect->left & ~1; + cam->vin_top = rect->top & ~1; + + /* Use VIN cropping to crop to the new window. */ + ret = rcar_vin_set_rect(icd); + if (ret < 0) + return ret; + + cam->subrect = *rect; + + dev_dbg(dev, "VIN cropped to %ux%u@%u:%u\n", + icd->user_width, icd->user_height, + cam->vin_left, cam->vin_top); + + /* Restore capture */ + for (i = 0; i < MAX_BUFFER_NUM; i++) { + if (priv->queue_buf[i] && priv->state == STOPPED) { + vnmc |= VNMC_ME; + break; + } + } + capture_restore(priv, vnmc); + + /* Even if only camera cropping succeeded */ + return ret; +} + +static int rcar_vin_get_crop(struct soc_camera_device *icd, + struct v4l2_crop *a) +{ + struct rcar_vin_cam *cam = icd->host_priv; + + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + a->c = cam->subrect; + + return 0; +} + +/* Similar to set_crop multistage iterative algorithm */ +static int rcar_vin_set_fmt(struct soc_camera_device *icd, + struct v4l2_format *f) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_priv *priv = ici->priv; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct rcar_vin_cam *cam = icd->host_priv; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct v4l2_mbus_framefmt mf; + struct device *dev = icd->parent; + __u32 pixfmt = pix->pixelformat; + const struct soc_camera_format_xlate *xlate; + unsigned int vin_sub_width = 0, vin_sub_height = 0; + int ret; + bool can_scale; + enum v4l2_field field; + v4l2_std_id std; + + dev_dbg(dev, "S_FMT(pix=0x%x, %ux%u)\n", + pixfmt, pix->width, pix->height); + + switch (pix->field) { + default: + pix->field = V4L2_FIELD_NONE; + /* fall-through */ + case V4L2_FIELD_NONE: + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + field = pix->field; + break; + case V4L2_FIELD_INTERLACED: + /* Query for standard if not explicitly mentioned _TB/_BT */ + ret = v4l2_subdev_call(sd, video, querystd, &std); + if (ret < 0) + std = V4L2_STD_625_50; + + field = std & V4L2_STD_625_50 ? V4L2_FIELD_INTERLACED_TB : + V4L2_FIELD_INTERLACED_BT; + break; + } + + xlate = soc_camera_xlate_by_fourcc(icd, pixfmt); + if (!xlate) { + dev_warn(dev, "Format %x not found\n", pixfmt); + return -EINVAL; + } + /* Calculate client output geometry */ + soc_camera_calc_client_output(icd, &cam->rect, &cam->subrect, pix, &mf, + 12); + mf.field = pix->field; + mf.colorspace = pix->colorspace; + mf.code = xlate->code; + + switch (pixfmt) { + case V4L2_PIX_FMT_RGB32: + can_scale = priv->chip != RCAR_E1; + break; + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_RGB555X: + can_scale = true; + break; + default: + can_scale = false; + break; + } + + dev_dbg(dev, "request camera output %ux%u\n", mf.width, mf.height); + + ret = soc_camera_client_scale(icd, &cam->rect, &cam->subrect, + &mf, &vin_sub_width, &vin_sub_height, + can_scale, 12); + + /* Done with the camera. Now see if we can improve the result */ + dev_dbg(dev, "Camera %d fmt %ux%u, requested %ux%u\n", + ret, mf.width, mf.height, pix->width, pix->height); + + if (ret == -ENOIOCTLCMD) + dev_dbg(dev, "Sensor doesn't support scaling\n"); + else if (ret < 0) + return ret; + + if (mf.code != xlate->code) + return -EINVAL; + + /* Prepare VIN crop */ + cam->width = mf.width; + cam->height = mf.height; + + /* Use VIN scaling to scale to the requested user window. */ + + /* We cannot scale up */ + if (pix->width > vin_sub_width) + vin_sub_width = pix->width; + + if (pix->height > vin_sub_height) + vin_sub_height = pix->height; + + pix->colorspace = mf.colorspace; + + if (!can_scale) { + pix->width = vin_sub_width; + pix->height = vin_sub_height; + } + + /* + * We have calculated CFLCR, the actual configuration will be performed + * in rcar_vin_set_bus_param() + */ + + dev_dbg(dev, "W: %u : %u, H: %u : %u\n", + vin_sub_width, pix->width, vin_sub_height, pix->height); + + icd->current_fmt = xlate; + + priv->field = field; + + return 0; +} + +static int rcar_vin_try_fmt(struct soc_camera_device *icd, + struct v4l2_format *f) +{ + const struct soc_camera_format_xlate *xlate; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct v4l2_mbus_framefmt mf; + __u32 pixfmt = pix->pixelformat; + int width, height; + int ret; + + xlate = soc_camera_xlate_by_fourcc(icd, pixfmt); + if (!xlate) { + xlate = icd->current_fmt; + dev_dbg(icd->parent, "Format %x not found, keeping %x\n", + pixfmt, xlate->host_fmt->fourcc); + pixfmt = xlate->host_fmt->fourcc; + pix->pixelformat = pixfmt; + pix->colorspace = icd->colorspace; + } + + /* FIXME: calculate using depth and bus width */ + v4l_bound_align_image(&pix->width, 2, VIN_MAX_WIDTH, 1, + &pix->height, 4, VIN_MAX_HEIGHT, 2, 0); + + width = pix->width; + height = pix->height; + + /* let soc-camera calculate these values */ + pix->bytesperline = 0; + pix->sizeimage = 0; + + /* limit to sensor capabilities */ + mf.width = pix->width; + mf.height = pix->height; + mf.field = pix->field; + mf.code = xlate->code; + mf.colorspace = pix->colorspace; + + ret = v4l2_device_call_until_err(sd->v4l2_dev, soc_camera_grp_id(icd), + video, try_mbus_fmt, &mf); + if (ret < 0) + return ret; + + pix->width = mf.width; + pix->height = mf.height; + pix->field = mf.field; + pix->colorspace = mf.colorspace; + + if (pixfmt == V4L2_PIX_FMT_NV16) { + /* FIXME: check against rect_max after converting soc-camera */ + /* We can scale precisely, need a bigger image from camera */ + if (pix->width < width || pix->height < height) { + /* + * We presume, the sensor behaves sanely, i.e. if + * requested a bigger rectangle, it will not return a + * smaller one. + */ + mf.width = VIN_MAX_WIDTH; + mf.height = VIN_MAX_HEIGHT; + ret = v4l2_device_call_until_err(sd->v4l2_dev, + soc_camera_grp_id(icd), + video, try_mbus_fmt, + &mf); + if (ret < 0) { + dev_err(icd->parent, + "client try_fmt() = %d\n", ret); + return ret; + } + } + /* We will scale exactly */ + if (mf.width > width) + pix->width = width; + if (mf.height > height) + pix->height = height; + } + + return ret; +} + +static unsigned int rcar_vin_poll(struct file *file, poll_table *pt) +{ + struct soc_camera_device *icd = file->private_data; + + return vb2_poll(&icd->vb2_vidq, file, pt); +} + +static int rcar_vin_querycap(struct soc_camera_host *ici, + struct v4l2_capability *cap) +{ + strlcpy(cap->card, "R_Car_VIN", sizeof(cap->card)); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + return 0; +} + +static int rcar_vin_init_videobuf2(struct vb2_queue *vq, + struct soc_camera_device *icd) +{ + vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vq->io_modes = VB2_MMAP | VB2_USERPTR; + vq->drv_priv = icd; + vq->ops = &rcar_vin_vb2_ops; + vq->mem_ops = &vb2_dma_contig_memops; + vq->buf_struct_size = sizeof(struct rcar_vin_buffer); + vq->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + + return vb2_queue_init(vq); +} + +static struct soc_camera_host_ops rcar_vin_host_ops = { + .owner = THIS_MODULE, + .add = rcar_vin_add_device, + .remove = rcar_vin_remove_device, + .clock_start = rcar_vin_clock_start, + .clock_stop = rcar_vin_clock_stop, + .get_formats = rcar_vin_get_formats, + .put_formats = rcar_vin_put_formats, + .get_crop = rcar_vin_get_crop, + .set_crop = rcar_vin_set_crop, + .try_fmt = rcar_vin_try_fmt, + .set_fmt = rcar_vin_set_fmt, + .poll = rcar_vin_poll, + .querycap = rcar_vin_querycap, + .set_bus_param = rcar_vin_set_bus_param, + .init_videobuf2 = rcar_vin_init_videobuf2, +}; + +static struct platform_device_id rcar_vin_id_table[] = { + { "r8a7779-vin", RCAR_H1 }, + { "r8a7778-vin", RCAR_M1 }, + { "uPD35004-vin", RCAR_E1 }, + {}, +}; +MODULE_DEVICE_TABLE(platform, rcar_vin_id_table); + +static int rcar_vin_probe(struct platform_device *pdev) +{ + struct rcar_vin_priv *priv; + struct resource *mem; + struct rcar_vin_platform_data *pdata; + int irq, ret; + + pdata = pdev->dev.platform_data; + if (!pdata || !pdata->flags) { + dev_err(&pdev->dev, "platform data not set\n"); + return -EINVAL; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (mem == NULL) + return -EINVAL; + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return -EINVAL; + + priv = devm_kzalloc(&pdev->dev, sizeof(struct rcar_vin_priv), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + ret = devm_request_irq(&pdev->dev, irq, rcar_vin_irq, IRQF_SHARED, + dev_name(&pdev->dev), priv); + if (ret) + return ret; + + priv->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); + if (IS_ERR(priv->alloc_ctx)) + return PTR_ERR(priv->alloc_ctx); + + priv->ici.priv = priv; + priv->ici.v4l2_dev.dev = &pdev->dev; + priv->ici.nr = pdev->id; + priv->ici.drv_name = dev_name(&pdev->dev); + priv->ici.ops = &rcar_vin_host_ops; + + priv->pdata = pdata; + priv->chip = pdev->id_entry->driver_data; + spin_lock_init(&priv->lock); + INIT_LIST_HEAD(&priv->capture); + + priv->state = STOPPED; + + pm_suspend_ignore_children(&pdev->dev, true); + pm_runtime_enable(&pdev->dev); + + ret = soc_camera_host_register(&priv->ici); + if (ret) + goto cleanup; + + return 0; + +cleanup: + pm_runtime_disable(&pdev->dev); + vb2_dma_contig_cleanup_ctx(priv->alloc_ctx); + + return ret; +} + +static int rcar_vin_remove(struct platform_device *pdev) +{ + struct soc_camera_host *soc_host = to_soc_camera_host(&pdev->dev); + struct rcar_vin_priv *priv = container_of(soc_host, + struct rcar_vin_priv, ici); + + soc_camera_host_unregister(soc_host); + pm_runtime_disable(&pdev->dev); + vb2_dma_contig_cleanup_ctx(priv->alloc_ctx); + + return 0; +} + +static struct platform_driver rcar_vin_driver = { + .probe = rcar_vin_probe, + .remove = rcar_vin_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .id_table = rcar_vin_id_table, +}; + +module_platform_driver(rcar_vin_driver); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rcar_vin"); +MODULE_DESCRIPTION("Renesas R-Car VIN camera host driver"); diff --git a/drivers/media/platform/soc_camera/sh_mobile_ceu_camera.c b/drivers/media/platform/soc_camera/sh_mobile_ceu_camera.c index f2de0066089a..8df22f779175 100644 --- a/drivers/media/platform/soc_camera/sh_mobile_ceu_camera.c +++ b/drivers/media/platform/soc_camera/sh_mobile_ceu_camera.c @@ -610,13 +610,12 @@ static void sh_mobile_ceu_remove_device(struct soc_camera_device *icd) static int sh_mobile_ceu_clock_start(struct soc_camera_host *ici) { struct sh_mobile_ceu_dev *pcdev = ici->priv; - int ret; pm_runtime_get_sync(ici->v4l2_dev.dev); pcdev->buf_total = 0; - ret = sh_mobile_ceu_soft_reset(pcdev); + sh_mobile_ceu_soft_reset(pcdev); return 0; } @@ -1837,9 +1836,9 @@ static int sh_mobile_ceu_probe(struct platform_device *pdev) for (j = 0; pcdev->pdata->asd_sizes[j]; j++) { for (i = 0; i < pcdev->pdata->asd_sizes[j]; i++, asd++) { dev_dbg(&pdev->dev, "%s(): subdev #%d, type %u\n", - __func__, i, (*asd)->bus_type); - if ((*asd)->bus_type == V4L2_ASYNC_BUS_PLATFORM && - !strncmp(name, (*asd)->match.platform.name, + __func__, i, (*asd)->match_type); + if ((*asd)->match_type == V4L2_ASYNC_MATCH_DEVNAME && + !strncmp(name, (*asd)->match.device_name.name, sizeof(name) - 1)) { pcdev->csi2_asd = *asd; break; diff --git a/drivers/media/platform/soc_camera/soc_camera.c b/drivers/media/platform/soc_camera/soc_camera.c index 2dd0e5272941..387a232d95a4 100644 --- a/drivers/media/platform/soc_camera/soc_camera.c +++ b/drivers/media/platform/soc_camera/soc_camera.c @@ -136,7 +136,7 @@ EXPORT_SYMBOL(soc_camera_power_off); int soc_camera_power_init(struct device *dev, struct soc_camera_subdev_desc *ssdd) { - + /* Should not have any effect in synchronous case */ return devm_regulator_bulk_get(dev, ssdd->num_regulators, ssdd->regulators); } @@ -1311,6 +1311,7 @@ eusrfmt: static int soc_camera_i2c_init(struct soc_camera_device *icd, struct soc_camera_desc *sdesc) { + struct soc_camera_subdev_desc *ssdd; struct i2c_client *client; struct soc_camera_host *ici; struct soc_camera_host_desc *shd = &sdesc->host_desc; @@ -1333,7 +1334,21 @@ static int soc_camera_i2c_init(struct soc_camera_device *icd, return -ENODEV; } - shd->board_info->platform_data = &sdesc->subdev_desc; + ssdd = kzalloc(sizeof(*ssdd), GFP_KERNEL); + if (!ssdd) { + ret = -ENOMEM; + goto ealloc; + } + + memcpy(ssdd, &sdesc->subdev_desc, sizeof(*ssdd)); + /* + * In synchronous case we request regulators ourselves in + * soc_camera_pdrv_probe(), make sure the subdevice driver doesn't try + * to allocate them again. + */ + ssdd->num_regulators = 0; + ssdd->regulators = NULL; + shd->board_info->platform_data = ssdd; snprintf(clk_name, sizeof(clk_name), "%d-%04x", shd->i2c_adapter_id, shd->board_info->addr); @@ -1359,8 +1374,10 @@ static int soc_camera_i2c_init(struct soc_camera_device *icd, return 0; ei2cnd: v4l2_clk_unregister(icd->clk); -eclkreg: icd->clk = NULL; +eclkreg: + kfree(ssdd); +ealloc: i2c_put_adapter(adap); return ret; } @@ -1370,15 +1387,18 @@ static void soc_camera_i2c_free(struct soc_camera_device *icd) struct i2c_client *client = to_i2c_client(to_soc_camera_control(icd)); struct i2c_adapter *adap; + struct soc_camera_subdev_desc *ssdd; icd->control = NULL; if (icd->sasc) return; adap = client->adapter; + ssdd = client->dev.platform_data; v4l2_device_unregister_subdev(i2c_get_clientdata(client)); i2c_unregister_device(client); i2c_put_adapter(adap); + kfree(ssdd); v4l2_clk_unregister(icd->clk); icd->clk = NULL; } @@ -1466,7 +1486,8 @@ static int scan_async_group(struct soc_camera_host *ici, struct soc_camera_device *icd; struct soc_camera_desc sdesc = {.host_desc.bus_id = ici->nr,}; char clk_name[V4L2_SUBDEV_NAME_SIZE]; - int ret, i; + unsigned int i; + int ret; /* First look for a sensor */ for (i = 0; i < size; i++) { @@ -1475,7 +1496,7 @@ static int scan_async_group(struct soc_camera_host *ici, break; } - if (i == size || asd[i]->bus_type != V4L2_ASYNC_BUS_I2C) { + if (i >= size || asd[i]->match_type != V4L2_ASYNC_MATCH_I2C) { /* All useless */ dev_err(ici->v4l2_dev.dev, "No I2C data source found!\n"); return -ENODEV; @@ -1501,7 +1522,7 @@ static int scan_async_group(struct soc_camera_host *ici, return -ENOMEM; } - sasc->notifier.subdev = asd; + sasc->notifier.subdevs = asd; sasc->notifier.num_subdevs = size; sasc->notifier.bound = soc_camera_async_bound; sasc->notifier.unbind = soc_camera_async_unbind; @@ -1994,9 +2015,10 @@ static int soc_camera_pdrv_probe(struct platform_device *pdev) /* * In the asynchronous case ssdd->num_regulators == 0 yet, so, the below - * regulator allocation is a dummy. They will be really requested later - * in soc_camera_async_bind(). Also note, that in that case regulators - * are attached to the I2C device and not to the camera platform device. + * regulator allocation is a dummy. They are actually requested by the + * subdevice driver, using soc_camera_power_init(). Also note, that in + * that case regulators are attached to the I2C device and not to the + * camera platform device. */ ret = devm_regulator_bulk_get(&pdev->dev, ssdd->num_regulators, ssdd->regulators); diff --git a/drivers/media/platform/vsp1/Makefile b/drivers/media/platform/vsp1/Makefile new file mode 100644 index 000000000000..4da226169e15 --- /dev/null +++ b/drivers/media/platform/vsp1/Makefile @@ -0,0 +1,5 @@ +vsp1-y := vsp1_drv.o vsp1_entity.o vsp1_video.o +vsp1-y += vsp1_rpf.o vsp1_rwpf.o vsp1_wpf.o +vsp1-y += vsp1_lif.o vsp1_uds.o + +obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1.o diff --git a/drivers/media/platform/vsp1/vsp1.h b/drivers/media/platform/vsp1/vsp1.h new file mode 100644 index 000000000000..d6c6ecd039ff --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1.h @@ -0,0 +1,74 @@ +/* + * vsp1.h -- R-Car VSP1 Driver + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#ifndef __VSP1_H__ +#define __VSP1_H__ + +#include <linux/io.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/platform_data/vsp1.h> + +#include <media/media-device.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> + +#include "vsp1_regs.h" + +struct clk; +struct device; + +struct vsp1_platform_data; +struct vsp1_lif; +struct vsp1_rwpf; +struct vsp1_uds; + +#define VPS1_MAX_RPF 5 +#define VPS1_MAX_UDS 3 +#define VPS1_MAX_WPF 4 + +struct vsp1_device { + struct device *dev; + struct vsp1_platform_data *pdata; + + void __iomem *mmio; + struct clk *clock; + struct clk *rt_clock; + + struct mutex lock; + int ref_count; + + struct vsp1_lif *lif; + struct vsp1_rwpf *rpf[VPS1_MAX_RPF]; + struct vsp1_uds *uds[VPS1_MAX_UDS]; + struct vsp1_rwpf *wpf[VPS1_MAX_WPF]; + + struct list_head entities; + + struct v4l2_device v4l2_dev; + struct media_device media_dev; +}; + +struct vsp1_device *vsp1_device_get(struct vsp1_device *vsp1); +void vsp1_device_put(struct vsp1_device *vsp1); + +static inline u32 vsp1_read(struct vsp1_device *vsp1, u32 reg) +{ + return ioread32(vsp1->mmio + reg); +} + +static inline void vsp1_write(struct vsp1_device *vsp1, u32 reg, u32 data) +{ + iowrite32(data, vsp1->mmio + reg); +} + +#endif /* __VSP1_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_drv.c b/drivers/media/platform/vsp1/vsp1_drv.c new file mode 100644 index 000000000000..1c9e771aa15c --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_drv.c @@ -0,0 +1,527 @@ +/* + * vsp1_drv.c -- R-Car VSP1 Driver + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/videodev2.h> + +#include "vsp1.h" +#include "vsp1_lif.h" +#include "vsp1_rwpf.h" +#include "vsp1_uds.h" + +/* ----------------------------------------------------------------------------- + * Interrupt Handling + */ + +static irqreturn_t vsp1_irq_handler(int irq, void *data) +{ + u32 mask = VI6_WFP_IRQ_STA_DFE | VI6_WFP_IRQ_STA_FRE; + struct vsp1_device *vsp1 = data; + irqreturn_t ret = IRQ_NONE; + unsigned int i; + + for (i = 0; i < vsp1->pdata->wpf_count; ++i) { + struct vsp1_rwpf *wpf = vsp1->wpf[i]; + struct vsp1_pipeline *pipe; + u32 status; + + if (wpf == NULL) + continue; + + pipe = to_vsp1_pipeline(&wpf->entity.subdev.entity); + status = vsp1_read(vsp1, VI6_WPF_IRQ_STA(i)); + vsp1_write(vsp1, VI6_WPF_IRQ_STA(i), ~status & mask); + + if (status & VI6_WFP_IRQ_STA_FRE) { + vsp1_pipeline_frame_end(pipe); + ret = IRQ_HANDLED; + } + } + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Entities + */ + +/* + * vsp1_create_links - Create links from all sources to the given sink + * + * This function creates media links from all valid sources to the given sink + * pad. Links that would be invalid according to the VSP1 hardware capabilities + * are skipped. Those include all links + * + * - from a UDS to a UDS (UDS entities can't be chained) + * - from an entity to itself (no loops are allowed) + */ +static int vsp1_create_links(struct vsp1_device *vsp1, struct vsp1_entity *sink) +{ + struct media_entity *entity = &sink->subdev.entity; + struct vsp1_entity *source; + unsigned int pad; + int ret; + + list_for_each_entry(source, &vsp1->entities, list_dev) { + u32 flags; + + if (source->type == sink->type) + continue; + + if (source->type == VSP1_ENTITY_LIF || + source->type == VSP1_ENTITY_WPF) + continue; + + flags = source->type == VSP1_ENTITY_RPF && + sink->type == VSP1_ENTITY_WPF && + source->index == sink->index + ? MEDIA_LNK_FL_ENABLED : 0; + + for (pad = 0; pad < entity->num_pads; ++pad) { + if (!(entity->pads[pad].flags & MEDIA_PAD_FL_SINK)) + continue; + + ret = media_entity_create_link(&source->subdev.entity, + source->source_pad, + entity, pad, flags); + if (ret < 0) + return ret; + + if (flags & MEDIA_LNK_FL_ENABLED) + source->sink = entity; + } + } + + return 0; +} + +static void vsp1_destroy_entities(struct vsp1_device *vsp1) +{ + struct vsp1_entity *entity; + struct vsp1_entity *next; + + list_for_each_entry_safe(entity, next, &vsp1->entities, list_dev) { + list_del(&entity->list_dev); + vsp1_entity_destroy(entity); + } + + v4l2_device_unregister(&vsp1->v4l2_dev); + media_device_unregister(&vsp1->media_dev); +} + +static int vsp1_create_entities(struct vsp1_device *vsp1) +{ + struct media_device *mdev = &vsp1->media_dev; + struct v4l2_device *vdev = &vsp1->v4l2_dev; + struct vsp1_entity *entity; + unsigned int i; + int ret; + + mdev->dev = vsp1->dev; + strlcpy(mdev->model, "VSP1", sizeof(mdev->model)); + snprintf(mdev->bus_info, sizeof(mdev->bus_info), "platform:%s", + dev_name(mdev->dev)); + ret = media_device_register(mdev); + if (ret < 0) { + dev_err(vsp1->dev, "media device registration failed (%d)\n", + ret); + return ret; + } + + vdev->mdev = mdev; + ret = v4l2_device_register(vsp1->dev, vdev); + if (ret < 0) { + dev_err(vsp1->dev, "V4L2 device registration failed (%d)\n", + ret); + goto done; + } + + /* Instantiate all the entities. */ + if (vsp1->pdata->features & VSP1_HAS_LIF) { + vsp1->lif = vsp1_lif_create(vsp1); + if (IS_ERR(vsp1->lif)) { + ret = PTR_ERR(vsp1->lif); + goto done; + } + + list_add_tail(&vsp1->lif->entity.list_dev, &vsp1->entities); + } + + for (i = 0; i < vsp1->pdata->rpf_count; ++i) { + struct vsp1_rwpf *rpf; + + rpf = vsp1_rpf_create(vsp1, i); + if (IS_ERR(rpf)) { + ret = PTR_ERR(rpf); + goto done; + } + + vsp1->rpf[i] = rpf; + list_add_tail(&rpf->entity.list_dev, &vsp1->entities); + } + + for (i = 0; i < vsp1->pdata->uds_count; ++i) { + struct vsp1_uds *uds; + + uds = vsp1_uds_create(vsp1, i); + if (IS_ERR(uds)) { + ret = PTR_ERR(uds); + goto done; + } + + vsp1->uds[i] = uds; + list_add_tail(&uds->entity.list_dev, &vsp1->entities); + } + + for (i = 0; i < vsp1->pdata->wpf_count; ++i) { + struct vsp1_rwpf *wpf; + + wpf = vsp1_wpf_create(vsp1, i); + if (IS_ERR(wpf)) { + ret = PTR_ERR(wpf); + goto done; + } + + vsp1->wpf[i] = wpf; + list_add_tail(&wpf->entity.list_dev, &vsp1->entities); + } + + /* Create links. */ + list_for_each_entry(entity, &vsp1->entities, list_dev) { + if (entity->type == VSP1_ENTITY_LIF || + entity->type == VSP1_ENTITY_RPF) + continue; + + ret = vsp1_create_links(vsp1, entity); + if (ret < 0) + goto done; + } + + if (vsp1->pdata->features & VSP1_HAS_LIF) { + ret = media_entity_create_link( + &vsp1->wpf[0]->entity.subdev.entity, RWPF_PAD_SOURCE, + &vsp1->lif->entity.subdev.entity, LIF_PAD_SINK, 0); + if (ret < 0) + return ret; + } + + /* Register all subdevs. */ + list_for_each_entry(entity, &vsp1->entities, list_dev) { + ret = v4l2_device_register_subdev(&vsp1->v4l2_dev, + &entity->subdev); + if (ret < 0) + goto done; + } + + ret = v4l2_device_register_subdev_nodes(&vsp1->v4l2_dev); + +done: + if (ret < 0) + vsp1_destroy_entities(vsp1); + + return ret; +} + +static int vsp1_device_init(struct vsp1_device *vsp1) +{ + unsigned int i; + u32 status; + + /* Reset any channel that might be running. */ + status = vsp1_read(vsp1, VI6_STATUS); + + for (i = 0; i < vsp1->pdata->wpf_count; ++i) { + unsigned int timeout; + + if (!(status & VI6_STATUS_SYS_ACT(i))) + continue; + + vsp1_write(vsp1, VI6_SRESET, VI6_SRESET_SRTS(i)); + for (timeout = 10; timeout > 0; --timeout) { + status = vsp1_read(vsp1, VI6_STATUS); + if (!(status & VI6_STATUS_SYS_ACT(i))) + break; + + usleep_range(1000, 2000); + } + + if (!timeout) { + dev_err(vsp1->dev, "failed to reset wpf.%u\n", i); + return -ETIMEDOUT; + } + } + + vsp1_write(vsp1, VI6_CLK_DCSWT, (8 << VI6_CLK_DCSWT_CSTPW_SHIFT) | + (8 << VI6_CLK_DCSWT_CSTRW_SHIFT)); + + for (i = 0; i < vsp1->pdata->rpf_count; ++i) + vsp1_write(vsp1, VI6_DPR_RPF_ROUTE(i), VI6_DPR_NODE_UNUSED); + + for (i = 0; i < vsp1->pdata->uds_count; ++i) + vsp1_write(vsp1, VI6_DPR_UDS_ROUTE(i), VI6_DPR_NODE_UNUSED); + + vsp1_write(vsp1, VI6_DPR_SRU_ROUTE, VI6_DPR_NODE_UNUSED); + vsp1_write(vsp1, VI6_DPR_LUT_ROUTE, VI6_DPR_NODE_UNUSED); + vsp1_write(vsp1, VI6_DPR_CLU_ROUTE, VI6_DPR_NODE_UNUSED); + vsp1_write(vsp1, VI6_DPR_HST_ROUTE, VI6_DPR_NODE_UNUSED); + vsp1_write(vsp1, VI6_DPR_HSI_ROUTE, VI6_DPR_NODE_UNUSED); + vsp1_write(vsp1, VI6_DPR_BRU_ROUTE, VI6_DPR_NODE_UNUSED); + + vsp1_write(vsp1, VI6_DPR_HGO_SMPPT, (7 << VI6_DPR_SMPPT_TGW_SHIFT) | + (VI6_DPR_NODE_UNUSED << VI6_DPR_SMPPT_PT_SHIFT)); + vsp1_write(vsp1, VI6_DPR_HGT_SMPPT, (7 << VI6_DPR_SMPPT_TGW_SHIFT) | + (VI6_DPR_NODE_UNUSED << VI6_DPR_SMPPT_PT_SHIFT)); + + return 0; +} + +static int vsp1_clocks_enable(struct vsp1_device *vsp1) +{ + int ret; + + ret = clk_prepare_enable(vsp1->clock); + if (ret < 0) + return ret; + + if (IS_ERR(vsp1->rt_clock)) + return 0; + + ret = clk_prepare_enable(vsp1->rt_clock); + if (ret < 0) { + clk_disable_unprepare(vsp1->clock); + return ret; + } + + return 0; +} + +static void vsp1_clocks_disable(struct vsp1_device *vsp1) +{ + if (!IS_ERR(vsp1->rt_clock)) + clk_disable_unprepare(vsp1->rt_clock); + clk_disable_unprepare(vsp1->clock); +} + +/* + * vsp1_device_get - Acquire the VSP1 device + * + * Increment the VSP1 reference count and initialize the device if the first + * reference is taken. + * + * Return a pointer to the VSP1 device or NULL if an error occured. + */ +struct vsp1_device *vsp1_device_get(struct vsp1_device *vsp1) +{ + struct vsp1_device *__vsp1 = vsp1; + int ret; + + mutex_lock(&vsp1->lock); + if (vsp1->ref_count > 0) + goto done; + + ret = vsp1_clocks_enable(vsp1); + if (ret < 0) { + __vsp1 = NULL; + goto done; + } + + ret = vsp1_device_init(vsp1); + if (ret < 0) { + vsp1_clocks_disable(vsp1); + __vsp1 = NULL; + goto done; + } + +done: + if (__vsp1) + vsp1->ref_count++; + + mutex_unlock(&vsp1->lock); + return __vsp1; +} + +/* + * vsp1_device_put - Release the VSP1 device + * + * Decrement the VSP1 reference count and cleanup the device if the last + * reference is released. + */ +void vsp1_device_put(struct vsp1_device *vsp1) +{ + mutex_lock(&vsp1->lock); + + if (--vsp1->ref_count == 0) + vsp1_clocks_disable(vsp1); + + mutex_unlock(&vsp1->lock); +} + +/* ----------------------------------------------------------------------------- + * Power Management + */ + +#ifdef CONFIG_PM_SLEEP +static int vsp1_pm_suspend(struct device *dev) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + + WARN_ON(mutex_is_locked(&vsp1->lock)); + + if (vsp1->ref_count == 0) + return 0; + + vsp1_clocks_disable(vsp1); + return 0; +} + +static int vsp1_pm_resume(struct device *dev) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + + WARN_ON(mutex_is_locked(&vsp1->lock)); + + if (vsp1->ref_count) + return 0; + + return vsp1_clocks_enable(vsp1); +} +#endif + +static const struct dev_pm_ops vsp1_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(vsp1_pm_suspend, vsp1_pm_resume) +}; + +/* ----------------------------------------------------------------------------- + * Platform Driver + */ + +static struct vsp1_platform_data * +vsp1_get_platform_data(struct platform_device *pdev) +{ + struct vsp1_platform_data *pdata = pdev->dev.platform_data; + + if (pdata == NULL) { + dev_err(&pdev->dev, "missing platform data\n"); + return NULL; + } + + if (pdata->rpf_count <= 0 || pdata->rpf_count > VPS1_MAX_RPF) { + dev_err(&pdev->dev, "invalid number of RPF (%u)\n", + pdata->rpf_count); + return NULL; + } + + if (pdata->uds_count <= 0 || pdata->uds_count > VPS1_MAX_UDS) { + dev_err(&pdev->dev, "invalid number of UDS (%u)\n", + pdata->uds_count); + return NULL; + } + + if (pdata->wpf_count <= 0 || pdata->wpf_count > VPS1_MAX_WPF) { + dev_err(&pdev->dev, "invalid number of WPF (%u)\n", + pdata->wpf_count); + return NULL; + } + + return pdata; +} + +static int vsp1_probe(struct platform_device *pdev) +{ + struct vsp1_device *vsp1; + struct resource *irq; + struct resource *io; + int ret; + + vsp1 = devm_kzalloc(&pdev->dev, sizeof(*vsp1), GFP_KERNEL); + if (vsp1 == NULL) + return -ENOMEM; + + vsp1->dev = &pdev->dev; + mutex_init(&vsp1->lock); + INIT_LIST_HEAD(&vsp1->entities); + + vsp1->pdata = vsp1_get_platform_data(pdev); + if (vsp1->pdata == NULL) + return -ENODEV; + + /* I/O, IRQ and clock resources */ + io = platform_get_resource(pdev, IORESOURCE_MEM, 0); + vsp1->mmio = devm_ioremap_resource(&pdev->dev, io); + if (IS_ERR(vsp1->mmio)) + return PTR_ERR(vsp1->mmio); + + vsp1->clock = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(vsp1->clock)) { + dev_err(&pdev->dev, "failed to get clock\n"); + return PTR_ERR(vsp1->clock); + } + + /* The RT clock is optional */ + vsp1->rt_clock = devm_clk_get(&pdev->dev, "rt"); + + irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!irq) { + dev_err(&pdev->dev, "missing IRQ\n"); + return -EINVAL; + } + + ret = devm_request_irq(&pdev->dev, irq->start, vsp1_irq_handler, + IRQF_SHARED, dev_name(&pdev->dev), vsp1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to request IRQ\n"); + return ret; + } + + /* Instanciate entities */ + ret = vsp1_create_entities(vsp1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to create entities\n"); + return ret; + } + + platform_set_drvdata(pdev, vsp1); + + return 0; +} + +static int vsp1_remove(struct platform_device *pdev) +{ + struct vsp1_device *vsp1 = platform_get_drvdata(pdev); + + vsp1_destroy_entities(vsp1); + + return 0; +} + +static struct platform_driver vsp1_platform_driver = { + .probe = vsp1_probe, + .remove = vsp1_remove, + .driver = { + .owner = THIS_MODULE, + .name = "vsp1", + .pm = &vsp1_pm_ops, + }, +}; + +module_platform_driver(vsp1_platform_driver); + +MODULE_ALIAS("vsp1"); +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); +MODULE_DESCRIPTION("Renesas VSP1 Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/vsp1/vsp1_entity.c b/drivers/media/platform/vsp1/vsp1_entity.c new file mode 100644 index 000000000000..9028f9d524f4 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_entity.c @@ -0,0 +1,181 @@ +/* + * vsp1_entity.c -- R-Car VSP1 Base Entity + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/device.h> +#include <linux/gfp.h> + +#include <media/media-entity.h> +#include <media/v4l2-subdev.h> + +#include "vsp1.h" +#include "vsp1_entity.h" + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +struct v4l2_mbus_framefmt * +vsp1_entity_get_pad_format(struct vsp1_entity *entity, + struct v4l2_subdev_fh *fh, + unsigned int pad, u32 which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(fh, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &entity->formats[pad]; + default: + return NULL; + } +} + +/* + * vsp1_entity_init_formats - Initialize formats on all pads + * @subdev: V4L2 subdevice + * @fh: V4L2 subdev file handle + * + * Initialize all pad formats with default values. If fh is not NULL, try + * formats are initialized on the file handle. Otherwise active formats are + * initialized on the device. + */ +void vsp1_entity_init_formats(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format; + unsigned int pad; + + for (pad = 0; pad < subdev->entity.num_pads - 1; ++pad) { + memset(&format, 0, sizeof(format)); + + format.pad = pad; + format.which = fh ? V4L2_SUBDEV_FORMAT_TRY + : V4L2_SUBDEV_FORMAT_ACTIVE; + + v4l2_subdev_call(subdev, pad, set_fmt, fh, &format); + } +} + +static int vsp1_entity_open(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh) +{ + vsp1_entity_init_formats(subdev, fh); + + return 0; +} + +const struct v4l2_subdev_internal_ops vsp1_subdev_internal_ops = { + .open = vsp1_entity_open, +}; + +/* ----------------------------------------------------------------------------- + * Media Operations + */ + +static int vsp1_entity_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct vsp1_entity *source; + + if (!(local->flags & MEDIA_PAD_FL_SOURCE)) + return 0; + + source = container_of(local->entity, struct vsp1_entity, subdev.entity); + + if (!source->route) + return 0; + + if (flags & MEDIA_LNK_FL_ENABLED) { + if (source->sink) + return -EBUSY; + source->sink = remote->entity; + } else { + source->sink = NULL; + } + + return 0; +} + +const struct media_entity_operations vsp1_media_ops = { + .link_setup = vsp1_entity_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +/* ----------------------------------------------------------------------------- + * Initialization + */ + +int vsp1_entity_init(struct vsp1_device *vsp1, struct vsp1_entity *entity, + unsigned int num_pads) +{ + static const struct { + unsigned int id; + unsigned int reg; + } routes[] = { + { VI6_DPR_NODE_LIF, 0 }, + { VI6_DPR_NODE_RPF(0), VI6_DPR_RPF_ROUTE(0) }, + { VI6_DPR_NODE_RPF(1), VI6_DPR_RPF_ROUTE(1) }, + { VI6_DPR_NODE_RPF(2), VI6_DPR_RPF_ROUTE(2) }, + { VI6_DPR_NODE_RPF(3), VI6_DPR_RPF_ROUTE(3) }, + { VI6_DPR_NODE_RPF(4), VI6_DPR_RPF_ROUTE(4) }, + { VI6_DPR_NODE_UDS(0), VI6_DPR_UDS_ROUTE(0) }, + { VI6_DPR_NODE_UDS(1), VI6_DPR_UDS_ROUTE(1) }, + { VI6_DPR_NODE_UDS(2), VI6_DPR_UDS_ROUTE(2) }, + { VI6_DPR_NODE_WPF(0), 0 }, + { VI6_DPR_NODE_WPF(1), 0 }, + { VI6_DPR_NODE_WPF(2), 0 }, + { VI6_DPR_NODE_WPF(3), 0 }, + }; + + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(routes); ++i) { + if (routes[i].id == entity->id) { + entity->route = routes[i].reg; + break; + } + } + + if (i == ARRAY_SIZE(routes)) + return -EINVAL; + + entity->vsp1 = vsp1; + entity->source_pad = num_pads - 1; + + /* Allocate formats and pads. */ + entity->formats = devm_kzalloc(vsp1->dev, + num_pads * sizeof(*entity->formats), + GFP_KERNEL); + if (entity->formats == NULL) + return -ENOMEM; + + entity->pads = devm_kzalloc(vsp1->dev, num_pads * sizeof(*entity->pads), + GFP_KERNEL); + if (entity->pads == NULL) + return -ENOMEM; + + /* Initialize pads. */ + for (i = 0; i < num_pads - 1; ++i) + entity->pads[i].flags = MEDIA_PAD_FL_SINK; + + entity->pads[num_pads - 1].flags = MEDIA_PAD_FL_SOURCE; + + /* Initialize the media entity. */ + return media_entity_init(&entity->subdev.entity, num_pads, + entity->pads, 0); +} + +void vsp1_entity_destroy(struct vsp1_entity *entity) +{ + media_entity_cleanup(&entity->subdev.entity); +} diff --git a/drivers/media/platform/vsp1/vsp1_entity.h b/drivers/media/platform/vsp1/vsp1_entity.h new file mode 100644 index 000000000000..c4feab2cbb81 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_entity.h @@ -0,0 +1,68 @@ +/* + * vsp1_entity.h -- R-Car VSP1 Base Entity + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#ifndef __VSP1_ENTITY_H__ +#define __VSP1_ENTITY_H__ + +#include <linux/list.h> + +#include <media/v4l2-subdev.h> + +struct vsp1_device; + +enum vsp1_entity_type { + VSP1_ENTITY_LIF, + VSP1_ENTITY_RPF, + VSP1_ENTITY_UDS, + VSP1_ENTITY_WPF, +}; + +struct vsp1_entity { + struct vsp1_device *vsp1; + + enum vsp1_entity_type type; + unsigned int index; + unsigned int id; + unsigned int route; + + struct list_head list_dev; + struct list_head list_pipe; + + struct media_pad *pads; + unsigned int source_pad; + + struct media_entity *sink; + + struct v4l2_subdev subdev; + struct v4l2_mbus_framefmt *formats; +}; + +static inline struct vsp1_entity *to_vsp1_entity(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_entity, subdev); +} + +int vsp1_entity_init(struct vsp1_device *vsp1, struct vsp1_entity *entity, + unsigned int num_pads); +void vsp1_entity_destroy(struct vsp1_entity *entity); + +extern const struct v4l2_subdev_internal_ops vsp1_subdev_internal_ops; +extern const struct media_entity_operations vsp1_media_ops; + +struct v4l2_mbus_framefmt * +vsp1_entity_get_pad_format(struct vsp1_entity *entity, + struct v4l2_subdev_fh *fh, + unsigned int pad, u32 which); +void vsp1_entity_init_formats(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh); + +#endif /* __VSP1_ENTITY_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_lif.c b/drivers/media/platform/vsp1/vsp1_lif.c new file mode 100644 index 000000000000..74a32e69ef10 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_lif.c @@ -0,0 +1,238 @@ +/* + * vsp1_lif.c -- R-Car VSP1 LCD Controller Interface + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/device.h> +#include <linux/gfp.h> + +#include <media/v4l2-subdev.h> + +#include "vsp1.h" +#include "vsp1_lif.h" + +#define LIF_MIN_SIZE 2U +#define LIF_MAX_SIZE 2048U + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline u32 vsp1_lif_read(struct vsp1_lif *lif, u32 reg) +{ + return vsp1_read(lif->entity.vsp1, reg); +} + +static inline void vsp1_lif_write(struct vsp1_lif *lif, u32 reg, u32 data) +{ + vsp1_write(lif->entity.vsp1, reg, data); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Core Operations + */ + +static int lif_s_stream(struct v4l2_subdev *subdev, int enable) +{ + const struct v4l2_mbus_framefmt *format; + struct vsp1_lif *lif = to_lif(subdev); + unsigned int hbth = 1300; + unsigned int obth = 400; + unsigned int lbth = 200; + + if (!enable) { + vsp1_lif_write(lif, VI6_LIF_CTRL, 0); + return 0; + } + + format = &lif->entity.formats[LIF_PAD_SOURCE]; + + obth = min(obth, (format->width + 1) / 2 * format->height - 4); + + vsp1_lif_write(lif, VI6_LIF_CSBTH, + (hbth << VI6_LIF_CSBTH_HBTH_SHIFT) | + (lbth << VI6_LIF_CSBTH_LBTH_SHIFT)); + + vsp1_lif_write(lif, VI6_LIF_CTRL, + (obth << VI6_LIF_CTRL_OBTH_SHIFT) | + (format->code == 0 ? VI6_LIF_CTRL_CFMT : 0) | + VI6_LIF_CTRL_REQSEL | VI6_LIF_CTRL_LIF_EN); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Pad Operations + */ + +static int lif_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + static const unsigned int codes[] = { + V4L2_MBUS_FMT_ARGB8888_1X32, + V4L2_MBUS_FMT_AYUV8_1X32, + }; + + if (code->pad == LIF_PAD_SINK) { + if (code->index >= ARRAY_SIZE(codes)) + return -EINVAL; + + code->code = codes[code->index]; + } else { + struct v4l2_mbus_framefmt *format; + + /* The LIF can't perform format conversion, the sink format is + * always identical to the source format. + */ + if (code->index) + return -EINVAL; + + format = v4l2_subdev_get_try_format(fh, LIF_PAD_SINK); + code->code = format->code; + } + + return 0; +} + +static int lif_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct v4l2_mbus_framefmt *format; + + format = v4l2_subdev_get_try_format(fh, LIF_PAD_SINK); + + if (fse->index || fse->code != format->code) + return -EINVAL; + + if (fse->pad == LIF_PAD_SINK) { + fse->min_width = LIF_MIN_SIZE; + fse->max_width = LIF_MAX_SIZE; + fse->min_height = LIF_MIN_SIZE; + fse->max_height = LIF_MAX_SIZE; + } else { + fse->min_width = format->width; + fse->max_width = format->width; + fse->min_height = format->height; + fse->max_height = format->height; + } + + return 0; +} + +static int lif_get_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_lif *lif = to_lif(subdev); + + fmt->format = *vsp1_entity_get_pad_format(&lif->entity, fh, fmt->pad, + fmt->which); + + return 0; +} + +static int lif_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_lif *lif = to_lif(subdev); + struct v4l2_mbus_framefmt *format; + + /* Default to YUV if the requested format is not supported. */ + if (fmt->format.code != V4L2_MBUS_FMT_ARGB8888_1X32 && + fmt->format.code != V4L2_MBUS_FMT_AYUV8_1X32) + fmt->format.code = V4L2_MBUS_FMT_AYUV8_1X32; + + format = vsp1_entity_get_pad_format(&lif->entity, fh, fmt->pad, + fmt->which); + + if (fmt->pad == LIF_PAD_SOURCE) { + /* The LIF source format is always identical to its sink + * format. + */ + fmt->format = *format; + return 0; + } + + format->code = fmt->format.code; + format->width = clamp_t(unsigned int, fmt->format.width, + LIF_MIN_SIZE, LIF_MAX_SIZE); + format->height = clamp_t(unsigned int, fmt->format.height, + LIF_MIN_SIZE, LIF_MAX_SIZE); + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + + fmt->format = *format; + + /* Propagate the format to the source pad. */ + format = vsp1_entity_get_pad_format(&lif->entity, fh, LIF_PAD_SOURCE, + fmt->which); + *format = fmt->format; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static struct v4l2_subdev_video_ops lif_video_ops = { + .s_stream = lif_s_stream, +}; + +static struct v4l2_subdev_pad_ops lif_pad_ops = { + .enum_mbus_code = lif_enum_mbus_code, + .enum_frame_size = lif_enum_frame_size, + .get_fmt = lif_get_format, + .set_fmt = lif_set_format, +}; + +static struct v4l2_subdev_ops lif_ops = { + .video = &lif_video_ops, + .pad = &lif_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_lif *vsp1_lif_create(struct vsp1_device *vsp1) +{ + struct v4l2_subdev *subdev; + struct vsp1_lif *lif; + int ret; + + lif = devm_kzalloc(vsp1->dev, sizeof(*lif), GFP_KERNEL); + if (lif == NULL) + return ERR_PTR(-ENOMEM); + + lif->entity.type = VSP1_ENTITY_LIF; + lif->entity.id = VI6_DPR_NODE_LIF; + + ret = vsp1_entity_init(vsp1, &lif->entity, 2); + if (ret < 0) + return ERR_PTR(ret); + + /* Initialize the V4L2 subdev. */ + subdev = &lif->entity.subdev; + v4l2_subdev_init(subdev, &lif_ops); + + subdev->entity.ops = &vsp1_media_ops; + subdev->internal_ops = &vsp1_subdev_internal_ops; + snprintf(subdev->name, sizeof(subdev->name), "%s lif", + dev_name(vsp1->dev)); + v4l2_set_subdevdata(subdev, lif); + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + vsp1_entity_init_formats(subdev, NULL); + + return lif; +} diff --git a/drivers/media/platform/vsp1/vsp1_lif.h b/drivers/media/platform/vsp1/vsp1_lif.h new file mode 100644 index 000000000000..89b93af56fdc --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_lif.h @@ -0,0 +1,37 @@ +/* + * vsp1_lif.h -- R-Car VSP1 LCD Controller Interface + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#ifndef __VSP1_LIF_H__ +#define __VSP1_LIF_H__ + +#include <media/media-entity.h> +#include <media/v4l2-subdev.h> + +#include "vsp1_entity.h" + +struct vsp1_device; + +#define LIF_PAD_SINK 0 +#define LIF_PAD_SOURCE 1 + +struct vsp1_lif { + struct vsp1_entity entity; +}; + +static inline struct vsp1_lif *to_lif(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_lif, entity.subdev); +} + +struct vsp1_lif *vsp1_lif_create(struct vsp1_device *vsp1); + +#endif /* __VSP1_LIF_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_regs.h b/drivers/media/platform/vsp1/vsp1_regs.h new file mode 100644 index 000000000000..1d3304f1365b --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_regs.h @@ -0,0 +1,581 @@ +/* + * vsp1_regs.h -- R-Car VSP1 Registers Definitions + * + * Copyright (C) 2013 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + */ + +#ifndef __VSP1_REGS_H__ +#define __VSP1_REGS_H__ + +/* ----------------------------------------------------------------------------- + * General Control Registers + */ + +#define VI6_CMD(n) (0x0000 + (n) * 4) +#define VI6_CMD_STRCMD (1 << 0) + +#define VI6_CLK_DCSWT 0x0018 +#define VI6_CLK_DCSWT_CSTPW_MASK (0xff << 8) +#define VI6_CLK_DCSWT_CSTPW_SHIFT 8 +#define VI6_CLK_DCSWT_CSTRW_MASK (0xff << 0) +#define VI6_CLK_DCSWT_CSTRW_SHIFT 0 + +#define VI6_SRESET 0x0028 +#define VI6_SRESET_SRTS(n) (1 << (n)) + +#define VI6_STATUS 0x0038 +#define VI6_STATUS_SYS_ACT(n) (1 << ((n) + 8)) + +#define VI6_WPF_IRQ_ENB(n) (0x0048 + (n) * 12) +#define VI6_WFP_IRQ_ENB_DFEE (1 << 1) +#define VI6_WFP_IRQ_ENB_FREE (1 << 0) + +#define VI6_WPF_IRQ_STA(n) (0x004c + (n) * 12) +#define VI6_WFP_IRQ_STA_DFE (1 << 1) +#define VI6_WFP_IRQ_STA_FRE (1 << 0) + +#define VI6_DISP_IRQ_ENB 0x0078 +#define VI6_DISP_IRQ_ENB_DSTE (1 << 8) +#define VI6_DISP_IRQ_ENB_MAEE (1 << 5) +#define VI6_DISP_IRQ_ENB_LNEE(n) (1 << ((n) + 4)) + +#define VI6_DISP_IRQ_STA 0x007c +#define VI6_DISP_IRQ_STA_DSE (1 << 8) +#define VI6_DISP_IRQ_STA_MAE (1 << 5) +#define VI6_DISP_IRQ_STA_LNE(n) (1 << ((n) + 4)) + +#define VI6_WPF_LINE_COUNT(n) (0x0084 + (n) * 4) +#define VI6_WPF_LINE_COUNT_MASK (0x1fffff << 0) + +/* ----------------------------------------------------------------------------- + * Display List Control Registers + */ + +#define VI6_DL_CTRL 0x0100 +#define VI6_DL_CTRL_AR_WAIT_MASK (0xffff << 16) +#define VI6_DL_CTRL_AR_WAIT_SHIFT 16 +#define VI6_DL_CTRL_DC2 (1 << 12) +#define VI6_DL_CTRL_DC1 (1 << 8) +#define VI6_DL_CTRL_DC0 (1 << 4) +#define VI6_DL_CTRL_CFM0 (1 << 2) +#define VI6_DL_CTRL_NH0 (1 << 1) +#define VI6_DL_CTRL_DLE (1 << 0) + +#define VI6_DL_HDR_ADDR(n) (0x0104 + (n) * 4) + +#define VI6_DL_SWAP 0x0114 +#define VI6_DL_SWAP_LWS (1 << 2) +#define VI6_DL_SWAP_WDS (1 << 1) +#define VI6_DL_SWAP_BTS (1 << 0) + +#define VI6_DL_EXT_CTRL 0x011c +#define VI6_DL_EXT_CTRL_NWE (1 << 16) +#define VI6_DL_EXT_CTRL_POLINT_MASK (0x3f << 8) +#define VI6_DL_EXT_CTRL_POLINT_SHIFT 8 +#define VI6_DL_EXT_CTRL_DLPRI (1 << 5) +#define VI6_DL_EXT_CTRL_EXPRI (1 << 4) +#define VI6_DL_EXT_CTRL_EXT (1 << 0) + +#define VI6_DL_BODY_SIZE 0x0120 +#define VI6_DL_BODY_SIZE_UPD (1 << 24) +#define VI6_DL_BODY_SIZE_BS_MASK (0x1ffff << 0) +#define VI6_DL_BODY_SIZE_BS_SHIFT 0 + +/* ----------------------------------------------------------------------------- + * RPF Control Registers + */ + +#define VI6_RPF_OFFSET 0x100 + +#define VI6_RPF_SRC_BSIZE 0x0300 +#define VI6_RPF_SRC_BSIZE_BHSIZE_MASK (0x1fff << 16) +#define VI6_RPF_SRC_BSIZE_BHSIZE_SHIFT 16 +#define VI6_RPF_SRC_BSIZE_BVSIZE_MASK (0x1fff << 0) +#define VI6_RPF_SRC_BSIZE_BVSIZE_SHIFT 0 + +#define VI6_RPF_SRC_ESIZE 0x0304 +#define VI6_RPF_SRC_ESIZE_EHSIZE_MASK (0x1fff << 16) +#define VI6_RPF_SRC_ESIZE_EHSIZE_SHIFT 16 +#define VI6_RPF_SRC_ESIZE_EVSIZE_MASK (0x1fff << 0) +#define VI6_RPF_SRC_ESIZE_EVSIZE_SHIFT 0 + +#define VI6_RPF_INFMT 0x0308 +#define VI6_RPF_INFMT_VIR (1 << 28) +#define VI6_RPF_INFMT_CIPM (1 << 16) +#define VI6_RPF_INFMT_SPYCS (1 << 15) +#define VI6_RPF_INFMT_SPUVS (1 << 14) +#define VI6_RPF_INFMT_CEXT_ZERO (0 << 12) +#define VI6_RPF_INFMT_CEXT_EXT (1 << 12) +#define VI6_RPF_INFMT_CEXT_ONE (2 << 12) +#define VI6_RPF_INFMT_CEXT_MASK (3 << 12) +#define VI6_RPF_INFMT_RDTM_BT601 (0 << 9) +#define VI6_RPF_INFMT_RDTM_BT601_EXT (1 << 9) +#define VI6_RPF_INFMT_RDTM_BT709 (2 << 9) +#define VI6_RPF_INFMT_RDTM_BT709_EXT (3 << 9) +#define VI6_RPF_INFMT_RDTM_MASK (7 << 9) +#define VI6_RPF_INFMT_CSC (1 << 8) +#define VI6_RPF_INFMT_RDFMT_MASK (0x7f << 0) +#define VI6_RPF_INFMT_RDFMT_SHIFT 0 + +#define VI6_RPF_DSWAP 0x030c +#define VI6_RPF_DSWAP_A_LLS (1 << 11) +#define VI6_RPF_DSWAP_A_LWS (1 << 10) +#define VI6_RPF_DSWAP_A_WDS (1 << 9) +#define VI6_RPF_DSWAP_A_BTS (1 << 8) +#define VI6_RPF_DSWAP_P_LLS (1 << 3) +#define VI6_RPF_DSWAP_P_LWS (1 << 2) +#define VI6_RPF_DSWAP_P_WDS (1 << 1) +#define VI6_RPF_DSWAP_P_BTS (1 << 0) + +#define VI6_RPF_LOC 0x0310 +#define VI6_RPF_LOC_HCOORD_MASK (0x1fff << 16) +#define VI6_RPF_LOC_HCOORD_SHIFT 16 +#define VI6_RPF_LOC_VCOORD_MASK (0x1fff << 0) +#define VI6_RPF_LOC_VCOORD_SHIFT 0 + +#define VI6_RPF_ALPH_SEL 0x0314 +#define VI6_RPF_ALPH_SEL_ASEL_PACKED (0 << 28) +#define VI6_RPF_ALPH_SEL_ASEL_8B_PLANE (1 << 28) +#define VI6_RPF_ALPH_SEL_ASEL_SELECT (2 << 28) +#define VI6_RPF_ALPH_SEL_ASEL_1B_PLANE (3 << 28) +#define VI6_RPF_ALPH_SEL_ASEL_FIXED (4 << 28) +#define VI6_RPF_ALPH_SEL_ASEL_MASK (7 << 28) +#define VI6_RPF_ALPH_SEL_ASEL_SHIFT 28 +#define VI6_RPF_ALPH_SEL_IROP_MASK (0xf << 24) +#define VI6_RPF_ALPH_SEL_IROP_SHIFT 24 +#define VI6_RPF_ALPH_SEL_BSEL (1 << 23) +#define VI6_RPF_ALPH_SEL_AEXT_ZERO (0 << 18) +#define VI6_RPF_ALPH_SEL_AEXT_EXT (1 << 18) +#define VI6_RPF_ALPH_SEL_AEXT_ONE (2 << 18) +#define VI6_RPF_ALPH_SEL_AEXT_MASK (3 << 18) +#define VI6_RPF_ALPH_SEL_ALPHA0_MASK (0xff << 8) +#define VI6_RPF_ALPH_SEL_ALPHA0_SHIFT 8 +#define VI6_RPF_ALPH_SEL_ALPHA1_MASK (0xff << 0) +#define VI6_RPF_ALPH_SEL_ALPHA1_SHIFT 0 + +#define VI6_RPF_VRTCOL_SET 0x0318 +#define VI6_RPF_VRTCOL_SET_LAYA_MASK (0xff << 24) +#define VI6_RPF_VRTCOL_SET_LAYA_SHIFT 24 +#define VI6_RPF_VRTCOL_SET_LAYR_MASK (0xff << 16) +#define VI6_RPF_VRTCOL_SET_LAYR_SHIFT 16 +#define VI6_RPF_VRTCOL_SET_LAYG_MASK (0xff << 8) +#define VI6_RPF_VRTCOL_SET_LAYG_SHIFT 8 +#define VI6_RPF_VRTCOL_SET_LAYB_MASK (0xff << 0) +#define VI6_RPF_VRTCOL_SET_LAYB_SHIFT 0 + +#define VI6_RPF_MSK_CTRL 0x031c +#define VI6_RPF_MSK_CTRL_MSK_EN (1 << 24) +#define VI6_RPF_MSK_CTRL_MGR_MASK (0xff << 16) +#define VI6_RPF_MSK_CTRL_MGR_SHIFT 16 +#define VI6_RPF_MSK_CTRL_MGG_MASK (0xff << 8) +#define VI6_RPF_MSK_CTRL_MGG_SHIFT 8 +#define VI6_RPF_MSK_CTRL_MGB_MASK (0xff << 0) +#define VI6_RPF_MSK_CTRL_MGB_SHIFT 0 + +#define VI6_RPF_MSK_SET0 0x0320 +#define VI6_RPF_MSK_SET1 0x0324 +#define VI6_RPF_MSK_SET_MSA_MASK (0xff << 24) +#define VI6_RPF_MSK_SET_MSA_SHIFT 24 +#define VI6_RPF_MSK_SET_MSR_MASK (0xff << 16) +#define VI6_RPF_MSK_SET_MSR_SHIFT 16 +#define VI6_RPF_MSK_SET_MSG_MASK (0xff << 8) +#define VI6_RPF_MSK_SET_MSG_SHIFT 8 +#define VI6_RPF_MSK_SET_MSB_MASK (0xff << 0) +#define VI6_RPF_MSK_SET_MSB_SHIFT 0 + +#define VI6_RPF_CKEY_CTRL 0x0328 +#define VI6_RPF_CKEY_CTRL_CV (1 << 4) +#define VI6_RPF_CKEY_CTRL_SAPE1 (1 << 1) +#define VI6_RPF_CKEY_CTRL_SAPE0 (1 << 0) + +#define VI6_RPF_CKEY_SET0 0x032c +#define VI6_RPF_CKEY_SET1 0x0330 +#define VI6_RPF_CKEY_SET_AP_MASK (0xff << 24) +#define VI6_RPF_CKEY_SET_AP_SHIFT 24 +#define VI6_RPF_CKEY_SET_R_MASK (0xff << 16) +#define VI6_RPF_CKEY_SET_R_SHIFT 16 +#define VI6_RPF_CKEY_SET_GY_MASK (0xff << 8) +#define VI6_RPF_CKEY_SET_GY_SHIFT 8 +#define VI6_RPF_CKEY_SET_B_MASK (0xff << 0) +#define VI6_RPF_CKEY_SET_B_SHIFT 0 + +#define VI6_RPF_SRCM_PSTRIDE 0x0334 +#define VI6_RPF_SRCM_PSTRIDE_Y_SHIFT 16 +#define VI6_RPF_SRCM_PSTRIDE_C_SHIFT 0 + +#define VI6_RPF_SRCM_ASTRIDE 0x0338 +#define VI6_RPF_SRCM_PSTRIDE_A_SHIFT 0 + +#define VI6_RPF_SRCM_ADDR_Y 0x033c +#define VI6_RPF_SRCM_ADDR_C0 0x0340 +#define VI6_RPF_SRCM_ADDR_C1 0x0344 +#define VI6_RPF_SRCM_ADDR_AI 0x0348 + +/* ----------------------------------------------------------------------------- + * WPF Control Registers + */ + +#define VI6_WPF_OFFSET 0x100 + +#define VI6_WPF_SRCRPF 0x1000 +#define VI6_WPF_SRCRPF_VIRACT_DIS (0 << 28) +#define VI6_WPF_SRCRPF_VIRACT_SUB (1 << 28) +#define VI6_WPF_SRCRPF_VIRACT_MST (2 << 28) +#define VI6_WPF_SRCRPF_VIRACT_MASK (3 << 28) +#define VI6_WPF_SRCRPF_RPF_ACT_DIS(n) (0 << ((n) * 2)) +#define VI6_WPF_SRCRPF_RPF_ACT_SUB(n) (1 << ((n) * 2)) +#define VI6_WPF_SRCRPF_RPF_ACT_MST(n) (2 << ((n) * 2)) +#define VI6_WPF_SRCRPF_RPF_ACT_MASK(n) (3 << ((n) * 2)) + +#define VI6_WPF_HSZCLIP 0x1004 +#define VI6_WPF_VSZCLIP 0x1008 +#define VI6_WPF_SZCLIP_EN (1 << 28) +#define VI6_WPF_SZCLIP_OFST_MASK (0xff << 16) +#define VI6_WPF_SZCLIP_OFST_SHIFT 16 +#define VI6_WPF_SZCLIP_SIZE_MASK (0x1fff << 0) +#define VI6_WPF_SZCLIP_SIZE_SHIFT 0 + +#define VI6_WPF_OUTFMT 0x100c +#define VI6_WPF_OUTFMT_PDV_MASK (0xff << 24) +#define VI6_WPF_OUTFMT_PDV_SHIFT 24 +#define VI6_WPF_OUTFMT_PXA (1 << 23) +#define VI6_WPF_OUTFMT_FLP (1 << 16) +#define VI6_WPF_OUTFMT_SPYCS (1 << 15) +#define VI6_WPF_OUTFMT_SPUVS (1 << 14) +#define VI6_WPF_OUTFMT_DITH_DIS (0 << 12) +#define VI6_WPF_OUTFMT_DITH_EN (3 << 12) +#define VI6_WPF_OUTFMT_DITH_MASK (3 << 12) +#define VI6_WPF_OUTFMT_WRTM_BT601 (0 << 9) +#define VI6_WPF_OUTFMT_WRTM_BT601_EXT (1 << 9) +#define VI6_WPF_OUTFMT_WRTM_BT709 (2 << 9) +#define VI6_WPF_OUTFMT_WRTM_BT709_EXT (3 << 9) +#define VI6_WPF_OUTFMT_WRTM_MASK (7 << 9) +#define VI6_WPF_OUTFMT_CSC (1 << 8) +#define VI6_WPF_OUTFMT_WRFMT_MASK (0x7f << 0) +#define VI6_WPF_OUTFMT_WRFMT_SHIFT 0 + +#define VI6_WPF_DSWAP 0x1010 +#define VI6_WPF_DSWAP_P_LLS (1 << 3) +#define VI6_WPF_DSWAP_P_LWS (1 << 2) +#define VI6_WPF_DSWAP_P_WDS (1 << 1) +#define VI6_WPF_DSWAP_P_BTS (1 << 0) + +#define VI6_WPF_RNDCTRL 0x1014 +#define VI6_WPF_RNDCTRL_CBRM (1 << 28) +#define VI6_WPF_RNDCTRL_ABRM_TRUNC (0 << 24) +#define VI6_WPF_RNDCTRL_ABRM_ROUND (1 << 24) +#define VI6_WPF_RNDCTRL_ABRM_THRESH (2 << 24) +#define VI6_WPF_RNDCTRL_ABRM_MASK (3 << 24) +#define VI6_WPF_RNDCTRL_ATHRESH_MASK (0xff << 16) +#define VI6_WPF_RNDCTRL_ATHRESH_SHIFT 16 +#define VI6_WPF_RNDCTRL_CLMD_FULL (0 << 12) +#define VI6_WPF_RNDCTRL_CLMD_CLIP (1 << 12) +#define VI6_WPF_RNDCTRL_CLMD_EXT (2 << 12) +#define VI6_WPF_RNDCTRL_CLMD_MASK (3 << 12) + +#define VI6_WPF_DSTM_STRIDE_Y 0x101c +#define VI6_WPF_DSTM_STRIDE_C 0x1020 +#define VI6_WPF_DSTM_ADDR_Y 0x1024 +#define VI6_WPF_DSTM_ADDR_C0 0x1028 +#define VI6_WPF_DSTM_ADDR_C1 0x102c + +#define VI6_WPF_WRBCK_CTRL 0x1034 +#define VI6_WPF_WRBCK_CTRL_WBMD (1 << 0) + +/* ----------------------------------------------------------------------------- + * DPR Control Registers + */ + +#define VI6_DPR_RPF_ROUTE(n) (0x2000 + (n) * 4) + +#define VI6_DPR_WPF_FPORCH(n) (0x2014 + (n) * 4) +#define VI6_DPR_WPF_FPORCH_FP_WPFN (5 << 8) + +#define VI6_DPR_SRU_ROUTE 0x2024 +#define VI6_DPR_UDS_ROUTE(n) (0x2028 + (n) * 4) +#define VI6_DPR_LUT_ROUTE 0x203c +#define VI6_DPR_CLU_ROUTE 0x2040 +#define VI6_DPR_HST_ROUTE 0x2044 +#define VI6_DPR_HSI_ROUTE 0x2048 +#define VI6_DPR_BRU_ROUTE 0x204c +#define VI6_DPR_ROUTE_FXA_MASK (0xff << 8) +#define VI6_DPR_ROUTE_FXA_SHIFT 16 +#define VI6_DPR_ROUTE_FP_MASK (0xff << 8) +#define VI6_DPR_ROUTE_FP_SHIFT 8 +#define VI6_DPR_ROUTE_RT_MASK (0x3f << 0) +#define VI6_DPR_ROUTE_RT_SHIFT 0 + +#define VI6_DPR_HGO_SMPPT 0x2050 +#define VI6_DPR_HGT_SMPPT 0x2054 +#define VI6_DPR_SMPPT_TGW_MASK (7 << 8) +#define VI6_DPR_SMPPT_TGW_SHIFT 8 +#define VI6_DPR_SMPPT_PT_MASK (0x3f << 0) +#define VI6_DPR_SMPPT_PT_SHIFT 0 + +#define VI6_DPR_NODE_RPF(n) (n) +#define VI6_DPR_NODE_SRU 16 +#define VI6_DPR_NODE_UDS(n) (17 + (n)) +#define VI6_DPR_NODE_LUT 22 +#define VI6_DPR_NODE_BRU_IN(n) (23 + (n)) +#define VI6_DPR_NODE_BRU_OUT 27 +#define VI6_DPR_NODE_CLU 29 +#define VI6_DPR_NODE_HST 30 +#define VI6_DPR_NODE_HSI 31 +#define VI6_DPR_NODE_LIF 55 +#define VI6_DPR_NODE_WPF(n) (56 + (n)) +#define VI6_DPR_NODE_UNUSED 63 + +/* ----------------------------------------------------------------------------- + * SRU Control Registers + */ + +#define VI6_SRU_CTRL0 0x2200 +#define VI6_SRU_CTRL1 0x2204 +#define VI6_SRU_CTRL2 0x2208 + +/* ----------------------------------------------------------------------------- + * UDS Control Registers + */ + +#define VI6_UDS_OFFSET 0x100 + +#define VI6_UDS_CTRL 0x2300 +#define VI6_UDS_CTRL_AMD (1 << 30) +#define VI6_UDS_CTRL_FMD (1 << 29) +#define VI6_UDS_CTRL_BLADV (1 << 28) +#define VI6_UDS_CTRL_AON (1 << 25) +#define VI6_UDS_CTRL_ATHON (1 << 24) +#define VI6_UDS_CTRL_BC (1 << 20) +#define VI6_UDS_CTRL_NE_A (1 << 19) +#define VI6_UDS_CTRL_NE_RCR (1 << 18) +#define VI6_UDS_CTRL_NE_GY (1 << 17) +#define VI6_UDS_CTRL_NE_BCB (1 << 16) +#define VI6_UDS_CTRL_TDIPC (1 << 1) + +#define VI6_UDS_SCALE 0x2304 +#define VI6_UDS_SCALE_HMANT_MASK (0xf << 28) +#define VI6_UDS_SCALE_HMANT_SHIFT 28 +#define VI6_UDS_SCALE_HFRAC_MASK (0xfff << 16) +#define VI6_UDS_SCALE_HFRAC_SHIFT 16 +#define VI6_UDS_SCALE_VMANT_MASK (0xf << 12) +#define VI6_UDS_SCALE_VMANT_SHIFT 12 +#define VI6_UDS_SCALE_VFRAC_MASK (0xfff << 0) +#define VI6_UDS_SCALE_VFRAC_SHIFT 0 + +#define VI6_UDS_ALPTH 0x2308 +#define VI6_UDS_ALPTH_TH1_MASK (0xff << 8) +#define VI6_UDS_ALPTH_TH1_SHIFT 8 +#define VI6_UDS_ALPTH_TH0_MASK (0xff << 0) +#define VI6_UDS_ALPTH_TH0_SHIFT 0 + +#define VI6_UDS_ALPVAL 0x230c +#define VI6_UDS_ALPVAL_VAL2_MASK (0xff << 16) +#define VI6_UDS_ALPVAL_VAL2_SHIFT 16 +#define VI6_UDS_ALPVAL_VAL1_MASK (0xff << 8) +#define VI6_UDS_ALPVAL_VAL1_SHIFT 8 +#define VI6_UDS_ALPVAL_VAL0_MASK (0xff << 0) +#define VI6_UDS_ALPVAL_VAL0_SHIFT 0 + +#define VI6_UDS_PASS_BWIDTH 0x2310 +#define VI6_UDS_PASS_BWIDTH_H_MASK (0x7f << 16) +#define VI6_UDS_PASS_BWIDTH_H_SHIFT 16 +#define VI6_UDS_PASS_BWIDTH_V_MASK (0x7f << 0) +#define VI6_UDS_PASS_BWIDTH_V_SHIFT 0 + +#define VI6_UDS_IPC 0x2318 +#define VI6_UDS_IPC_FIELD (1 << 27) +#define VI6_UDS_IPC_VEDP_MASK (0xfff << 0) +#define VI6_UDS_IPC_VEDP_SHIFT 0 + +#define VI6_UDS_CLIP_SIZE 0x2324 +#define VI6_UDS_CLIP_SIZE_HSIZE_MASK (0x1fff << 16) +#define VI6_UDS_CLIP_SIZE_HSIZE_SHIFT 16 +#define VI6_UDS_CLIP_SIZE_VSIZE_MASK (0x1fff << 0) +#define VI6_UDS_CLIP_SIZE_VSIZE_SHIFT 0 + +#define VI6_UDS_FILL_COLOR 0x2328 +#define VI6_UDS_FILL_COLOR_RFILC_MASK (0xff << 16) +#define VI6_UDS_FILL_COLOR_RFILC_SHIFT 16 +#define VI6_UDS_FILL_COLOR_GFILC_MASK (0xff << 8) +#define VI6_UDS_FILL_COLOR_GFILC_SHIFT 8 +#define VI6_UDS_FILL_COLOR_BFILC_MASK (0xff << 0) +#define VI6_UDS_FILL_COLOR_BFILC_SHIFT 0 + +/* ----------------------------------------------------------------------------- + * LUT Control Registers + */ + +#define VI6_LUT_CTRL 0x2800 + +/* ----------------------------------------------------------------------------- + * CLU Control Registers + */ + +#define VI6_CLU_CTRL 0x2900 + +/* ----------------------------------------------------------------------------- + * HST Control Registers + */ + +#define VI6_HST_CTRL 0x2a00 + +/* ----------------------------------------------------------------------------- + * HSI Control Registers + */ + +#define VI6_HSI_CTRL 0x2b00 + +/* ----------------------------------------------------------------------------- + * BRU Control Registers + */ + +#define VI6_BRU_INCTRL 0x2c00 +#define VI6_BRU_VIRRPF_SIZE 0x2c04 +#define VI6_BRU_VIRRPF_LOC 0x2c08 +#define VI6_BRU_VIRRPF_COL 0x2c0c +#define VI6_BRU_CTRL(n) (0x2c10 + (n) * 8) +#define VI6_BRU_BLD(n) (0x2c14 + (n) * 8) +#define VI6_BRU_ROP 0x2c30 + +/* ----------------------------------------------------------------------------- + * HGO Control Registers + */ + +#define VI6_HGO_OFFSET 0x3000 +#define VI6_HGO_SIZE 0x3004 +#define VI6_HGO_MODE 0x3008 +#define VI6_HGO_LB_TH 0x300c +#define VI6_HGO_LBn_H(n) (0x3010 + (n) * 8) +#define VI6_HGO_LBn_V(n) (0x3014 + (n) * 8) +#define VI6_HGO_R_HISTO 0x3030 +#define VI6_HGO_R_MAXMIN 0x3130 +#define VI6_HGO_R_SUM 0x3134 +#define VI6_HGO_R_LB_DET 0x3138 +#define VI6_HGO_G_HISTO 0x3140 +#define VI6_HGO_G_MAXMIN 0x3240 +#define VI6_HGO_G_SUM 0x3244 +#define VI6_HGO_G_LB_DET 0x3248 +#define VI6_HGO_B_HISTO 0x3250 +#define VI6_HGO_B_MAXMIN 0x3350 +#define VI6_HGO_B_SUM 0x3354 +#define VI6_HGO_B_LB_DET 0x3358 +#define VI6_HGO_REGRST 0x33fc + +/* ----------------------------------------------------------------------------- + * HGT Control Registers + */ + +#define VI6_HGT_OFFSET 0x3400 +#define VI6_HGT_SIZE 0x3404 +#define VI6_HGT_MODE 0x3408 +#define VI6_HGT_HUE_AREA(n) (0x340c + (n) * 4) +#define VI6_HGT_LB_TH 0x3424 +#define VI6_HGT_LBn_H(n) (0x3438 + (n) * 8) +#define VI6_HGT_LBn_V(n) (0x342c + (n) * 8) +#define VI6_HGT_HISTO(m, n) (0x3450 + (m) * 128 + (n) * 4) +#define VI6_HGT_MAXMIN 0x3750 +#define VI6_HGT_SUM 0x3754 +#define VI6_HGT_LB_DET 0x3758 +#define VI6_HGT_REGRST 0x37fc + +/* ----------------------------------------------------------------------------- + * LIF Control Registers + */ + +#define VI6_LIF_CTRL 0x3b00 +#define VI6_LIF_CTRL_OBTH_MASK (0x7ff << 16) +#define VI6_LIF_CTRL_OBTH_SHIFT 16 +#define VI6_LIF_CTRL_CFMT (1 << 4) +#define VI6_LIF_CTRL_REQSEL (1 << 1) +#define VI6_LIF_CTRL_LIF_EN (1 << 0) + +#define VI6_LIF_CSBTH 0x3b04 +#define VI6_LIF_CSBTH_HBTH_MASK (0x7ff << 16) +#define VI6_LIF_CSBTH_HBTH_SHIFT 16 +#define VI6_LIF_CSBTH_LBTH_MASK (0x7ff << 0) +#define VI6_LIF_CSBTH_LBTH_SHIFT 0 + +/* ----------------------------------------------------------------------------- + * Security Control Registers + */ + +#define VI6_SECURITY_CTRL0 0x3d00 +#define VI6_SECURITY_CTRL1 0x3d04 + +/* ----------------------------------------------------------------------------- + * RPF CLUT Registers + */ + +#define VI6_CLUT_TABLE 0x4000 + +/* ----------------------------------------------------------------------------- + * 1D LUT Registers + */ + +#define VI6_LUT_TABLE 0x7000 + +/* ----------------------------------------------------------------------------- + * 3D LUT Registers + */ + +#define VI6_CLU_ADDR 0x7400 +#define VI6_CLU_DATA 0x7404 + +/* ----------------------------------------------------------------------------- + * Formats + */ + +#define VI6_FMT_RGB_332 0x00 +#define VI6_FMT_XRGB_4444 0x01 +#define VI6_FMT_RGBX_4444 0x02 +#define VI6_FMT_XRGB_1555 0x04 +#define VI6_FMT_RGBX_5551 0x05 +#define VI6_FMT_RGB_565 0x06 +#define VI6_FMT_AXRGB_86666 0x07 +#define VI6_FMT_RGBXA_66668 0x08 +#define VI6_FMT_XRGBA_66668 0x09 +#define VI6_FMT_ARGBX_86666 0x0a +#define VI6_FMT_AXRXGXB_8262626 0x0b +#define VI6_FMT_XRXGXBA_2626268 0x0c +#define VI6_FMT_ARXGXBX_8626262 0x0d +#define VI6_FMT_RXGXBXA_6262628 0x0e +#define VI6_FMT_XRGB_6666 0x0f +#define VI6_FMT_RGBX_6666 0x10 +#define VI6_FMT_XRXGXB_262626 0x11 +#define VI6_FMT_RXGXBX_626262 0x12 +#define VI6_FMT_ARGB_8888 0x13 +#define VI6_FMT_RGBA_8888 0x14 +#define VI6_FMT_RGB_888 0x15 +#define VI6_FMT_XRGXGB_763763 0x16 +#define VI6_FMT_XXRGB_86666 0x17 +#define VI6_FMT_BGR_888 0x18 +#define VI6_FMT_ARGB_4444 0x19 +#define VI6_FMT_RGBA_4444 0x1a +#define VI6_FMT_ARGB_1555 0x1b +#define VI6_FMT_RGBA_5551 0x1c +#define VI6_FMT_ABGR_4444 0x1d +#define VI6_FMT_BGRA_4444 0x1e +#define VI6_FMT_ABGR_1555 0x1f +#define VI6_FMT_BGRA_5551 0x20 +#define VI6_FMT_XBXGXR_262626 0x21 +#define VI6_FMT_ABGR_8888 0x22 +#define VI6_FMT_XXRGB_88565 0x23 + +#define VI6_FMT_Y_UV_444 0x40 +#define VI6_FMT_Y_UV_422 0x41 +#define VI6_FMT_Y_UV_420 0x42 +#define VI6_FMT_YUV_444 0x46 +#define VI6_FMT_YUYV_422 0x47 +#define VI6_FMT_YYUV_422 0x48 +#define VI6_FMT_YUV_420 0x49 +#define VI6_FMT_Y_U_V_444 0x4a +#define VI6_FMT_Y_U_V_422 0x4b +#define VI6_FMT_Y_U_V_420 0x4c + +#endif /* __VSP1_REGS_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_rpf.c b/drivers/media/platform/vsp1/vsp1_rpf.c new file mode 100644 index 000000000000..254871d3423e --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_rpf.c @@ -0,0 +1,209 @@ +/* + * vsp1_rpf.c -- R-Car VSP1 Read Pixel Formatter + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/device.h> + +#include <media/v4l2-subdev.h> + +#include "vsp1.h" +#include "vsp1_rwpf.h" +#include "vsp1_video.h" + +#define RPF_MAX_WIDTH 8190 +#define RPF_MAX_HEIGHT 8190 + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline u32 vsp1_rpf_read(struct vsp1_rwpf *rpf, u32 reg) +{ + return vsp1_read(rpf->entity.vsp1, + reg + rpf->entity.index * VI6_RPF_OFFSET); +} + +static inline void vsp1_rpf_write(struct vsp1_rwpf *rpf, u32 reg, u32 data) +{ + vsp1_write(rpf->entity.vsp1, + reg + rpf->entity.index * VI6_RPF_OFFSET, data); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Core Operations + */ + +static int rpf_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct vsp1_rwpf *rpf = to_rwpf(subdev); + const struct vsp1_format_info *fmtinfo = rpf->video.fmtinfo; + const struct v4l2_pix_format_mplane *format = &rpf->video.format; + u32 pstride; + u32 infmt; + + if (!enable) + return 0; + + /* Source size and stride. Cropping isn't supported yet. */ + vsp1_rpf_write(rpf, VI6_RPF_SRC_BSIZE, + (format->width << VI6_RPF_SRC_BSIZE_BHSIZE_SHIFT) | + (format->height << VI6_RPF_SRC_BSIZE_BVSIZE_SHIFT)); + vsp1_rpf_write(rpf, VI6_RPF_SRC_ESIZE, + (format->width << VI6_RPF_SRC_ESIZE_EHSIZE_SHIFT) | + (format->height << VI6_RPF_SRC_ESIZE_EVSIZE_SHIFT)); + + pstride = format->plane_fmt[0].bytesperline + << VI6_RPF_SRCM_PSTRIDE_Y_SHIFT; + if (format->num_planes > 1) + pstride |= format->plane_fmt[1].bytesperline + << VI6_RPF_SRCM_PSTRIDE_C_SHIFT; + + vsp1_rpf_write(rpf, VI6_RPF_SRCM_PSTRIDE, pstride); + + /* Format */ + infmt = VI6_RPF_INFMT_CIPM + | (fmtinfo->hwfmt << VI6_RPF_INFMT_RDFMT_SHIFT); + + if (fmtinfo->swap_yc) + infmt |= VI6_RPF_INFMT_SPYCS; + if (fmtinfo->swap_uv) + infmt |= VI6_RPF_INFMT_SPUVS; + + if (rpf->entity.formats[RWPF_PAD_SINK].code != + rpf->entity.formats[RWPF_PAD_SOURCE].code) + infmt |= VI6_RPF_INFMT_CSC; + + vsp1_rpf_write(rpf, VI6_RPF_INFMT, infmt); + vsp1_rpf_write(rpf, VI6_RPF_DSWAP, fmtinfo->swap); + + /* Output location. Composing isn't supported yet. */ + vsp1_rpf_write(rpf, VI6_RPF_LOC, 0); + + /* Disable alpha, mask and color key. Set the alpha channel to a fixed + * value of 255. + */ + vsp1_rpf_write(rpf, VI6_RPF_ALPH_SEL, VI6_RPF_ALPH_SEL_ASEL_FIXED); + vsp1_rpf_write(rpf, VI6_RPF_VRTCOL_SET, + 255 << VI6_RPF_VRTCOL_SET_LAYA_SHIFT); + vsp1_rpf_write(rpf, VI6_RPF_MSK_CTRL, 0); + vsp1_rpf_write(rpf, VI6_RPF_CKEY_CTRL, 0); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static struct v4l2_subdev_video_ops rpf_video_ops = { + .s_stream = rpf_s_stream, +}; + +static struct v4l2_subdev_pad_ops rpf_pad_ops = { + .enum_mbus_code = vsp1_rwpf_enum_mbus_code, + .enum_frame_size = vsp1_rwpf_enum_frame_size, + .get_fmt = vsp1_rwpf_get_format, + .set_fmt = vsp1_rwpf_set_format, +}; + +static struct v4l2_subdev_ops rpf_ops = { + .video = &rpf_video_ops, + .pad = &rpf_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Video Device Operations + */ + +static void rpf_vdev_queue(struct vsp1_video *video, + struct vsp1_video_buffer *buf) +{ + struct vsp1_rwpf *rpf = container_of(video, struct vsp1_rwpf, video); + + vsp1_rpf_write(rpf, VI6_RPF_SRCM_ADDR_Y, buf->addr[0]); + if (buf->buf.num_planes > 1) + vsp1_rpf_write(rpf, VI6_RPF_SRCM_ADDR_C0, buf->addr[1]); + if (buf->buf.num_planes > 2) + vsp1_rpf_write(rpf, VI6_RPF_SRCM_ADDR_C1, buf->addr[2]); +} + +static const struct vsp1_video_operations rpf_vdev_ops = { + .queue = rpf_vdev_queue, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_rwpf *vsp1_rpf_create(struct vsp1_device *vsp1, unsigned int index) +{ + struct v4l2_subdev *subdev; + struct vsp1_video *video; + struct vsp1_rwpf *rpf; + int ret; + + rpf = devm_kzalloc(vsp1->dev, sizeof(*rpf), GFP_KERNEL); + if (rpf == NULL) + return ERR_PTR(-ENOMEM); + + rpf->max_width = RPF_MAX_WIDTH; + rpf->max_height = RPF_MAX_HEIGHT; + + rpf->entity.type = VSP1_ENTITY_RPF; + rpf->entity.index = index; + rpf->entity.id = VI6_DPR_NODE_RPF(index); + + ret = vsp1_entity_init(vsp1, &rpf->entity, 2); + if (ret < 0) + return ERR_PTR(ret); + + /* Initialize the V4L2 subdev. */ + subdev = &rpf->entity.subdev; + v4l2_subdev_init(subdev, &rpf_ops); + + subdev->entity.ops = &vsp1_media_ops; + subdev->internal_ops = &vsp1_subdev_internal_ops; + snprintf(subdev->name, sizeof(subdev->name), "%s rpf.%u", + dev_name(vsp1->dev), index); + v4l2_set_subdevdata(subdev, rpf); + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + vsp1_entity_init_formats(subdev, NULL); + + /* Initialize the video device. */ + video = &rpf->video; + + video->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + video->vsp1 = vsp1; + video->ops = &rpf_vdev_ops; + + ret = vsp1_video_init(video, &rpf->entity); + if (ret < 0) + goto error_video; + + /* Connect the video device to the RPF. */ + ret = media_entity_create_link(&rpf->video.video.entity, 0, + &rpf->entity.subdev.entity, + RWPF_PAD_SINK, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret < 0) + goto error_link; + + return rpf; + +error_link: + vsp1_video_cleanup(video); +error_video: + media_entity_cleanup(&rpf->entity.subdev.entity); + return ERR_PTR(ret); +} diff --git a/drivers/media/platform/vsp1/vsp1_rwpf.c b/drivers/media/platform/vsp1/vsp1_rwpf.c new file mode 100644 index 000000000000..9752d5516ceb --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_rwpf.c @@ -0,0 +1,124 @@ +/* + * vsp1_rwpf.c -- R-Car VSP1 Read and Write Pixel Formatters + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <media/v4l2-subdev.h> + +#include "vsp1.h" +#include "vsp1_rwpf.h" +#include "vsp1_video.h" + +#define RWPF_MIN_WIDTH 1 +#define RWPF_MIN_HEIGHT 1 + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Pad Operations + */ + +int vsp1_rwpf_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + static const unsigned int codes[] = { + V4L2_MBUS_FMT_ARGB8888_1X32, + V4L2_MBUS_FMT_AYUV8_1X32, + }; + + if (code->index >= ARRAY_SIZE(codes)) + return -EINVAL; + + code->code = codes[code->index]; + + return 0; +} + +int vsp1_rwpf_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct vsp1_rwpf *rwpf = to_rwpf(subdev); + struct v4l2_mbus_framefmt *format; + + format = v4l2_subdev_get_try_format(fh, fse->pad); + + if (fse->index || fse->code != format->code) + return -EINVAL; + + if (fse->pad == RWPF_PAD_SINK) { + fse->min_width = RWPF_MIN_WIDTH; + fse->max_width = rwpf->max_width; + fse->min_height = RWPF_MIN_HEIGHT; + fse->max_height = rwpf->max_height; + } else { + /* The size on the source pad are fixed and always identical to + * the size on the sink pad. + */ + fse->min_width = format->width; + fse->max_width = format->width; + fse->min_height = format->height; + fse->max_height = format->height; + } + + return 0; +} + +int vsp1_rwpf_get_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_rwpf *rwpf = to_rwpf(subdev); + + fmt->format = *vsp1_entity_get_pad_format(&rwpf->entity, fh, fmt->pad, + fmt->which); + + return 0; +} + +int vsp1_rwpf_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_rwpf *rwpf = to_rwpf(subdev); + struct v4l2_mbus_framefmt *format; + + /* Default to YUV if the requested format is not supported. */ + if (fmt->format.code != V4L2_MBUS_FMT_ARGB8888_1X32 && + fmt->format.code != V4L2_MBUS_FMT_AYUV8_1X32) + fmt->format.code = V4L2_MBUS_FMT_AYUV8_1X32; + + format = vsp1_entity_get_pad_format(&rwpf->entity, fh, fmt->pad, + fmt->which); + + if (fmt->pad == RWPF_PAD_SOURCE) { + /* The RWPF performs format conversion but can't scale, only the + * format code can be changed on the source pad. + */ + format->code = fmt->format.code; + fmt->format = *format; + return 0; + } + + format->code = fmt->format.code; + format->width = clamp_t(unsigned int, fmt->format.width, + RWPF_MIN_WIDTH, rwpf->max_width); + format->height = clamp_t(unsigned int, fmt->format.height, + RWPF_MIN_HEIGHT, rwpf->max_height); + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + + fmt->format = *format; + + /* Propagate the format to the source pad. */ + format = vsp1_entity_get_pad_format(&rwpf->entity, fh, RWPF_PAD_SOURCE, + fmt->which); + *format = fmt->format; + + return 0; +} diff --git a/drivers/media/platform/vsp1/vsp1_rwpf.h b/drivers/media/platform/vsp1/vsp1_rwpf.h new file mode 100644 index 000000000000..c182d85f36b3 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_rwpf.h @@ -0,0 +1,53 @@ +/* + * vsp1_rwpf.h -- R-Car VSP1 Read and Write Pixel Formatters + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#ifndef __VSP1_RWPF_H__ +#define __VSP1_RWPF_H__ + +#include <media/media-entity.h> +#include <media/v4l2-subdev.h> + +#include "vsp1.h" +#include "vsp1_entity.h" +#include "vsp1_video.h" + +#define RWPF_PAD_SINK 0 +#define RWPF_PAD_SOURCE 1 + +struct vsp1_rwpf { + struct vsp1_entity entity; + struct vsp1_video video; + + unsigned int max_width; + unsigned int max_height; +}; + +static inline struct vsp1_rwpf *to_rwpf(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_rwpf, entity.subdev); +} + +struct vsp1_rwpf *vsp1_rpf_create(struct vsp1_device *vsp1, unsigned int index); +struct vsp1_rwpf *vsp1_wpf_create(struct vsp1_device *vsp1, unsigned int index); + +int vsp1_rwpf_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code); +int vsp1_rwpf_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse); +int vsp1_rwpf_get_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt); +int vsp1_rwpf_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt); + +#endif /* __VSP1_RWPF_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_uds.c b/drivers/media/platform/vsp1/vsp1_uds.c new file mode 100644 index 000000000000..0e50b37f060d --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_uds.c @@ -0,0 +1,346 @@ +/* + * vsp1_uds.c -- R-Car VSP1 Up and Down Scaler + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/device.h> +#include <linux/gfp.h> + +#include <media/v4l2-subdev.h> + +#include "vsp1.h" +#include "vsp1_uds.h" + +#define UDS_MIN_SIZE 4U +#define UDS_MAX_SIZE 8190U + +#define UDS_MIN_FACTOR 0x0100 +#define UDS_MAX_FACTOR 0xffff + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline u32 vsp1_uds_read(struct vsp1_uds *uds, u32 reg) +{ + return vsp1_read(uds->entity.vsp1, + reg + uds->entity.index * VI6_UDS_OFFSET); +} + +static inline void vsp1_uds_write(struct vsp1_uds *uds, u32 reg, u32 data) +{ + vsp1_write(uds->entity.vsp1, + reg + uds->entity.index * VI6_UDS_OFFSET, data); +} + +/* ----------------------------------------------------------------------------- + * Scaling Computation + */ + +/* + * uds_output_size - Return the output size for an input size and scaling ratio + * @input: input size in pixels + * @ratio: scaling ratio in U4.12 fixed-point format + */ +static unsigned int uds_output_size(unsigned int input, unsigned int ratio) +{ + if (ratio > 4096) { + /* Down-scaling */ + unsigned int mp; + + mp = ratio / 4096; + mp = mp < 4 ? 1 : (mp < 8 ? 2 : 4); + + return (input - 1) / mp * mp * 4096 / ratio + 1; + } else { + /* Up-scaling */ + return (input - 1) * 4096 / ratio + 1; + } +} + +/* + * uds_output_limits - Return the min and max output sizes for an input size + * @input: input size in pixels + * @minimum: minimum output size (returned) + * @maximum: maximum output size (returned) + */ +static void uds_output_limits(unsigned int input, + unsigned int *minimum, unsigned int *maximum) +{ + *minimum = max(uds_output_size(input, UDS_MAX_FACTOR), UDS_MIN_SIZE); + *maximum = min(uds_output_size(input, UDS_MIN_FACTOR), UDS_MAX_SIZE); +} + +/* + * uds_passband_width - Return the passband filter width for a scaling ratio + * @ratio: scaling ratio in U4.12 fixed-point format + */ +static unsigned int uds_passband_width(unsigned int ratio) +{ + if (ratio >= 4096) { + /* Down-scaling */ + unsigned int mp; + + mp = ratio / 4096; + mp = mp < 4 ? 1 : (mp < 8 ? 2 : 4); + + return 64 * 4096 * mp / ratio; + } else { + /* Up-scaling */ + return 64; + } +} + +static unsigned int uds_compute_ratio(unsigned int input, unsigned int output) +{ + /* TODO: This is an approximation that will need to be refined. */ + return (input - 1) * 4096 / (output - 1); +} + +static void uds_compute_ratios(struct vsp1_uds *uds) +{ + struct v4l2_mbus_framefmt *input = &uds->entity.formats[UDS_PAD_SINK]; + struct v4l2_mbus_framefmt *output = + &uds->entity.formats[UDS_PAD_SOURCE]; + + uds->hscale = uds_compute_ratio(input->width, output->width); + uds->vscale = uds_compute_ratio(input->height, output->height); + + dev_dbg(uds->entity.vsp1->dev, "hscale %u vscale %u\n", + uds->hscale, uds->vscale); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Core Operations + */ + +static int uds_s_stream(struct v4l2_subdev *subdev, int enable) +{ + const struct v4l2_mbus_framefmt *format; + struct vsp1_uds *uds = to_uds(subdev); + + if (!enable) + return 0; + + /* Enable multi-tap scaling. */ + vsp1_uds_write(uds, VI6_UDS_CTRL, VI6_UDS_CTRL_BC); + + vsp1_uds_write(uds, VI6_UDS_PASS_BWIDTH, + (uds_passband_width(uds->hscale) + << VI6_UDS_PASS_BWIDTH_H_SHIFT) | + (uds_passband_width(uds->vscale) + << VI6_UDS_PASS_BWIDTH_V_SHIFT)); + + + /* Set the scaling ratios and the output size. */ + format = &uds->entity.formats[UDS_PAD_SOURCE]; + + vsp1_uds_write(uds, VI6_UDS_SCALE, + (uds->hscale << VI6_UDS_SCALE_HFRAC_SHIFT) | + (uds->vscale << VI6_UDS_SCALE_VFRAC_SHIFT)); + vsp1_uds_write(uds, VI6_UDS_CLIP_SIZE, + (format->width << VI6_UDS_CLIP_SIZE_HSIZE_SHIFT) | + (format->height << VI6_UDS_CLIP_SIZE_VSIZE_SHIFT)); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Pad Operations + */ + +static int uds_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + static const unsigned int codes[] = { + V4L2_MBUS_FMT_ARGB8888_1X32, + V4L2_MBUS_FMT_AYUV8_1X32, + }; + + if (code->pad == UDS_PAD_SINK) { + if (code->index >= ARRAY_SIZE(codes)) + return -EINVAL; + + code->code = codes[code->index]; + } else { + struct v4l2_mbus_framefmt *format; + + /* The UDS can't perform format conversion, the sink format is + * always identical to the source format. + */ + if (code->index) + return -EINVAL; + + format = v4l2_subdev_get_try_format(fh, UDS_PAD_SINK); + code->code = format->code; + } + + return 0; +} + +static int uds_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct v4l2_mbus_framefmt *format; + + format = v4l2_subdev_get_try_format(fh, UDS_PAD_SINK); + + if (fse->index || fse->code != format->code) + return -EINVAL; + + if (fse->pad == UDS_PAD_SINK) { + fse->min_width = UDS_MIN_SIZE; + fse->max_width = UDS_MAX_SIZE; + fse->min_height = UDS_MIN_SIZE; + fse->max_height = UDS_MAX_SIZE; + } else { + uds_output_limits(format->width, &fse->min_width, + &fse->max_width); + uds_output_limits(format->height, &fse->min_height, + &fse->max_height); + } + + return 0; +} + +static int uds_get_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_uds *uds = to_uds(subdev); + + fmt->format = *vsp1_entity_get_pad_format(&uds->entity, fh, fmt->pad, + fmt->which); + + return 0; +} + +static void uds_try_format(struct vsp1_uds *uds, struct v4l2_subdev_fh *fh, + unsigned int pad, struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + struct v4l2_mbus_framefmt *format; + unsigned int minimum; + unsigned int maximum; + + switch (pad) { + case UDS_PAD_SINK: + /* Default to YUV if the requested format is not supported. */ + if (fmt->code != V4L2_MBUS_FMT_ARGB8888_1X32 && + fmt->code != V4L2_MBUS_FMT_AYUV8_1X32) + fmt->code = V4L2_MBUS_FMT_AYUV8_1X32; + + fmt->width = clamp(fmt->width, UDS_MIN_SIZE, UDS_MAX_SIZE); + fmt->height = clamp(fmt->height, UDS_MIN_SIZE, UDS_MAX_SIZE); + break; + + case UDS_PAD_SOURCE: + /* The UDS scales but can't perform format conversion. */ + format = vsp1_entity_get_pad_format(&uds->entity, fh, + UDS_PAD_SINK, which); + fmt->code = format->code; + + uds_output_limits(format->width, &minimum, &maximum); + fmt->width = clamp(fmt->width, minimum, maximum); + uds_output_limits(format->height, &minimum, &maximum); + fmt->height = clamp(fmt->height, minimum, maximum); + break; + } + + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; +} + +static int uds_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_uds *uds = to_uds(subdev); + struct v4l2_mbus_framefmt *format; + + uds_try_format(uds, fh, fmt->pad, &fmt->format, fmt->which); + + format = vsp1_entity_get_pad_format(&uds->entity, fh, fmt->pad, + fmt->which); + *format = fmt->format; + + if (fmt->pad == UDS_PAD_SINK) { + /* Propagate the format to the source pad. */ + format = vsp1_entity_get_pad_format(&uds->entity, fh, + UDS_PAD_SOURCE, fmt->which); + *format = fmt->format; + + uds_try_format(uds, fh, UDS_PAD_SOURCE, format, fmt->which); + } + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) + uds_compute_ratios(uds); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static struct v4l2_subdev_video_ops uds_video_ops = { + .s_stream = uds_s_stream, +}; + +static struct v4l2_subdev_pad_ops uds_pad_ops = { + .enum_mbus_code = uds_enum_mbus_code, + .enum_frame_size = uds_enum_frame_size, + .get_fmt = uds_get_format, + .set_fmt = uds_set_format, +}; + +static struct v4l2_subdev_ops uds_ops = { + .video = &uds_video_ops, + .pad = &uds_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_uds *vsp1_uds_create(struct vsp1_device *vsp1, unsigned int index) +{ + struct v4l2_subdev *subdev; + struct vsp1_uds *uds; + int ret; + + uds = devm_kzalloc(vsp1->dev, sizeof(*uds), GFP_KERNEL); + if (uds == NULL) + return ERR_PTR(-ENOMEM); + + uds->entity.type = VSP1_ENTITY_UDS; + uds->entity.index = index; + uds->entity.id = VI6_DPR_NODE_UDS(index); + + ret = vsp1_entity_init(vsp1, &uds->entity, 2); + if (ret < 0) + return ERR_PTR(ret); + + /* Initialize the V4L2 subdev. */ + subdev = &uds->entity.subdev; + v4l2_subdev_init(subdev, &uds_ops); + + subdev->entity.ops = &vsp1_media_ops; + subdev->internal_ops = &vsp1_subdev_internal_ops; + snprintf(subdev->name, sizeof(subdev->name), "%s uds.%u", + dev_name(vsp1->dev), index); + v4l2_set_subdevdata(subdev, uds); + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + vsp1_entity_init_formats(subdev, NULL); + + return uds; +} diff --git a/drivers/media/platform/vsp1/vsp1_uds.h b/drivers/media/platform/vsp1/vsp1_uds.h new file mode 100644 index 000000000000..972a285abdb9 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_uds.h @@ -0,0 +1,40 @@ +/* + * vsp1_uds.h -- R-Car VSP1 Up and Down Scaler + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#ifndef __VSP1_UDS_H__ +#define __VSP1_UDS_H__ + +#include <media/media-entity.h> +#include <media/v4l2-subdev.h> + +#include "vsp1_entity.h" + +struct vsp1_device; + +#define UDS_PAD_SINK 0 +#define UDS_PAD_SOURCE 1 + +struct vsp1_uds { + struct vsp1_entity entity; + + unsigned int hscale; + unsigned int vscale; +}; + +static inline struct vsp1_uds *to_uds(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_uds, entity.subdev); +} + +struct vsp1_uds *vsp1_uds_create(struct vsp1_device *vsp1, unsigned int index); + +#endif /* __VSP1_UDS_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_video.c b/drivers/media/platform/vsp1/vsp1_video.c new file mode 100644 index 000000000000..714c53ef6c11 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_video.c @@ -0,0 +1,1069 @@ +/* + * vsp1_video.c -- R-Car VSP1 Video Node + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/v4l2-mediabus.h> +#include <linux/videodev2.h> + +#include <media/media-entity.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-subdev.h> +#include <media/videobuf2-core.h> +#include <media/videobuf2-dma-contig.h> + +#include "vsp1.h" +#include "vsp1_entity.h" +#include "vsp1_rwpf.h" +#include "vsp1_video.h" + +#define VSP1_VIDEO_DEF_FORMAT V4L2_PIX_FMT_YUYV +#define VSP1_VIDEO_DEF_WIDTH 1024 +#define VSP1_VIDEO_DEF_HEIGHT 768 + +#define VSP1_VIDEO_MIN_WIDTH 2U +#define VSP1_VIDEO_MAX_WIDTH 8190U +#define VSP1_VIDEO_MIN_HEIGHT 2U +#define VSP1_VIDEO_MAX_HEIGHT 8190U + +/* ----------------------------------------------------------------------------- + * Helper functions + */ + +static const struct vsp1_format_info vsp1_video_formats[] = { + { V4L2_PIX_FMT_RGB332, V4L2_MBUS_FMT_ARGB8888_1X32, + VI6_FMT_RGB_332, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 8, 0, 0 }, false, false, 1, 1 }, + { V4L2_PIX_FMT_RGB444, V4L2_MBUS_FMT_ARGB8888_1X32, + VI6_FMT_XRGB_4444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1 }, + { V4L2_PIX_FMT_RGB555, V4L2_MBUS_FMT_ARGB8888_1X32, + VI6_FMT_XRGB_1555, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1 }, + { V4L2_PIX_FMT_RGB565, V4L2_MBUS_FMT_ARGB8888_1X32, + VI6_FMT_RGB_565, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1 }, + { V4L2_PIX_FMT_BGR24, V4L2_MBUS_FMT_ARGB8888_1X32, + VI6_FMT_BGR_888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 24, 0, 0 }, false, false, 1, 1 }, + { V4L2_PIX_FMT_RGB24, V4L2_MBUS_FMT_ARGB8888_1X32, + VI6_FMT_RGB_888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 24, 0, 0 }, false, false, 1, 1 }, + { V4L2_PIX_FMT_BGR32, V4L2_MBUS_FMT_ARGB8888_1X32, + VI6_FMT_ARGB_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS, + 1, { 32, 0, 0 }, false, false, 1, 1 }, + { V4L2_PIX_FMT_RGB32, V4L2_MBUS_FMT_ARGB8888_1X32, + VI6_FMT_ARGB_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 32, 0, 0 }, false, false, 1, 1 }, + { V4L2_PIX_FMT_UYVY, V4L2_MBUS_FMT_AYUV8_1X32, + VI6_FMT_YUYV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 16, 0, 0 }, false, false, 2, 1 }, + { V4L2_PIX_FMT_VYUY, V4L2_MBUS_FMT_AYUV8_1X32, + VI6_FMT_YUYV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 16, 0, 0 }, false, true, 2, 1 }, + { V4L2_PIX_FMT_YUYV, V4L2_MBUS_FMT_AYUV8_1X32, + VI6_FMT_YUYV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 16, 0, 0 }, true, false, 2, 1 }, + { V4L2_PIX_FMT_YVYU, V4L2_MBUS_FMT_AYUV8_1X32, + VI6_FMT_YUYV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 16, 0, 0 }, true, true, 2, 1 }, + { V4L2_PIX_FMT_NV12M, V4L2_MBUS_FMT_AYUV8_1X32, + VI6_FMT_Y_UV_420, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 2, { 8, 16, 0 }, false, false, 2, 2 }, + { V4L2_PIX_FMT_NV21M, V4L2_MBUS_FMT_AYUV8_1X32, + VI6_FMT_Y_UV_420, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 2, { 8, 16, 0 }, false, true, 2, 2 }, + { V4L2_PIX_FMT_NV16M, V4L2_MBUS_FMT_AYUV8_1X32, + VI6_FMT_Y_UV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 2, { 8, 16, 0 }, false, false, 2, 1 }, + { V4L2_PIX_FMT_NV61M, V4L2_MBUS_FMT_AYUV8_1X32, + VI6_FMT_Y_UV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 2, { 8, 16, 0 }, false, true, 2, 1 }, + { V4L2_PIX_FMT_YUV420M, V4L2_MBUS_FMT_AYUV8_1X32, + VI6_FMT_Y_U_V_420, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 3, { 8, 8, 8 }, false, false, 2, 2 }, +}; + +/* + * vsp1_get_format_info - Retrieve format information for a 4CC + * @fourcc: the format 4CC + * + * Return a pointer to the format information structure corresponding to the + * given V4L2 format 4CC, or NULL if no corresponding format can be found. + */ +static const struct vsp1_format_info *vsp1_get_format_info(u32 fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vsp1_video_formats); ++i) { + const struct vsp1_format_info *info = &vsp1_video_formats[i]; + + if (info->fourcc == fourcc) + return info; + } + + return NULL; +} + + +static struct v4l2_subdev * +vsp1_video_remote_subdev(struct media_pad *local, u32 *pad) +{ + struct media_pad *remote; + + remote = media_entity_remote_pad(local); + if (remote == NULL || + media_entity_type(remote->entity) != MEDIA_ENT_T_V4L2_SUBDEV) + return NULL; + + if (pad) + *pad = remote->index; + + return media_entity_to_v4l2_subdev(remote->entity); +} + +static int vsp1_video_verify_format(struct vsp1_video *video) +{ + struct v4l2_subdev_format fmt; + struct v4l2_subdev *subdev; + int ret; + + subdev = vsp1_video_remote_subdev(&video->pad, &fmt.pad); + if (subdev == NULL) + return -EINVAL; + + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); + if (ret < 0) + return ret == -ENOIOCTLCMD ? -EINVAL : ret; + + if (video->fmtinfo->mbus != fmt.format.code || + video->format.height != fmt.format.height || + video->format.width != fmt.format.width) + return -EINVAL; + + return 0; +} + +static int __vsp1_video_try_format(struct vsp1_video *video, + struct v4l2_pix_format_mplane *pix, + const struct vsp1_format_info **fmtinfo) +{ + const struct vsp1_format_info *info; + unsigned int width = pix->width; + unsigned int height = pix->height; + unsigned int i; + + /* Retrieve format information and select the default format if the + * requested format isn't supported. + */ + info = vsp1_get_format_info(pix->pixelformat); + if (info == NULL) + info = vsp1_get_format_info(VSP1_VIDEO_DEF_FORMAT); + + pix->pixelformat = info->fourcc; + pix->colorspace = V4L2_COLORSPACE_SRGB; + pix->field = V4L2_FIELD_NONE; + memset(pix->reserved, 0, sizeof(pix->reserved)); + + /* Align the width and height for YUV 4:2:2 and 4:2:0 formats. */ + width = round_down(width, info->hsub); + height = round_down(height, info->vsub); + + /* Clamp the width and height. */ + pix->width = clamp(width, VSP1_VIDEO_MIN_WIDTH, VSP1_VIDEO_MAX_WIDTH); + pix->height = clamp(height, VSP1_VIDEO_MIN_HEIGHT, + VSP1_VIDEO_MAX_HEIGHT); + + /* Compute and clamp the stride and image size. While not documented in + * the datasheet, strides not aligned to a multiple of 128 bytes result + * in image corruption. + */ + for (i = 0; i < max(info->planes, 2U); ++i) { + unsigned int hsub = i > 0 ? info->hsub : 1; + unsigned int vsub = i > 0 ? info->vsub : 1; + unsigned int align = 128; + unsigned int bpl; + + bpl = clamp_t(unsigned int, pix->plane_fmt[i].bytesperline, + pix->width / hsub * info->bpp[i] / 8, + round_down(65535U, align)); + + pix->plane_fmt[i].bytesperline = round_up(bpl, align); + pix->plane_fmt[i].sizeimage = pix->plane_fmt[i].bytesperline + * pix->height / vsub; + } + + if (info->planes == 3) { + /* The second and third planes must have the same stride. */ + pix->plane_fmt[2].bytesperline = pix->plane_fmt[1].bytesperline; + pix->plane_fmt[2].sizeimage = pix->plane_fmt[1].sizeimage; + } + + pix->num_planes = info->planes; + + if (fmtinfo) + *fmtinfo = info; + + return 0; +} + +static bool +vsp1_video_format_adjust(struct vsp1_video *video, + const struct v4l2_pix_format_mplane *format, + struct v4l2_pix_format_mplane *adjust) +{ + unsigned int i; + + *adjust = *format; + __vsp1_video_try_format(video, adjust, NULL); + + if (format->width != adjust->width || + format->height != adjust->height || + format->pixelformat != adjust->pixelformat || + format->num_planes != adjust->num_planes) + return false; + + for (i = 0; i < format->num_planes; ++i) { + if (format->plane_fmt[i].bytesperline != + adjust->plane_fmt[i].bytesperline) + return false; + + adjust->plane_fmt[i].sizeimage = + max(adjust->plane_fmt[i].sizeimage, + format->plane_fmt[i].sizeimage); + } + + return true; +} + +/* ----------------------------------------------------------------------------- + * Pipeline Management + */ + +static int vsp1_pipeline_validate_branch(struct vsp1_rwpf *input, + struct vsp1_rwpf *output) +{ + struct vsp1_entity *entity; + unsigned int entities = 0; + struct media_pad *pad; + bool uds_found = false; + + pad = media_entity_remote_pad(&input->entity.pads[RWPF_PAD_SOURCE]); + + while (1) { + if (pad == NULL) + return -EPIPE; + + /* We've reached a video node, that shouldn't have happened. */ + if (media_entity_type(pad->entity) != MEDIA_ENT_T_V4L2_SUBDEV) + return -EPIPE; + + entity = to_vsp1_entity(media_entity_to_v4l2_subdev(pad->entity)); + + /* We've reached the WPF, we're done. */ + if (entity->type == VSP1_ENTITY_WPF) + break; + + /* Ensure the branch has no loop. */ + if (entities & (1 << entity->subdev.entity.id)) + return -EPIPE; + + entities |= 1 << entity->subdev.entity.id; + + /* UDS can't be chained. */ + if (entity->type == VSP1_ENTITY_UDS) { + if (uds_found) + return -EPIPE; + uds_found = true; + } + + /* Follow the source link. The link setup operations ensure + * that the output fan-out can't be more than one, there is thus + * no need to verify here that only a single source link is + * activated. + */ + pad = &entity->pads[entity->source_pad]; + pad = media_entity_remote_pad(pad); + } + + /* The last entity must be the output WPF. */ + if (entity != &output->entity) + return -EPIPE; + + return 0; +} + +static int vsp1_pipeline_validate(struct vsp1_pipeline *pipe, + struct vsp1_video *video) +{ + struct media_entity_graph graph; + struct media_entity *entity = &video->video.entity; + struct media_device *mdev = entity->parent; + unsigned int i; + int ret; + + mutex_lock(&mdev->graph_mutex); + + /* Walk the graph to locate the entities and video nodes. */ + media_entity_graph_walk_start(&graph, entity); + + while ((entity = media_entity_graph_walk_next(&graph))) { + struct v4l2_subdev *subdev; + struct vsp1_rwpf *rwpf; + struct vsp1_entity *e; + + if (media_entity_type(entity) != MEDIA_ENT_T_V4L2_SUBDEV) { + pipe->num_video++; + continue; + } + + subdev = media_entity_to_v4l2_subdev(entity); + e = to_vsp1_entity(subdev); + list_add_tail(&e->list_pipe, &pipe->entities); + + if (e->type == VSP1_ENTITY_RPF) { + rwpf = to_rwpf(subdev); + pipe->inputs[pipe->num_inputs++] = rwpf; + rwpf->video.pipe_index = pipe->num_inputs; + } else if (e->type == VSP1_ENTITY_WPF) { + rwpf = to_rwpf(subdev); + pipe->output = to_rwpf(subdev); + rwpf->video.pipe_index = 0; + } else if (e->type == VSP1_ENTITY_LIF) { + pipe->lif = e; + } + } + + mutex_unlock(&mdev->graph_mutex); + + /* We need one output and at least one input. */ + if (pipe->num_inputs == 0 || !pipe->output) { + ret = -EPIPE; + goto error; + } + + /* Follow links downstream for each input and make sure the graph + * contains no loop and that all branches end at the output WPF. + */ + for (i = 0; i < pipe->num_inputs; ++i) { + ret = vsp1_pipeline_validate_branch(pipe->inputs[i], + pipe->output); + if (ret < 0) + goto error; + } + + return 0; + +error: + INIT_LIST_HEAD(&pipe->entities); + pipe->buffers_ready = 0; + pipe->num_video = 0; + pipe->num_inputs = 0; + pipe->output = NULL; + pipe->lif = NULL; + return ret; +} + +static int vsp1_pipeline_init(struct vsp1_pipeline *pipe, + struct vsp1_video *video) +{ + int ret; + + mutex_lock(&pipe->lock); + + /* If we're the first user validate and initialize the pipeline. */ + if (pipe->use_count == 0) { + ret = vsp1_pipeline_validate(pipe, video); + if (ret < 0) + goto done; + } + + pipe->use_count++; + ret = 0; + +done: + mutex_unlock(&pipe->lock); + return ret; +} + +static void vsp1_pipeline_cleanup(struct vsp1_pipeline *pipe) +{ + mutex_lock(&pipe->lock); + + /* If we're the last user clean up the pipeline. */ + if (--pipe->use_count == 0) { + INIT_LIST_HEAD(&pipe->entities); + pipe->state = VSP1_PIPELINE_STOPPED; + pipe->buffers_ready = 0; + pipe->num_video = 0; + pipe->num_inputs = 0; + pipe->output = NULL; + pipe->lif = NULL; + } + + mutex_unlock(&pipe->lock); +} + +static void vsp1_pipeline_run(struct vsp1_pipeline *pipe) +{ + struct vsp1_device *vsp1 = pipe->output->entity.vsp1; + + vsp1_write(vsp1, VI6_CMD(pipe->output->entity.index), VI6_CMD_STRCMD); + pipe->state = VSP1_PIPELINE_RUNNING; + pipe->buffers_ready = 0; +} + +static int vsp1_pipeline_stop(struct vsp1_pipeline *pipe) +{ + struct vsp1_entity *entity; + unsigned long flags; + int ret; + + spin_lock_irqsave(&pipe->irqlock, flags); + pipe->state = VSP1_PIPELINE_STOPPING; + spin_unlock_irqrestore(&pipe->irqlock, flags); + + ret = wait_event_timeout(pipe->wq, pipe->state == VSP1_PIPELINE_STOPPED, + msecs_to_jiffies(500)); + ret = ret == 0 ? -ETIMEDOUT : 0; + + list_for_each_entry(entity, &pipe->entities, list_pipe) { + if (entity->route) + vsp1_write(entity->vsp1, entity->route, + VI6_DPR_NODE_UNUSED); + + v4l2_subdev_call(&entity->subdev, video, s_stream, 0); + } + + return ret; +} + +static bool vsp1_pipeline_ready(struct vsp1_pipeline *pipe) +{ + unsigned int mask; + + mask = ((1 << pipe->num_inputs) - 1) << 1; + if (!pipe->lif) + mask |= 1 << 0; + + return pipe->buffers_ready == mask; +} + +/* + * vsp1_video_complete_buffer - Complete the current buffer + * @video: the video node + * + * This function completes the current buffer by filling its sequence number, + * time stamp and payload size, and hands it back to the videobuf core. + * + * Return the next queued buffer or NULL if the queue is empty. + */ +static struct vsp1_video_buffer * +vsp1_video_complete_buffer(struct vsp1_video *video) +{ + struct vsp1_video_buffer *next = NULL; + struct vsp1_video_buffer *done; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&video->irqlock, flags); + + if (list_empty(&video->irqqueue)) { + spin_unlock_irqrestore(&video->irqlock, flags); + return NULL; + } + + done = list_first_entry(&video->irqqueue, + struct vsp1_video_buffer, queue); + list_del(&done->queue); + + if (!list_empty(&video->irqqueue)) + next = list_first_entry(&video->irqqueue, + struct vsp1_video_buffer, queue); + + spin_unlock_irqrestore(&video->irqlock, flags); + + done->buf.v4l2_buf.sequence = video->sequence++; + v4l2_get_timestamp(&done->buf.v4l2_buf.timestamp); + for (i = 0; i < done->buf.num_planes; ++i) + vb2_set_plane_payload(&done->buf, i, done->length[i]); + vb2_buffer_done(&done->buf, VB2_BUF_STATE_DONE); + + return next; +} + +static void vsp1_video_frame_end(struct vsp1_pipeline *pipe, + struct vsp1_video *video) +{ + struct vsp1_video_buffer *buf; + unsigned long flags; + + buf = vsp1_video_complete_buffer(video); + if (buf == NULL) + return; + + spin_lock_irqsave(&pipe->irqlock, flags); + + video->ops->queue(video, buf); + pipe->buffers_ready |= 1 << video->pipe_index; + + spin_unlock_irqrestore(&pipe->irqlock, flags); +} + +void vsp1_pipeline_frame_end(struct vsp1_pipeline *pipe) +{ + unsigned long flags; + unsigned int i; + + if (pipe == NULL) + return; + + /* Complete buffers on all video nodes. */ + for (i = 0; i < pipe->num_inputs; ++i) + vsp1_video_frame_end(pipe, &pipe->inputs[i]->video); + + if (!pipe->lif) + vsp1_video_frame_end(pipe, &pipe->output->video); + + spin_lock_irqsave(&pipe->irqlock, flags); + + /* If a stop has been requested, mark the pipeline as stopped and + * return. + */ + if (pipe->state == VSP1_PIPELINE_STOPPING) { + pipe->state = VSP1_PIPELINE_STOPPED; + wake_up(&pipe->wq); + goto done; + } + + /* Restart the pipeline if ready. */ + if (vsp1_pipeline_ready(pipe)) + vsp1_pipeline_run(pipe); + +done: + spin_unlock_irqrestore(&pipe->irqlock, flags); +} + +/* ----------------------------------------------------------------------------- + * videobuf2 Queue Operations + */ + +static int +vsp1_video_queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct vsp1_video *video = vb2_get_drv_priv(vq); + const struct v4l2_pix_format_mplane *format; + struct v4l2_pix_format_mplane pix_mp; + unsigned int i; + + if (fmt) { + /* Make sure the format is valid and adjust the sizeimage field + * if needed. + */ + if (!vsp1_video_format_adjust(video, &fmt->fmt.pix_mp, &pix_mp)) + return -EINVAL; + + format = &pix_mp; + } else { + format = &video->format; + } + + *nplanes = format->num_planes; + + for (i = 0; i < format->num_planes; ++i) { + sizes[i] = format->plane_fmt[i].sizeimage; + alloc_ctxs[i] = video->alloc_ctx; + } + + return 0; +} + +static int vsp1_video_buffer_prepare(struct vb2_buffer *vb) +{ + struct vsp1_video *video = vb2_get_drv_priv(vb->vb2_queue); + struct vsp1_video_buffer *buf = to_vsp1_video_buffer(vb); + const struct v4l2_pix_format_mplane *format = &video->format; + unsigned int i; + + if (vb->num_planes < format->num_planes) + return -EINVAL; + + buf->video = video; + + for (i = 0; i < vb->num_planes; ++i) { + buf->addr[i] = vb2_dma_contig_plane_dma_addr(vb, i); + buf->length[i] = vb2_plane_size(vb, i); + + if (buf->length[i] < format->plane_fmt[i].sizeimage) + return -EINVAL; + } + + return 0; +} + +static void vsp1_video_buffer_queue(struct vb2_buffer *vb) +{ + struct vsp1_video *video = vb2_get_drv_priv(vb->vb2_queue); + struct vsp1_pipeline *pipe = to_vsp1_pipeline(&video->video.entity); + struct vsp1_video_buffer *buf = to_vsp1_video_buffer(vb); + unsigned long flags; + bool empty; + + spin_lock_irqsave(&video->irqlock, flags); + empty = list_empty(&video->irqqueue); + list_add_tail(&buf->queue, &video->irqqueue); + spin_unlock_irqrestore(&video->irqlock, flags); + + if (!empty) + return; + + spin_lock_irqsave(&pipe->irqlock, flags); + + video->ops->queue(video, buf); + pipe->buffers_ready |= 1 << video->pipe_index; + + if (vb2_is_streaming(&video->queue) && + vsp1_pipeline_ready(pipe)) + vsp1_pipeline_run(pipe); + + spin_unlock_irqrestore(&pipe->irqlock, flags); +} + +static void vsp1_entity_route_setup(struct vsp1_entity *source) +{ + struct vsp1_entity *sink; + + if (source->route == 0) + return; + + sink = container_of(source->sink, struct vsp1_entity, subdev.entity); + vsp1_write(source->vsp1, source->route, sink->id); +} + +static int vsp1_video_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct vsp1_video *video = vb2_get_drv_priv(vq); + struct vsp1_pipeline *pipe = to_vsp1_pipeline(&video->video.entity); + struct vsp1_entity *entity; + unsigned long flags; + int ret; + + mutex_lock(&pipe->lock); + if (pipe->stream_count == pipe->num_video - 1) { + list_for_each_entry(entity, &pipe->entities, list_pipe) { + vsp1_entity_route_setup(entity); + + ret = v4l2_subdev_call(&entity->subdev, video, + s_stream, 1); + if (ret < 0) { + mutex_unlock(&pipe->lock); + return ret; + } + } + } + + pipe->stream_count++; + mutex_unlock(&pipe->lock); + + spin_lock_irqsave(&pipe->irqlock, flags); + if (vsp1_pipeline_ready(pipe)) + vsp1_pipeline_run(pipe); + spin_unlock_irqrestore(&pipe->irqlock, flags); + + return 0; +} + +static int vsp1_video_stop_streaming(struct vb2_queue *vq) +{ + struct vsp1_video *video = vb2_get_drv_priv(vq); + struct vsp1_pipeline *pipe = to_vsp1_pipeline(&video->video.entity); + unsigned long flags; + int ret; + + mutex_lock(&pipe->lock); + if (--pipe->stream_count == 0) { + /* Stop the pipeline. */ + ret = vsp1_pipeline_stop(pipe); + if (ret == -ETIMEDOUT) + dev_err(video->vsp1->dev, "pipeline stop timeout\n"); + } + mutex_unlock(&pipe->lock); + + vsp1_pipeline_cleanup(pipe); + media_entity_pipeline_stop(&video->video.entity); + + /* Remove all buffers from the IRQ queue. */ + spin_lock_irqsave(&video->irqlock, flags); + INIT_LIST_HEAD(&video->irqqueue); + spin_unlock_irqrestore(&video->irqlock, flags); + + return 0; +} + +static struct vb2_ops vsp1_video_queue_qops = { + .queue_setup = vsp1_video_queue_setup, + .buf_prepare = vsp1_video_buffer_prepare, + .buf_queue = vsp1_video_buffer_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = vsp1_video_start_streaming, + .stop_streaming = vsp1_video_stop_streaming, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 ioctls + */ + +static int +vsp1_video_querycap(struct file *file, void *fh, struct v4l2_capability *cap) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_video *video = to_vsp1_video(vfh->vdev); + + cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING + | V4L2_CAP_VIDEO_CAPTURE_MPLANE + | V4L2_CAP_VIDEO_OUTPUT_MPLANE; + + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE + | V4L2_CAP_STREAMING; + else + cap->device_caps = V4L2_CAP_VIDEO_OUTPUT_MPLANE + | V4L2_CAP_STREAMING; + + strlcpy(cap->driver, "vsp1", sizeof(cap->driver)); + strlcpy(cap->card, video->video.name, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", + dev_name(video->vsp1->dev)); + + return 0; +} + +static int +vsp1_video_get_format(struct file *file, void *fh, struct v4l2_format *format) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_video *video = to_vsp1_video(vfh->vdev); + + if (format->type != video->queue.type) + return -EINVAL; + + mutex_lock(&video->lock); + format->fmt.pix_mp = video->format; + mutex_unlock(&video->lock); + + return 0; +} + +static int +vsp1_video_try_format(struct file *file, void *fh, struct v4l2_format *format) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_video *video = to_vsp1_video(vfh->vdev); + + if (format->type != video->queue.type) + return -EINVAL; + + return __vsp1_video_try_format(video, &format->fmt.pix_mp, NULL); +} + +static int +vsp1_video_set_format(struct file *file, void *fh, struct v4l2_format *format) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_video *video = to_vsp1_video(vfh->vdev); + const struct vsp1_format_info *info; + int ret; + + if (format->type != video->queue.type) + return -EINVAL; + + ret = __vsp1_video_try_format(video, &format->fmt.pix_mp, &info); + if (ret < 0) + return ret; + + mutex_lock(&video->lock); + + if (vb2_is_busy(&video->queue)) { + ret = -EBUSY; + goto done; + } + + video->format = format->fmt.pix_mp; + video->fmtinfo = info; + +done: + mutex_unlock(&video->lock); + return ret; +} + +static int +vsp1_video_streamon(struct file *file, void *fh, enum v4l2_buf_type type) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_video *video = to_vsp1_video(vfh->vdev); + struct vsp1_pipeline *pipe; + int ret; + + if (video->queue.owner && video->queue.owner != file->private_data) + return -EBUSY; + + video->sequence = 0; + + /* Start streaming on the pipeline. No link touching an entity in the + * pipeline can be activated or deactivated once streaming is started. + * + * Use the VSP1 pipeline object embedded in the first video object that + * starts streaming. + */ + pipe = video->video.entity.pipe + ? to_vsp1_pipeline(&video->video.entity) : &video->pipe; + + ret = media_entity_pipeline_start(&video->video.entity, &pipe->pipe); + if (ret < 0) + return ret; + + /* Verify that the configured format matches the output of the connected + * subdev. + */ + ret = vsp1_video_verify_format(video); + if (ret < 0) + goto err_stop; + + ret = vsp1_pipeline_init(pipe, video); + if (ret < 0) + goto err_stop; + + /* Start the queue. */ + ret = vb2_streamon(&video->queue, type); + if (ret < 0) + goto err_cleanup; + + return 0; + +err_cleanup: + vsp1_pipeline_cleanup(pipe); +err_stop: + media_entity_pipeline_stop(&video->video.entity); + return ret; +} + +static const struct v4l2_ioctl_ops vsp1_video_ioctl_ops = { + .vidioc_querycap = vsp1_video_querycap, + .vidioc_g_fmt_vid_cap_mplane = vsp1_video_get_format, + .vidioc_s_fmt_vid_cap_mplane = vsp1_video_set_format, + .vidioc_try_fmt_vid_cap_mplane = vsp1_video_try_format, + .vidioc_g_fmt_vid_out_mplane = vsp1_video_get_format, + .vidioc_s_fmt_vid_out_mplane = vsp1_video_set_format, + .vidioc_try_fmt_vid_out_mplane = vsp1_video_try_format, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vsp1_video_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 File Operations + */ + +static int vsp1_video_open(struct file *file) +{ + struct vsp1_video *video = video_drvdata(file); + struct v4l2_fh *vfh; + int ret = 0; + + vfh = kzalloc(sizeof(*vfh), GFP_KERNEL); + if (vfh == NULL) + return -ENOMEM; + + v4l2_fh_init(vfh, &video->video); + v4l2_fh_add(vfh); + + file->private_data = vfh; + + if (!vsp1_device_get(video->vsp1)) { + ret = -EBUSY; + v4l2_fh_del(vfh); + kfree(vfh); + } + + return ret; +} + +static int vsp1_video_release(struct file *file) +{ + struct vsp1_video *video = video_drvdata(file); + struct v4l2_fh *vfh = file->private_data; + + mutex_lock(&video->lock); + if (video->queue.owner == vfh) { + vb2_queue_release(&video->queue); + video->queue.owner = NULL; + } + mutex_unlock(&video->lock); + + vsp1_device_put(video->vsp1); + + v4l2_fh_release(file); + + file->private_data = NULL; + + return 0; +} + +static struct v4l2_file_operations vsp1_video_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = vsp1_video_open, + .release = vsp1_video_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +int vsp1_video_init(struct vsp1_video *video, struct vsp1_entity *rwpf) +{ + const char *direction; + int ret; + + switch (video->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + direction = "output"; + video->pad.flags = MEDIA_PAD_FL_SINK; + break; + + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + direction = "input"; + video->pad.flags = MEDIA_PAD_FL_SOURCE; + video->video.vfl_dir = VFL_DIR_TX; + break; + + default: + return -EINVAL; + } + + video->rwpf = rwpf; + + mutex_init(&video->lock); + spin_lock_init(&video->irqlock); + INIT_LIST_HEAD(&video->irqqueue); + + mutex_init(&video->pipe.lock); + spin_lock_init(&video->pipe.irqlock); + INIT_LIST_HEAD(&video->pipe.entities); + init_waitqueue_head(&video->pipe.wq); + video->pipe.state = VSP1_PIPELINE_STOPPED; + + /* Initialize the media entity... */ + ret = media_entity_init(&video->video.entity, 1, &video->pad, 0); + if (ret < 0) + return ret; + + /* ... and the format ... */ + video->fmtinfo = vsp1_get_format_info(VSP1_VIDEO_DEF_FORMAT); + video->format.pixelformat = video->fmtinfo->fourcc; + video->format.colorspace = V4L2_COLORSPACE_SRGB; + video->format.field = V4L2_FIELD_NONE; + video->format.width = VSP1_VIDEO_DEF_WIDTH; + video->format.height = VSP1_VIDEO_DEF_HEIGHT; + video->format.num_planes = 1; + video->format.plane_fmt[0].bytesperline = + video->format.width * video->fmtinfo->bpp[0] / 8; + video->format.plane_fmt[0].sizeimage = + video->format.plane_fmt[0].bytesperline * video->format.height; + + /* ... and the video node... */ + video->video.v4l2_dev = &video->vsp1->v4l2_dev; + video->video.fops = &vsp1_video_fops; + snprintf(video->video.name, sizeof(video->video.name), "%s %s", + rwpf->subdev.name, direction); + video->video.vfl_type = VFL_TYPE_GRABBER; + video->video.release = video_device_release_empty; + video->video.ioctl_ops = &vsp1_video_ioctl_ops; + + video_set_drvdata(&video->video, video); + + /* ... and the buffers queue... */ + video->alloc_ctx = vb2_dma_contig_init_ctx(video->vsp1->dev); + if (IS_ERR(video->alloc_ctx)) + goto error; + + video->queue.type = video->type; + video->queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + video->queue.lock = &video->lock; + video->queue.drv_priv = video; + video->queue.buf_struct_size = sizeof(struct vsp1_video_buffer); + video->queue.ops = &vsp1_video_queue_qops; + video->queue.mem_ops = &vb2_dma_contig_memops; + video->queue.timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_COPY; + ret = vb2_queue_init(&video->queue); + if (ret < 0) { + dev_err(video->vsp1->dev, "failed to initialize vb2 queue\n"); + goto error; + } + + /* ... and register the video device. */ + video->video.queue = &video->queue; + ret = video_register_device(&video->video, VFL_TYPE_GRABBER, -1); + if (ret < 0) { + dev_err(video->vsp1->dev, "failed to register video device\n"); + goto error; + } + + return 0; + +error: + vb2_dma_contig_cleanup_ctx(video->alloc_ctx); + vsp1_video_cleanup(video); + return ret; +} + +void vsp1_video_cleanup(struct vsp1_video *video) +{ + if (video_is_registered(&video->video)) + video_unregister_device(&video->video); + + vb2_dma_contig_cleanup_ctx(video->alloc_ctx); + media_entity_cleanup(&video->video.entity); +} diff --git a/drivers/media/platform/vsp1/vsp1_video.h b/drivers/media/platform/vsp1/vsp1_video.h new file mode 100644 index 000000000000..d8612a378345 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_video.h @@ -0,0 +1,144 @@ +/* + * vsp1_video.h -- R-Car VSP1 Video Node + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#ifndef __VSP1_VIDEO_H__ +#define __VSP1_VIDEO_H__ + +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/wait.h> + +#include <media/media-entity.h> +#include <media/videobuf2-core.h> + +struct vsp1_video; + +/* + * struct vsp1_format_info - VSP1 video format description + * @mbus: media bus format code + * @fourcc: V4L2 pixel format FCC identifier + * @planes: number of planes + * @bpp: bits per pixel + * @hwfmt: VSP1 hardware format + * @swap_yc: the Y and C components are swapped (Y comes before C) + * @swap_uv: the U and V components are swapped (V comes before U) + * @hsub: horizontal subsampling factor + * @vsub: vertical subsampling factor + */ +struct vsp1_format_info { + u32 fourcc; + unsigned int mbus; + unsigned int hwfmt; + unsigned int swap; + unsigned int planes; + unsigned int bpp[3]; + bool swap_yc; + bool swap_uv; + unsigned int hsub; + unsigned int vsub; +}; + +enum vsp1_pipeline_state { + VSP1_PIPELINE_STOPPED, + VSP1_PIPELINE_RUNNING, + VSP1_PIPELINE_STOPPING, +}; + +/* + * struct vsp1_pipeline - A VSP1 hardware pipeline + * @media: the media pipeline + * @irqlock: protects the pipeline state + * @lock: protects the pipeline use count and stream count + */ +struct vsp1_pipeline { + struct media_pipeline pipe; + + spinlock_t irqlock; + enum vsp1_pipeline_state state; + wait_queue_head_t wq; + + struct mutex lock; + unsigned int use_count; + unsigned int stream_count; + unsigned int buffers_ready; + + unsigned int num_video; + unsigned int num_inputs; + struct vsp1_rwpf *inputs[VPS1_MAX_RPF]; + struct vsp1_rwpf *output; + struct vsp1_entity *lif; + + struct list_head entities; +}; + +static inline struct vsp1_pipeline *to_vsp1_pipeline(struct media_entity *e) +{ + if (likely(e->pipe)) + return container_of(e->pipe, struct vsp1_pipeline, pipe); + else + return NULL; +} + +struct vsp1_video_buffer { + struct vsp1_video *video; + struct vb2_buffer buf; + struct list_head queue; + + dma_addr_t addr[3]; + unsigned int length[3]; +}; + +static inline struct vsp1_video_buffer * +to_vsp1_video_buffer(struct vb2_buffer *vb) +{ + return container_of(vb, struct vsp1_video_buffer, buf); +} + +struct vsp1_video_operations { + void (*queue)(struct vsp1_video *video, struct vsp1_video_buffer *buf); +}; + +struct vsp1_video { + struct vsp1_device *vsp1; + struct vsp1_entity *rwpf; + + const struct vsp1_video_operations *ops; + + struct video_device video; + enum v4l2_buf_type type; + struct media_pad pad; + + struct mutex lock; + struct v4l2_pix_format_mplane format; + const struct vsp1_format_info *fmtinfo; + + struct vsp1_pipeline pipe; + unsigned int pipe_index; + + struct vb2_queue queue; + void *alloc_ctx; + spinlock_t irqlock; + struct list_head irqqueue; + unsigned int sequence; +}; + +static inline struct vsp1_video *to_vsp1_video(struct video_device *vdev) +{ + return container_of(vdev, struct vsp1_video, video); +} + +int vsp1_video_init(struct vsp1_video *video, struct vsp1_entity *rwpf); +void vsp1_video_cleanup(struct vsp1_video *video); + +void vsp1_pipeline_frame_end(struct vsp1_pipeline *pipe); + +#endif /* __VSP1_VIDEO_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_wpf.c b/drivers/media/platform/vsp1/vsp1_wpf.c new file mode 100644 index 000000000000..db4b85ee05fc --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_wpf.c @@ -0,0 +1,233 @@ +/* + * vsp1_wpf.c -- R-Car VSP1 Write Pixel Formatter + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/device.h> + +#include <media/v4l2-subdev.h> + +#include "vsp1.h" +#include "vsp1_rwpf.h" +#include "vsp1_video.h" + +#define WPF_MAX_WIDTH 2048 +#define WPF_MAX_HEIGHT 2048 + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline u32 vsp1_wpf_read(struct vsp1_rwpf *wpf, u32 reg) +{ + return vsp1_read(wpf->entity.vsp1, + reg + wpf->entity.index * VI6_WPF_OFFSET); +} + +static inline void vsp1_wpf_write(struct vsp1_rwpf *wpf, u32 reg, u32 data) +{ + vsp1_write(wpf->entity.vsp1, + reg + wpf->entity.index * VI6_WPF_OFFSET, data); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Core Operations + */ + +static int wpf_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct vsp1_rwpf *wpf = to_rwpf(subdev); + struct vsp1_pipeline *pipe = + to_vsp1_pipeline(&wpf->entity.subdev.entity); + struct vsp1_device *vsp1 = wpf->entity.vsp1; + const struct v4l2_mbus_framefmt *format = + &wpf->entity.formats[RWPF_PAD_SOURCE]; + unsigned int i; + u32 srcrpf = 0; + u32 outfmt = 0; + + if (!enable) { + vsp1_write(vsp1, VI6_WPF_IRQ_ENB(wpf->entity.index), 0); + return 0; + } + + /* Sources */ + for (i = 0; i < pipe->num_inputs; ++i) { + struct vsp1_rwpf *input = pipe->inputs[i]; + + srcrpf |= VI6_WPF_SRCRPF_RPF_ACT_MST(input->entity.index); + } + + vsp1_wpf_write(wpf, VI6_WPF_SRCRPF, srcrpf); + + /* Destination stride. Cropping isn't supported yet. */ + if (!pipe->lif) { + struct v4l2_pix_format_mplane *format = &wpf->video.format; + + vsp1_wpf_write(wpf, VI6_WPF_DSTM_STRIDE_Y, + format->plane_fmt[0].bytesperline); + if (format->num_planes > 1) + vsp1_wpf_write(wpf, VI6_WPF_DSTM_STRIDE_C, + format->plane_fmt[1].bytesperline); + } + + vsp1_wpf_write(wpf, VI6_WPF_HSZCLIP, + format->width << VI6_WPF_SZCLIP_SIZE_SHIFT); + vsp1_wpf_write(wpf, VI6_WPF_VSZCLIP, + format->height << VI6_WPF_SZCLIP_SIZE_SHIFT); + + /* Format */ + if (!pipe->lif) { + const struct vsp1_format_info *fmtinfo = wpf->video.fmtinfo; + + outfmt = fmtinfo->hwfmt << VI6_WPF_OUTFMT_WRFMT_SHIFT; + + if (fmtinfo->swap_yc) + outfmt |= VI6_WPF_OUTFMT_SPYCS; + if (fmtinfo->swap_uv) + outfmt |= VI6_WPF_OUTFMT_SPUVS; + + vsp1_wpf_write(wpf, VI6_WPF_DSWAP, fmtinfo->swap); + } + + if (wpf->entity.formats[RWPF_PAD_SINK].code != + wpf->entity.formats[RWPF_PAD_SOURCE].code) + outfmt |= VI6_WPF_OUTFMT_CSC; + + vsp1_wpf_write(wpf, VI6_WPF_OUTFMT, outfmt); + + vsp1_write(vsp1, VI6_DPR_WPF_FPORCH(wpf->entity.index), + VI6_DPR_WPF_FPORCH_FP_WPFN); + + vsp1_write(vsp1, VI6_WPF_WRBCK_CTRL, 0); + + /* Enable interrupts */ + vsp1_write(vsp1, VI6_WPF_IRQ_STA(wpf->entity.index), 0); + vsp1_write(vsp1, VI6_WPF_IRQ_ENB(wpf->entity.index), + VI6_WFP_IRQ_ENB_FREE); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static struct v4l2_subdev_video_ops wpf_video_ops = { + .s_stream = wpf_s_stream, +}; + +static struct v4l2_subdev_pad_ops wpf_pad_ops = { + .enum_mbus_code = vsp1_rwpf_enum_mbus_code, + .enum_frame_size = vsp1_rwpf_enum_frame_size, + .get_fmt = vsp1_rwpf_get_format, + .set_fmt = vsp1_rwpf_set_format, +}; + +static struct v4l2_subdev_ops wpf_ops = { + .video = &wpf_video_ops, + .pad = &wpf_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Video Device Operations + */ + +static void wpf_vdev_queue(struct vsp1_video *video, + struct vsp1_video_buffer *buf) +{ + struct vsp1_rwpf *wpf = container_of(video, struct vsp1_rwpf, video); + + vsp1_wpf_write(wpf, VI6_WPF_DSTM_ADDR_Y, buf->addr[0]); + if (buf->buf.num_planes > 1) + vsp1_wpf_write(wpf, VI6_WPF_DSTM_ADDR_C0, buf->addr[1]); + if (buf->buf.num_planes > 2) + vsp1_wpf_write(wpf, VI6_WPF_DSTM_ADDR_C1, buf->addr[2]); +} + +static const struct vsp1_video_operations wpf_vdev_ops = { + .queue = wpf_vdev_queue, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_rwpf *vsp1_wpf_create(struct vsp1_device *vsp1, unsigned int index) +{ + struct v4l2_subdev *subdev; + struct vsp1_video *video; + struct vsp1_rwpf *wpf; + unsigned int flags; + int ret; + + wpf = devm_kzalloc(vsp1->dev, sizeof(*wpf), GFP_KERNEL); + if (wpf == NULL) + return ERR_PTR(-ENOMEM); + + wpf->max_width = WPF_MAX_WIDTH; + wpf->max_height = WPF_MAX_HEIGHT; + + wpf->entity.type = VSP1_ENTITY_WPF; + wpf->entity.index = index; + wpf->entity.id = VI6_DPR_NODE_WPF(index); + + ret = vsp1_entity_init(vsp1, &wpf->entity, 2); + if (ret < 0) + return ERR_PTR(ret); + + /* Initialize the V4L2 subdev. */ + subdev = &wpf->entity.subdev; + v4l2_subdev_init(subdev, &wpf_ops); + + subdev->entity.ops = &vsp1_media_ops; + subdev->internal_ops = &vsp1_subdev_internal_ops; + snprintf(subdev->name, sizeof(subdev->name), "%s wpf.%u", + dev_name(vsp1->dev), index); + v4l2_set_subdevdata(subdev, wpf); + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + vsp1_entity_init_formats(subdev, NULL); + + /* Initialize the video device. */ + video = &wpf->video; + + video->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + video->vsp1 = vsp1; + video->ops = &wpf_vdev_ops; + + ret = vsp1_video_init(video, &wpf->entity); + if (ret < 0) + goto error_video; + + /* Connect the video device to the WPF. All connections are immutable + * except for the WPF0 source link if a LIF is present. + */ + flags = MEDIA_LNK_FL_ENABLED; + if (!(vsp1->pdata->features & VSP1_HAS_LIF) || index != 0) + flags |= MEDIA_LNK_FL_IMMUTABLE; + + ret = media_entity_create_link(&wpf->entity.subdev.entity, + RWPF_PAD_SOURCE, + &wpf->video.video.entity, 0, flags); + if (ret < 0) + goto error_link; + + wpf->entity.sink = &wpf->video.video.entity; + + return wpf; + +error_link: + vsp1_video_cleanup(video); +error_video: + media_entity_cleanup(&wpf->entity.subdev.entity); + return ERR_PTR(ret); +} diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index d529ba788f41..39882ddd2594 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -12,6 +12,9 @@ menuconfig RADIO_ADAPTERS if RADIO_ADAPTERS && VIDEO_V4L2 +config RADIO_TEA575X + tristate + config RADIO_SI470X bool "Silicon Labs Si470x FM Radio Receiver support" depends on VIDEO_V4L2 @@ -61,7 +64,8 @@ config USB_DSBR config RADIO_MAXIRADIO tristate "Guillemot MAXI Radio FM 2000 radio" - depends on VIDEO_V4L2 && PCI && SND + depends on VIDEO_V4L2 && PCI + select RADIO_TEA575X ---help--- Choose Y here if you have this radio card. This card may also be found as Gemtek PCI FM. @@ -76,7 +80,8 @@ config RADIO_MAXIRADIO config RADIO_SHARK tristate "Griffin radioSHARK USB radio receiver" - depends on USB && SND + depends on USB + select RADIO_TEA575X ---help--- Choose Y here if you have this radio receiver. @@ -393,7 +398,8 @@ config RADIO_SF16FMI config RADIO_SF16FMR2 tristate "SF16-FMR2/SF16-FMD2 Radio" - depends on ISA && VIDEO_V4L2 && SND + depends on ISA && VIDEO_V4L2 + select RADIO_TEA575X ---help--- Choose Y here if you have one of these FM radio cards. diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index 0dcdb320cfc7..3b645601800d 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_RADIO_TEF6862) += tef6862.o obj-$(CONFIG_RADIO_TIMBERDALE) += radio-timb.o obj-$(CONFIG_RADIO_WL1273) += radio-wl1273.o obj-$(CONFIG_RADIO_WL128X) += wl128x/ +obj-$(CONFIG_RADIO_TEA575X) += tea575x.o shark2-objs := radio-shark2.o radio-tea5777.o diff --git a/drivers/media/radio/radio-aztech.c b/drivers/media/radio/radio-aztech.c index 177bcbd7a7c1..705dd6f9162c 100644 --- a/drivers/media/radio/radio-aztech.c +++ b/drivers/media/radio/radio-aztech.c @@ -26,6 +26,7 @@ #include <media/v4l2-ioctl.h> #include <media/v4l2-ctrls.h> #include "radio-isa.h" +#include "lm7000.h" MODULE_AUTHOR("Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); MODULE_DESCRIPTION("A driver for the Aztech radio card."); @@ -54,18 +55,29 @@ struct aztech { int curvol; }; -static void send_0_byte(struct aztech *az) -{ - udelay(radio_wait_time); - outb_p(2 + az->curvol, az->isa.io); - outb_p(64 + 2 + az->curvol, az->isa.io); -} +/* bit definitions for register read */ +#define AZTECH_BIT_NOT_TUNED (1 << 0) +#define AZTECH_BIT_MONO (1 << 1) +/* bit definitions for register write */ +#define AZTECH_BIT_TUN_CE (1 << 1) +#define AZTECH_BIT_TUN_CLK (1 << 6) +#define AZTECH_BIT_TUN_DATA (1 << 7) +/* bits 0 and 2 are volume control, bits 3..5 are not connected */ -static void send_1_byte(struct aztech *az) +static void aztech_set_pins(void *handle, u8 pins) { - udelay(radio_wait_time); - outb_p(128 + 2 + az->curvol, az->isa.io); - outb_p(128 + 64 + 2 + az->curvol, az->isa.io); + struct radio_isa_card *isa = handle; + struct aztech *az = container_of(isa, struct aztech, isa); + u8 bits = az->curvol; + + if (pins & LM7000_DATA) + bits |= AZTECH_BIT_TUN_DATA; + if (pins & LM7000_CLK) + bits |= AZTECH_BIT_TUN_CLK; + if (pins & LM7000_CE) + bits |= AZTECH_BIT_TUN_CE; + + outb_p(bits, az->isa.io); } static struct radio_isa_card *aztech_alloc(void) @@ -77,58 +89,21 @@ static struct radio_isa_card *aztech_alloc(void) static int aztech_s_frequency(struct radio_isa_card *isa, u32 freq) { - struct aztech *az = container_of(isa, struct aztech, isa); - int i; - - freq += 171200; /* Add 10.7 MHz IF */ - freq /= 800; /* Convert to 50 kHz units */ - - send_0_byte(az); /* 0: LSB of frequency */ - - for (i = 0; i < 13; i++) /* : frequency bits (1-13) */ - if (freq & (1 << i)) - send_1_byte(az); - else - send_0_byte(az); - - send_0_byte(az); /* 14: test bit - always 0 */ - send_0_byte(az); /* 15: test bit - always 0 */ - send_0_byte(az); /* 16: band data 0 - always 0 */ - if (isa->stereo) /* 17: stereo (1 to enable) */ - send_1_byte(az); - else - send_0_byte(az); - - send_1_byte(az); /* 18: band data 1 - unknown */ - send_0_byte(az); /* 19: time base - always 0 */ - send_0_byte(az); /* 20: spacing (0 = 25 kHz) */ - send_1_byte(az); /* 21: spacing (1 = 25 kHz) */ - send_0_byte(az); /* 22: spacing (0 = 25 kHz) */ - send_1_byte(az); /* 23: AM/FM (FM = 1, always) */ - - /* latch frequency */ - - udelay(radio_wait_time); - outb_p(128 + 64 + az->curvol, az->isa.io); + lm7000_set_freq(freq, isa, aztech_set_pins); return 0; } -/* thanks to Michael Dwyer for giving me a dose of clues in - * the signal strength department.. - * - * This card has a stereo bit - bit 0 set = mono, not set = stereo - */ static u32 aztech_g_rxsubchans(struct radio_isa_card *isa) { - if (inb(isa->io) & 1) + if (inb(isa->io) & AZTECH_BIT_MONO) return V4L2_TUNER_SUB_MONO; return V4L2_TUNER_SUB_STEREO; } -static int aztech_s_stereo(struct radio_isa_card *isa, bool stereo) +static u32 aztech_g_signal(struct radio_isa_card *isa) { - return aztech_s_frequency(isa, isa->freq); + return (inb(isa->io) & AZTECH_BIT_NOT_TUNED) ? 0 : 0xffff; } static int aztech_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) @@ -146,8 +121,8 @@ static const struct radio_isa_ops aztech_ops = { .alloc = aztech_alloc, .s_mute_volume = aztech_s_mute_volume, .s_frequency = aztech_s_frequency, - .s_stereo = aztech_s_stereo, .g_rxsubchans = aztech_g_rxsubchans, + .g_signal = aztech_g_signal, }; static const int aztech_ioports[] = { 0x350, 0x358 }; @@ -165,7 +140,7 @@ static struct radio_isa_driver aztech_driver = { .radio_nr_params = radio_nr, .io_ports = aztech_ioports, .num_of_io_ports = ARRAY_SIZE(aztech_ioports), - .region_size = 2, + .region_size = 8, .card = "Aztech Radio", .ops = &aztech_ops, .has_stereo = true, diff --git a/drivers/media/radio/radio-maxiradio.c b/drivers/media/radio/radio-maxiradio.c index bd4d3a7cdadd..5236035f0f2a 100644 --- a/drivers/media/radio/radio-maxiradio.c +++ b/drivers/media/radio/radio-maxiradio.c @@ -42,7 +42,7 @@ #include <linux/videodev2.h> #include <linux/io.h> #include <linux/slab.h> -#include <sound/tea575x-tuner.h> +#include <media/tea575x.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-fh.h> @@ -200,15 +200,4 @@ static struct pci_driver maxiradio_driver = { .remove = maxiradio_remove, }; -static int __init maxiradio_init(void) -{ - return pci_register_driver(&maxiradio_driver); -} - -static void __exit maxiradio_exit(void) -{ - pci_unregister_driver(&maxiradio_driver); -} - -module_init(maxiradio_init); -module_exit(maxiradio_exit); +module_pci_driver(maxiradio_driver); diff --git a/drivers/media/radio/radio-sf16fmr2.c b/drivers/media/radio/radio-sf16fmr2.c index 9c0990457a7c..f1e3714b5f16 100644 --- a/drivers/media/radio/radio-sf16fmr2.c +++ b/drivers/media/radio/radio-sf16fmr2.c @@ -14,7 +14,7 @@ #include <linux/io.h> /* outb, outb_p */ #include <linux/isa.h> #include <linux/pnp.h> -#include <sound/tea575x-tuner.h> +#include <media/tea575x.h> MODULE_AUTHOR("Ondrej Zary"); MODULE_DESCRIPTION("MediaForte SF16-FMR2 and SF16-FMD2 FM radio card driver"); diff --git a/drivers/media/radio/radio-shark.c b/drivers/media/radio/radio-shark.c index 8fa18ab5b725..b91477212413 100644 --- a/drivers/media/radio/radio-shark.c +++ b/drivers/media/radio/radio-shark.c @@ -33,7 +33,7 @@ #include <linux/usb.h> #include <linux/workqueue.h> #include <media/v4l2-device.h> -#include <sound/tea575x-tuner.h> +#include <media/tea575x.h> #if defined(CONFIG_LEDS_CLASS) || \ (defined(CONFIG_LEDS_CLASS_MODULE) && defined(CONFIG_RADIO_SHARK_MODULE)) diff --git a/drivers/media/radio/si470x/radio-si470x-usb.c b/drivers/media/radio/si470x/radio-si470x-usb.c index 62f3edec39bc..d6d4d60261d5 100644 --- a/drivers/media/radio/si470x/radio-si470x-usb.c +++ b/drivers/media/radio/si470x/radio-si470x-usb.c @@ -142,8 +142,6 @@ MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*"); /************************************************************************** * Software/Hardware Versions from Scratch Page **************************************************************************/ -#define RADIO_SW_VERSION_NOT_BOOTLOADABLE 6 -#define RADIO_SW_VERSION 1 #define RADIO_HW_VERSION 1 @@ -682,15 +680,6 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, } dev_info(&intf->dev, "software version %d, hardware version %d\n", radio->software_version, radio->hardware_version); - if (radio->software_version < RADIO_SW_VERSION) { - dev_warn(&intf->dev, - "This driver is known to work with " - "software version %hu,\n", RADIO_SW_VERSION); - dev_warn(&intf->dev, - "but the device has software version %hu.\n", - radio->software_version); - version_warning = 1; - } if (radio->hardware_version < RADIO_HW_VERSION) { dev_warn(&intf->dev, "This driver is known to work with " diff --git a/drivers/media/radio/tea575x.c b/drivers/media/radio/tea575x.c new file mode 100644 index 000000000000..cef06981b7c9 --- /dev/null +++ b/drivers/media/radio/tea575x.c @@ -0,0 +1,584 @@ +/* + * ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips + * + * Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz> + * + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <media/v4l2-device.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-event.h> +#include <media/tea575x.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); +MODULE_DESCRIPTION("Routines for control of TEA5757/5759 Philips AM/FM radio tuner chips"); +MODULE_LICENSE("GPL"); + +/* + * definitions + */ + +#define TEA575X_BIT_SEARCH (1<<24) /* 1 = search action, 0 = tuned */ +#define TEA575X_BIT_UPDOWN (1<<23) /* 0 = search down, 1 = search up */ +#define TEA575X_BIT_MONO (1<<22) /* 0 = stereo, 1 = mono */ +#define TEA575X_BIT_BAND_MASK (3<<20) +#define TEA575X_BIT_BAND_FM (0<<20) +#define TEA575X_BIT_BAND_MW (1<<20) +#define TEA575X_BIT_BAND_LW (2<<20) +#define TEA575X_BIT_BAND_SW (3<<20) +#define TEA575X_BIT_PORT_0 (1<<19) /* user bit */ +#define TEA575X_BIT_PORT_1 (1<<18) /* user bit */ +#define TEA575X_BIT_SEARCH_MASK (3<<16) /* search level */ +#define TEA575X_BIT_SEARCH_5_28 (0<<16) /* FM >5uV, AM >28uV */ +#define TEA575X_BIT_SEARCH_10_40 (1<<16) /* FM >10uV, AM > 40uV */ +#define TEA575X_BIT_SEARCH_30_63 (2<<16) /* FM >30uV, AM > 63uV */ +#define TEA575X_BIT_SEARCH_150_1000 (3<<16) /* FM > 150uV, AM > 1000uV */ +#define TEA575X_BIT_DUMMY (1<<15) /* buffer */ +#define TEA575X_BIT_FREQ_MASK 0x7fff + +enum { BAND_FM, BAND_FM_JAPAN, BAND_AM }; + +static const struct v4l2_frequency_band bands[] = { + { + .type = V4L2_TUNER_RADIO, + .index = 0, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 87500 * 16, + .rangehigh = 108000 * 16, + .modulation = V4L2_BAND_MODULATION_FM, + }, + { + .type = V4L2_TUNER_RADIO, + .index = 0, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 76000 * 16, + .rangehigh = 91000 * 16, + .modulation = V4L2_BAND_MODULATION_FM, + }, + { + .type = V4L2_TUNER_RADIO, + .index = 1, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 530 * 16, + .rangehigh = 1710 * 16, + .modulation = V4L2_BAND_MODULATION_AM, + }, +}; + +/* + * lowlevel part + */ + +static void snd_tea575x_write(struct snd_tea575x *tea, unsigned int val) +{ + u16 l; + u8 data; + + if (tea->ops->write_val) + return tea->ops->write_val(tea, val); + + tea->ops->set_direction(tea, 1); + udelay(16); + + for (l = 25; l > 0; l--) { + data = (val >> 24) & TEA575X_DATA; + val <<= 1; /* shift data */ + tea->ops->set_pins(tea, data | TEA575X_WREN); + udelay(2); + tea->ops->set_pins(tea, data | TEA575X_WREN | TEA575X_CLK); + udelay(2); + tea->ops->set_pins(tea, data | TEA575X_WREN); + udelay(2); + } + + if (!tea->mute) + tea->ops->set_pins(tea, 0); +} + +static u32 snd_tea575x_read(struct snd_tea575x *tea) +{ + u16 l, rdata; + u32 data = 0; + + if (tea->ops->read_val) + return tea->ops->read_val(tea); + + tea->ops->set_direction(tea, 0); + tea->ops->set_pins(tea, 0); + udelay(16); + + for (l = 24; l--;) { + tea->ops->set_pins(tea, TEA575X_CLK); + udelay(2); + if (!l) + tea->tuned = tea->ops->get_pins(tea) & TEA575X_MOST ? 0 : 1; + tea->ops->set_pins(tea, 0); + udelay(2); + data <<= 1; /* shift data */ + rdata = tea->ops->get_pins(tea); + if (!l) + tea->stereo = (rdata & TEA575X_MOST) ? 0 : 1; + if (rdata & TEA575X_DATA) + data++; + udelay(2); + } + + if (tea->mute) + tea->ops->set_pins(tea, TEA575X_WREN); + + return data; +} + +static u32 snd_tea575x_val_to_freq(struct snd_tea575x *tea, u32 val) +{ + u32 freq = val & TEA575X_BIT_FREQ_MASK; + + if (freq == 0) + return freq; + + switch (tea->band) { + case BAND_FM: + /* freq *= 12.5 */ + freq *= 125; + freq /= 10; + /* crystal fixup */ + freq -= TEA575X_FMIF; + break; + case BAND_FM_JAPAN: + /* freq *= 12.5 */ + freq *= 125; + freq /= 10; + /* crystal fixup */ + freq += TEA575X_FMIF; + break; + case BAND_AM: + /* crystal fixup */ + freq -= TEA575X_AMIF; + break; + } + + return clamp(freq * 16, bands[tea->band].rangelow, + bands[tea->band].rangehigh); /* from kHz */ +} + +static u32 snd_tea575x_get_freq(struct snd_tea575x *tea) +{ + return snd_tea575x_val_to_freq(tea, snd_tea575x_read(tea)); +} + +void snd_tea575x_set_freq(struct snd_tea575x *tea) +{ + u32 freq = tea->freq / 16; /* to kHz */ + u32 band = 0; + + switch (tea->band) { + case BAND_FM: + band = TEA575X_BIT_BAND_FM; + /* crystal fixup */ + freq += TEA575X_FMIF; + /* freq /= 12.5 */ + freq *= 10; + freq /= 125; + break; + case BAND_FM_JAPAN: + band = TEA575X_BIT_BAND_FM; + /* crystal fixup */ + freq -= TEA575X_FMIF; + /* freq /= 12.5 */ + freq *= 10; + freq /= 125; + break; + case BAND_AM: + band = TEA575X_BIT_BAND_MW; + /* crystal fixup */ + freq += TEA575X_AMIF; + break; + } + + tea->val &= ~(TEA575X_BIT_FREQ_MASK | TEA575X_BIT_BAND_MASK); + tea->val |= band; + tea->val |= freq & TEA575X_BIT_FREQ_MASK; + snd_tea575x_write(tea, tea->val); + tea->freq = snd_tea575x_val_to_freq(tea, tea->val); +} + +/* + * Linux Video interface + */ + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *v) +{ + struct snd_tea575x *tea = video_drvdata(file); + + strlcpy(v->driver, tea->v4l2_dev->name, sizeof(v->driver)); + strlcpy(v->card, tea->card, sizeof(v->card)); + strlcat(v->card, tea->tea5759 ? " TEA5759" : " TEA5757", sizeof(v->card)); + strlcpy(v->bus_info, tea->bus_info, sizeof(v->bus_info)); + v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + if (!tea->cannot_read_data) + v->device_caps |= V4L2_CAP_HW_FREQ_SEEK; + v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int vidioc_enum_freq_bands(struct file *file, void *priv, + struct v4l2_frequency_band *band) +{ + struct snd_tea575x *tea = video_drvdata(file); + int index; + + if (band->tuner != 0) + return -EINVAL; + + switch (band->index) { + case 0: + if (tea->tea5759) + index = BAND_FM_JAPAN; + else + index = BAND_FM; + break; + case 1: + if (tea->has_am) { + index = BAND_AM; + break; + } + /* Fall through */ + default: + return -EINVAL; + } + + *band = bands[index]; + if (!tea->cannot_read_data) + band->capability |= V4L2_TUNER_CAP_HWSEEK_BOUNDED; + + return 0; +} + +static int vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *v) +{ + struct snd_tea575x *tea = video_drvdata(file); + struct v4l2_frequency_band band_fm = { 0, }; + + if (v->index > 0) + return -EINVAL; + + snd_tea575x_read(tea); + vidioc_enum_freq_bands(file, priv, &band_fm); + + memset(v, 0, sizeof(*v)); + strlcpy(v->name, tea->has_am ? "FM/AM" : "FM", sizeof(v->name)); + v->type = V4L2_TUNER_RADIO; + v->capability = band_fm.capability; + v->rangelow = tea->has_am ? bands[BAND_AM].rangelow : band_fm.rangelow; + v->rangehigh = band_fm.rangehigh; + v->rxsubchans = tea->stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; + v->audmode = (tea->val & TEA575X_BIT_MONO) ? + V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO; + v->signal = tea->tuned ? 0xffff : 0; + return 0; +} + +static int vidioc_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *v) +{ + struct snd_tea575x *tea = video_drvdata(file); + u32 orig_val = tea->val; + + if (v->index) + return -EINVAL; + tea->val &= ~TEA575X_BIT_MONO; + if (v->audmode == V4L2_TUNER_MODE_MONO) + tea->val |= TEA575X_BIT_MONO; + /* Only apply changes if currently tuning FM */ + if (tea->band != BAND_AM && tea->val != orig_val) + snd_tea575x_set_freq(tea); + + return 0; +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct snd_tea575x *tea = video_drvdata(file); + + if (f->tuner != 0) + return -EINVAL; + f->type = V4L2_TUNER_RADIO; + f->frequency = tea->freq; + return 0; +} + +static int vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct snd_tea575x *tea = video_drvdata(file); + + if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + if (tea->has_am && f->frequency < (20000 * 16)) + tea->band = BAND_AM; + else if (tea->tea5759) + tea->band = BAND_FM_JAPAN; + else + tea->band = BAND_FM; + + tea->freq = clamp_t(u32, f->frequency, bands[tea->band].rangelow, + bands[tea->band].rangehigh); + snd_tea575x_set_freq(tea); + return 0; +} + +static int vidioc_s_hw_freq_seek(struct file *file, void *fh, + const struct v4l2_hw_freq_seek *a) +{ + struct snd_tea575x *tea = video_drvdata(file); + unsigned long timeout; + int i, spacing; + + if (tea->cannot_read_data) + return -ENOTTY; + if (a->tuner || a->wrap_around) + return -EINVAL; + + if (file->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + + if (a->rangelow || a->rangehigh) { + for (i = 0; i < ARRAY_SIZE(bands); i++) { + if ((i == BAND_FM && tea->tea5759) || + (i == BAND_FM_JAPAN && !tea->tea5759) || + (i == BAND_AM && !tea->has_am)) + continue; + if (bands[i].rangelow == a->rangelow && + bands[i].rangehigh == a->rangehigh) + break; + } + if (i == ARRAY_SIZE(bands)) + return -EINVAL; /* No matching band found */ + if (i != tea->band) { + tea->band = i; + tea->freq = clamp(tea->freq, bands[i].rangelow, + bands[i].rangehigh); + snd_tea575x_set_freq(tea); + } + } + + spacing = (tea->band == BAND_AM) ? 5 : 50; /* kHz */ + + /* clear the frequency, HW will fill it in */ + tea->val &= ~TEA575X_BIT_FREQ_MASK; + tea->val |= TEA575X_BIT_SEARCH; + if (a->seek_upward) + tea->val |= TEA575X_BIT_UPDOWN; + else + tea->val &= ~TEA575X_BIT_UPDOWN; + snd_tea575x_write(tea, tea->val); + timeout = jiffies + msecs_to_jiffies(10000); + for (;;) { + if (time_after(jiffies, timeout)) + break; + if (schedule_timeout_interruptible(msecs_to_jiffies(10))) { + /* some signal arrived, stop search */ + tea->val &= ~TEA575X_BIT_SEARCH; + snd_tea575x_set_freq(tea); + return -ERESTARTSYS; + } + if (!(snd_tea575x_read(tea) & TEA575X_BIT_SEARCH)) { + u32 freq; + + /* Found a frequency, wait until it can be read */ + for (i = 0; i < 100; i++) { + msleep(10); + freq = snd_tea575x_get_freq(tea); + if (freq) /* available */ + break; + } + if (freq == 0) /* shouldn't happen */ + break; + /* + * if we moved by less than the spacing, or in the + * wrong direction, continue seeking + */ + if (abs(tea->freq - freq) < 16 * spacing || + (a->seek_upward && freq < tea->freq) || + (!a->seek_upward && freq > tea->freq)) { + snd_tea575x_write(tea, tea->val); + continue; + } + tea->freq = freq; + tea->val &= ~TEA575X_BIT_SEARCH; + return 0; + } + } + tea->val &= ~TEA575X_BIT_SEARCH; + snd_tea575x_set_freq(tea); + return -ENODATA; +} + +static int tea575x_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct snd_tea575x *tea = container_of(ctrl->handler, struct snd_tea575x, ctrl_handler); + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + tea->mute = ctrl->val; + snd_tea575x_set_freq(tea); + return 0; + } + + return -EINVAL; +} + +static const struct v4l2_file_operations tea575x_fops = { + .unlocked_ioctl = video_ioctl2, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .poll = v4l2_ctrl_poll, +}; + +static const struct v4l2_ioctl_ops tea575x_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek, + .vidioc_enum_freq_bands = vidioc_enum_freq_bands, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct video_device tea575x_radio = { + .ioctl_ops = &tea575x_ioctl_ops, + .release = video_device_release_empty, +}; + +static const struct v4l2_ctrl_ops tea575x_ctrl_ops = { + .s_ctrl = tea575x_s_ctrl, +}; + + +int snd_tea575x_hw_init(struct snd_tea575x *tea) +{ + tea->mute = true; + + /* Not all devices can or know how to read the data back. + Such devices can set cannot_read_data to true. */ + if (!tea->cannot_read_data) { + snd_tea575x_write(tea, 0x55AA); + if (snd_tea575x_read(tea) != 0x55AA) + return -ENODEV; + } + + tea->val = TEA575X_BIT_BAND_FM | TEA575X_BIT_SEARCH_5_28; + tea->freq = 90500 * 16; /* 90.5Mhz default */ + snd_tea575x_set_freq(tea); + + return 0; +} +EXPORT_SYMBOL(snd_tea575x_hw_init); + +int snd_tea575x_init(struct snd_tea575x *tea, struct module *owner) +{ + int retval = snd_tea575x_hw_init(tea); + + if (retval) + return retval; + + tea->vd = tea575x_radio; + video_set_drvdata(&tea->vd, tea); + mutex_init(&tea->mutex); + strlcpy(tea->vd.name, tea->v4l2_dev->name, sizeof(tea->vd.name)); + tea->vd.lock = &tea->mutex; + tea->vd.v4l2_dev = tea->v4l2_dev; + tea->fops = tea575x_fops; + tea->fops.owner = owner; + tea->vd.fops = &tea->fops; + set_bit(V4L2_FL_USE_FH_PRIO, &tea->vd.flags); + /* disable hw_freq_seek if we can't use it */ + if (tea->cannot_read_data) + v4l2_disable_ioctl(&tea->vd, VIDIOC_S_HW_FREQ_SEEK); + + if (!tea->cannot_mute) { + tea->vd.ctrl_handler = &tea->ctrl_handler; + v4l2_ctrl_handler_init(&tea->ctrl_handler, 1); + v4l2_ctrl_new_std(&tea->ctrl_handler, &tea575x_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); + retval = tea->ctrl_handler.error; + if (retval) { + v4l2_err(tea->v4l2_dev, "can't initialize controls\n"); + v4l2_ctrl_handler_free(&tea->ctrl_handler); + return retval; + } + + if (tea->ext_init) { + retval = tea->ext_init(tea); + if (retval) { + v4l2_ctrl_handler_free(&tea->ctrl_handler); + return retval; + } + } + + v4l2_ctrl_handler_setup(&tea->ctrl_handler); + } + + retval = video_register_device(&tea->vd, VFL_TYPE_RADIO, tea->radio_nr); + if (retval) { + v4l2_err(tea->v4l2_dev, "can't register video device!\n"); + v4l2_ctrl_handler_free(tea->vd.ctrl_handler); + return retval; + } + + return 0; +} + +void snd_tea575x_exit(struct snd_tea575x *tea) +{ + video_unregister_device(&tea->vd); + v4l2_ctrl_handler_free(tea->vd.ctrl_handler); +} + +static int __init alsa_tea575x_module_init(void) +{ + return 0; +} + +static void __exit alsa_tea575x_module_exit(void) +{ +} + +module_init(alsa_tea575x_module_init) +module_exit(alsa_tea575x_module_exit) + +EXPORT_SYMBOL(snd_tea575x_init); +EXPORT_SYMBOL(snd_tea575x_exit); +EXPORT_SYMBOL(snd_tea575x_set_freq); diff --git a/drivers/media/rc/Kconfig b/drivers/media/rc/Kconfig index 5a79c333d45e..11e84bcc23a1 100644 --- a/drivers/media/rc/Kconfig +++ b/drivers/media/rc/Kconfig @@ -223,6 +223,8 @@ config IR_REDRAT3 tristate "RedRat3 IR Transceiver" depends on USB_ARCH_HAS_HCD depends on RC_CORE + select NEW_LEDS + select LEDS_CLASS select USB ---help--- Say Y here if you want to use a RedRat3 Infrared Transceiver. @@ -248,7 +250,6 @@ config IR_WINBOND_CIR depends on RC_CORE select NEW_LEDS select LEDS_CLASS - select LEDS_TRIGGERS select BITREVERSE ---help--- Say Y here if you want to use the IR remote functionality found diff --git a/drivers/media/rc/ene_ir.c b/drivers/media/rc/ene_ir.c index ed184f68c17c..c1444f84717d 100644 --- a/drivers/media/rc/ene_ir.c +++ b/drivers/media/rc/ene_ir.c @@ -476,7 +476,7 @@ select_timeout: } /* Enable the device for receive */ -static void ene_rx_enable(struct ene_device *dev) +static void ene_rx_enable_hw(struct ene_device *dev) { u8 reg_value; @@ -504,11 +504,17 @@ static void ene_rx_enable(struct ene_device *dev) /* enter idle mode */ ir_raw_event_set_idle(dev->rdev, true); +} + +/* Enable the device for receive - wrapper to track the state*/ +static void ene_rx_enable(struct ene_device *dev) +{ + ene_rx_enable_hw(dev); dev->rx_enabled = true; } /* Disable the device receiver */ -static void ene_rx_disable(struct ene_device *dev) +static void ene_rx_disable_hw(struct ene_device *dev) { /* disable inputs */ ene_rx_enable_cir_engine(dev, false); @@ -516,8 +522,13 @@ static void ene_rx_disable(struct ene_device *dev) /* disable hardware IRQ and firmware flag */ ene_clear_reg_mask(dev, ENE_FW1, ENE_FW1_ENABLE | ENE_FW1_IRQ); - ir_raw_event_set_idle(dev->rdev, true); +} + +/* Disable the device receiver - wrapper to track the state */ +static void ene_rx_disable(struct ene_device *dev) +{ + ene_rx_disable_hw(dev); dev->rx_enabled = false; } @@ -1022,6 +1033,8 @@ static int ene_probe(struct pnp_dev *pnp_dev, const struct pnp_device_id *id) spin_lock_init(&dev->hw_lock); dev->hw_io = pnp_port_start(pnp_dev, 0); + dev->irq = pnp_irq(pnp_dev, 0); + pnp_set_drvdata(pnp_dev, dev); dev->pnp_dev = pnp_dev; @@ -1085,7 +1098,6 @@ static int ene_probe(struct pnp_dev *pnp_dev, const struct pnp_device_id *id) goto exit_unregister_device; } - dev->irq = pnp_irq(pnp_dev, 0); if (request_irq(dev->irq, ene_isr, IRQF_SHARED, ENE_DRIVER_NAME, (void *)dev)) { goto exit_release_hw_io; @@ -1123,9 +1135,8 @@ static void ene_remove(struct pnp_dev *pnp_dev) } /* enable wake on IR (wakes on specific button on original remote) */ -static void ene_enable_wake(struct ene_device *dev, int enable) +static void ene_enable_wake(struct ene_device *dev, bool enable) { - enable = enable && device_may_wakeup(&dev->pnp_dev->dev); dbg("wake on IR %s", enable ? "enabled" : "disabled"); ene_set_clear_reg_mask(dev, ENE_FW1, ENE_FW1_WAKE, enable); } @@ -1134,9 +1145,12 @@ static void ene_enable_wake(struct ene_device *dev, int enable) static int ene_suspend(struct pnp_dev *pnp_dev, pm_message_t state) { struct ene_device *dev = pnp_get_drvdata(pnp_dev); - ene_enable_wake(dev, true); + bool wake = device_may_wakeup(&dev->pnp_dev->dev); + + if (!wake && dev->rx_enabled) + ene_rx_disable_hw(dev); - /* TODO: add support for wake pattern */ + ene_enable_wake(dev, wake); return 0; } diff --git a/drivers/media/rc/ene_ir.h b/drivers/media/rc/ene_ir.h index 6f978e85db8c..a7911e3b9bc0 100644 --- a/drivers/media/rc/ene_ir.h +++ b/drivers/media/rc/ene_ir.h @@ -185,7 +185,7 @@ #define __dbg(level, format, ...) \ do { \ if (debug >= level) \ - pr_debug(format "\n", ## __VA_ARGS__); \ + pr_info(format "\n", ## __VA_ARGS__); \ } while (0) #define dbg(format, ...) __dbg(1, format, ## __VA_ARGS__) diff --git a/drivers/media/rc/iguanair.c b/drivers/media/rc/iguanair.c index a4ab2e6b3f82..19632b1c2190 100644 --- a/drivers/media/rc/iguanair.c +++ b/drivers/media/rc/iguanair.c @@ -364,8 +364,8 @@ static int iguanair_tx(struct rc_dev *dev, unsigned *txbuf, unsigned count) periods = DIV_ROUND_CLOSEST(txbuf[i] * ir->carrier, 1000000); bytes = DIV_ROUND_UP(periods, 127); if (size + bytes > ir->bufsize) { - count = i; - break; + rc = -EINVAL; + goto out; } while (periods > 127) { ir->packet->payload[size++] = 127 | space; diff --git a/drivers/media/rc/ir-lirc-codec.c b/drivers/media/rc/ir-lirc-codec.c index e4561264e124..ed2c8a1ed8ca 100644 --- a/drivers/media/rc/ir-lirc-codec.c +++ b/drivers/media/rc/ir-lirc-codec.c @@ -140,11 +140,20 @@ static ssize_t ir_lirc_transmit_ir(struct file *file, const char __user *buf, goto out; } + for (i = 0; i < count; i++) { + if (txbuf[i] > IR_MAX_DURATION / 1000 - duration || !txbuf[i]) { + ret = -EINVAL; + goto out; + } + + duration += txbuf[i]; + } + ret = dev->tx_ir(dev, txbuf, count); if (ret < 0) goto out; - for (i = 0; i < ret; i++) + for (duration = i = 0; i < ret; i++) duration += txbuf[i]; ret *= sizeof(unsigned int); @@ -375,6 +384,7 @@ static int ir_lirc_register(struct rc_dev *dev) drv->code_length = sizeof(struct ir_raw_event) * 8; drv->fops = &lirc_fops; drv->dev = &dev->dev; + drv->rdev = dev; drv->owner = THIS_MODULE; drv->minor = lirc_register_driver(drv); diff --git a/drivers/media/rc/lirc_dev.c b/drivers/media/rc/lirc_dev.c index 8dc057b273f2..dc5cbffcd5a2 100644 --- a/drivers/media/rc/lirc_dev.c +++ b/drivers/media/rc/lirc_dev.c @@ -35,6 +35,7 @@ #include <linux/device.h> #include <linux/cdev.h> +#include <media/rc-core.h> #include <media/lirc.h> #include <media/lirc_dev.h> @@ -467,6 +468,12 @@ int lirc_dev_fop_open(struct inode *inode, struct file *file) goto error; } + if (ir->d.rdev) { + retval = rc_open(ir->d.rdev); + if (retval) + goto error; + } + cdev = ir->cdev; if (try_module_get(cdev->owner)) { ir->open++; @@ -511,6 +518,9 @@ int lirc_dev_fop_close(struct inode *inode, struct file *file) WARN_ON(mutex_lock_killable(&lirc_dev_lock)); + if (ir->d.rdev) + rc_close(ir->d.rdev); + ir->open--; if (ir->attached) { ir->d.set_use_dec(ir->d.data); diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c index 1cf382a0b277..46da365c9c84 100644 --- a/drivers/media/rc/rc-main.c +++ b/drivers/media/rc/rc-main.c @@ -16,6 +16,7 @@ #include <linux/spinlock.h> #include <linux/delay.h> #include <linux/input.h> +#include <linux/leds.h> #include <linux/slab.h> #include <linux/device.h> #include <linux/module.h> @@ -31,6 +32,7 @@ /* Used to keep track of known keymaps */ static LIST_HEAD(rc_map_list); static DEFINE_SPINLOCK(rc_map_lock); +static struct led_trigger *led_feedback; static struct rc_map_list *seek_rc_map(const char *name) { @@ -535,6 +537,7 @@ static void ir_do_keyup(struct rc_dev *dev, bool sync) IR_dprintk(1, "keyup key 0x%04x\n", dev->last_keycode); input_report_key(dev->input_dev, dev->last_keycode, 0); + led_trigger_event(led_feedback, LED_OFF); if (sync) input_sync(dev->input_dev); dev->keypressed = false; @@ -648,6 +651,7 @@ static void ir_do_keydown(struct rc_dev *dev, int scancode, input_report_key(dev->input_dev, keycode, 1); } + led_trigger_event(led_feedback, LED_FULL); input_sync(dev->input_dev); } @@ -699,19 +703,50 @@ void rc_keydown_notimeout(struct rc_dev *dev, int scancode, u8 toggle) } EXPORT_SYMBOL_GPL(rc_keydown_notimeout); +int rc_open(struct rc_dev *rdev) +{ + int rval = 0; + + if (!rdev) + return -EINVAL; + + mutex_lock(&rdev->lock); + if (!rdev->users++ && rdev->open != NULL) + rval = rdev->open(rdev); + + if (rval) + rdev->users--; + + mutex_unlock(&rdev->lock); + + return rval; +} +EXPORT_SYMBOL_GPL(rc_open); + static int ir_open(struct input_dev *idev) { struct rc_dev *rdev = input_get_drvdata(idev); - return rdev->open(rdev); + return rc_open(rdev); +} + +void rc_close(struct rc_dev *rdev) +{ + if (rdev) { + mutex_lock(&rdev->lock); + + if (!--rdev->users && rdev->close != NULL) + rdev->close(rdev); + + mutex_unlock(&rdev->lock); + } } +EXPORT_SYMBOL_GPL(rc_close); static void ir_close(struct input_dev *idev) { struct rc_dev *rdev = input_get_drvdata(idev); - - if (rdev) - rdev->close(rdev); + rc_close(rdev); } /* class for /sys/class/rc */ @@ -1076,7 +1111,14 @@ int rc_register_device(struct rc_dev *dev) memcpy(&dev->input_dev->id, &dev->input_id, sizeof(dev->input_id)); dev->input_dev->phys = dev->input_phys; dev->input_dev->name = dev->input_name; + + /* input_register_device can call ir_open, so unlock mutex here */ + mutex_unlock(&dev->lock); + rc = input_register_device(dev->input_dev); + + mutex_lock(&dev->lock); + if (rc) goto out_table; @@ -1184,6 +1226,7 @@ static int __init rc_core_init(void) return rc; } + led_trigger_register_simple("rc-feedback", &led_feedback); rc_map_register(&empty_map); return 0; @@ -1192,6 +1235,7 @@ static int __init rc_core_init(void) static void __exit rc_core_exit(void) { class_unregister(&rc_class); + led_trigger_unregister_simple(led_feedback); rc_map_unregister(&empty_map); } diff --git a/drivers/media/rc/redrat3.c b/drivers/media/rc/redrat3.c index 12167a6b5472..094484fac94c 100644 --- a/drivers/media/rc/redrat3.c +++ b/drivers/media/rc/redrat3.c @@ -47,6 +47,7 @@ #include <asm/unaligned.h> #include <linux/device.h> +#include <linux/leds.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/usb.h> @@ -186,6 +187,13 @@ struct redrat3_dev { struct rc_dev *rc; struct device *dev; + /* led control */ + struct led_classdev led; + atomic_t flash; + struct usb_ctrlrequest flash_control; + struct urb *flash_urb; + u8 flash_in_buf; + /* save off the usb device pointer */ struct usb_device *udev; @@ -206,8 +214,6 @@ struct redrat3_dev { struct timer_list rx_timeout; u32 hw_timeout; - /* is the detector enabled*/ - bool det_enabled; /* Is the device currently transmitting?*/ bool transmitting; @@ -472,40 +478,19 @@ static int redrat3_enable_detector(struct redrat3_dev *rr3) return -EIO; } - rr3->det_enabled = true; redrat3_issue_async(rr3); return 0; } -/* Disables the rr3 long range detector */ -static void redrat3_disable_detector(struct redrat3_dev *rr3) -{ - struct device *dev = rr3->dev; - u8 ret; - - rr3_ftr(dev, "Entering %s\n", __func__); - - ret = redrat3_send_cmd(RR3_RC_DET_DISABLE, rr3); - if (ret != 0) - dev_err(dev, "%s: failure!\n", __func__); - - ret = redrat3_send_cmd(RR3_RC_DET_STATUS, rr3); - if (ret != 0) - dev_warn(dev, "%s: detector status: %d, should be 0\n", - __func__, ret); - - rr3->det_enabled = false; -} - static inline void redrat3_delete(struct redrat3_dev *rr3, struct usb_device *udev) { rr3_ftr(rr3->dev, "%s cleaning up\n", __func__); usb_kill_urb(rr3->read_urb); - + usb_kill_urb(rr3->flash_urb); usb_free_urb(rr3->read_urb); - + usb_free_urb(rr3->flash_urb); usb_free_coherent(udev, le16_to_cpu(rr3->ep_in->wMaxPacketSize), rr3->bulk_in_buf, rr3->dma_in); @@ -686,7 +671,8 @@ static int redrat3_get_ir_data(struct redrat3_dev *rr3, unsigned len) goto out; } - if (rr3->bytes_read < be16_to_cpu(rr3->irdata.header.length)) + if (rr3->bytes_read < be16_to_cpu(rr3->irdata.header.length) + + sizeof(struct redrat3_header)) /* we're still accumulating data */ return 0; @@ -785,10 +771,10 @@ static int redrat3_transmit_ir(struct rc_dev *rcdev, unsigned *txbuf, return -EAGAIN; } - count = min_t(unsigned, count, RR3_MAX_SIG_SIZE - RR3_TX_TRAILER_LEN); + if (count > RR3_MAX_SIG_SIZE - RR3_TX_TRAILER_LEN) + return -EINVAL; /* rr3 will disable rc detector on transmit */ - rr3->det_enabled = false; rr3->transmitting = true; sample_lens = kzalloc(sizeof(int) * RR3_DRIVER_MAXLENS, GFP_KERNEL); @@ -825,8 +811,8 @@ static int redrat3_transmit_ir(struct rc_dev *rcdev, unsigned *txbuf, &irdata->lens[curlencheck]); curlencheck++; } else { - count = i - 1; - break; + ret = -EINVAL; + goto out; } } irdata->sigdata[i] = lencheck; @@ -868,11 +854,48 @@ out: rr3->transmitting = false; /* rr3 re-enables rc detector because it was enabled before */ - rr3->det_enabled = true; return ret; } +static void redrat3_brightness_set(struct led_classdev *led_dev, enum + led_brightness brightness) +{ + struct redrat3_dev *rr3 = container_of(led_dev, struct redrat3_dev, + led); + + if (brightness != LED_OFF && atomic_cmpxchg(&rr3->flash, 0, 1) == 0) { + int ret = usb_submit_urb(rr3->flash_urb, GFP_ATOMIC); + if (ret != 0) { + dev_dbg(rr3->dev, "%s: unexpected ret of %d\n", + __func__, ret); + atomic_set(&rr3->flash, 0); + } + } +} + +static void redrat3_led_complete(struct urb *urb) +{ + struct redrat3_dev *rr3 = urb->context; + + switch (urb->status) { + case 0: + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + usb_unlink_urb(urb); + return; + case -EPIPE: + default: + dev_dbg(rr3->dev, "Error: urb status = %d\n", urb->status); + break; + } + + rr3->led.brightness = LED_OFF; + atomic_dec(&rr3->flash); +} + static struct rc_dev *redrat3_init_rc_dev(struct redrat3_dev *rr3) { struct device *dev = rr3->dev; @@ -1016,10 +1039,35 @@ static int redrat3_dev_probe(struct usb_interface *intf, /* default.. will get overridden by any sends with a freq defined */ rr3->carrier = 38000; + /* led control */ + rr3->led.name = "redrat3:red:feedback"; + rr3->led.default_trigger = "rc-feedback"; + rr3->led.brightness_set = redrat3_brightness_set; + retval = led_classdev_register(&intf->dev, &rr3->led); + if (retval) + goto error; + + atomic_set(&rr3->flash, 0); + rr3->flash_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!rr3->flash_urb) { + retval = -ENOMEM; + goto led_free_error; + } + + /* setup packet is 'c0 b9 0000 0000 0001' */ + rr3->flash_control.bRequestType = 0xc0; + rr3->flash_control.bRequest = RR3_BLINK_LED; + rr3->flash_control.wLength = cpu_to_le16(1); + + usb_fill_control_urb(rr3->flash_urb, udev, usb_rcvctrlpipe(udev, 0), + (unsigned char *)&rr3->flash_control, + &rr3->flash_in_buf, sizeof(rr3->flash_in_buf), + redrat3_led_complete, rr3); + rr3->rc = redrat3_init_rc_dev(rr3); if (!rr3->rc) { retval = -ENOMEM; - goto error; + goto led_free_error; } setup_timer(&rr3->rx_timeout, redrat3_rx_timeout, (unsigned long)rr3); @@ -1029,6 +1077,8 @@ static int redrat3_dev_probe(struct usb_interface *intf, rr3_ftr(dev, "Exiting %s\n", __func__); return 0; +led_free_error: + led_classdev_unregister(&rr3->led); error: redrat3_delete(rr3, rr3->udev); @@ -1048,10 +1098,9 @@ static void redrat3_dev_disconnect(struct usb_interface *intf) if (!rr3) return; - redrat3_disable_detector(rr3); - usb_set_intfdata(intf, NULL); rc_unregister_device(rr3->rc); + led_classdev_unregister(&rr3->led); del_timer_sync(&rr3->rx_timeout); redrat3_delete(rr3, udev); @@ -1062,7 +1111,9 @@ static int redrat3_dev_suspend(struct usb_interface *intf, pm_message_t message) { struct redrat3_dev *rr3 = usb_get_intfdata(intf); rr3_ftr(rr3->dev, "suspend\n"); + led_classdev_suspend(&rr3->led); usb_kill_urb(rr3->read_urb); + usb_kill_urb(rr3->flash_urb); return 0; } @@ -1072,6 +1123,7 @@ static int redrat3_dev_resume(struct usb_interface *intf) rr3_ftr(rr3->dev, "resume\n"); if (usb_submit_urb(rr3->read_urb, GFP_ATOMIC)) return -EIO; + led_classdev_resume(&rr3->led); return 0; } diff --git a/drivers/media/rc/ttusbir.c b/drivers/media/rc/ttusbir.c index 891762d167ed..d8de2056a4f6 100644 --- a/drivers/media/rc/ttusbir.c +++ b/drivers/media/rc/ttusbir.c @@ -302,6 +302,7 @@ static int ttusbir_probe(struct usb_interface *intf, ttusbir_bulk_complete, tt); tt->led.name = "ttusbir:green:power"; + tt->led.default_trigger = "rc-feedback"; tt->led.brightness_set = ttusbir_brightness_set; tt->led.brightness_get = ttusbir_brightness_get; tt->is_led_on = tt->led_on = true; diff --git a/drivers/media/rc/winbond-cir.c b/drivers/media/rc/winbond-cir.c index 87af2d3ba601..98bd4960c75e 100644 --- a/drivers/media/rc/winbond-cir.c +++ b/drivers/media/rc/winbond-cir.c @@ -213,13 +213,11 @@ struct wbcir_data { /* RX state */ enum wbcir_rxstate rxstate; - struct led_trigger *rxtrigger; int carrier_report_enabled; u32 pulse_duration; /* TX state */ enum wbcir_txstate txstate; - struct led_trigger *txtrigger; u32 txlen; u32 txoff; u32 *txbuf; @@ -366,14 +364,11 @@ wbcir_idle_rx(struct rc_dev *dev, bool idle) { struct wbcir_data *data = dev->priv; - if (!idle && data->rxstate == WBCIR_RXSTATE_INACTIVE) { + if (!idle && data->rxstate == WBCIR_RXSTATE_INACTIVE) data->rxstate = WBCIR_RXSTATE_ACTIVE; - led_trigger_event(data->rxtrigger, LED_FULL); - } if (idle && data->rxstate != WBCIR_RXSTATE_INACTIVE) { data->rxstate = WBCIR_RXSTATE_INACTIVE; - led_trigger_event(data->rxtrigger, LED_OFF); if (data->carrier_report_enabled) wbcir_carrier_report(data); @@ -425,7 +420,6 @@ wbcir_irq_tx(struct wbcir_data *data) case WBCIR_TXSTATE_INACTIVE: /* TX FIFO empty */ space = 16; - led_trigger_event(data->txtrigger, LED_FULL); break; case WBCIR_TXSTATE_ACTIVE: /* TX FIFO low (3 bytes or less) */ @@ -464,7 +458,6 @@ wbcir_irq_tx(struct wbcir_data *data) /* Clear TX underrun bit */ outb(WBCIR_TX_UNDERRUN, data->sbase + WBCIR_REG_SP3_ASCR); wbcir_set_irqmask(data, WBCIR_IRQ_RX | WBCIR_IRQ_ERR); - led_trigger_event(data->txtrigger, LED_OFF); kfree(data->txbuf); data->txbuf = NULL; data->txstate = WBCIR_TXSTATE_INACTIVE; @@ -878,15 +871,13 @@ finish: */ wbcir_set_irqmask(data, WBCIR_IRQ_NONE); disable_irq(data->irq); - - /* Disable LED */ - led_trigger_event(data->rxtrigger, LED_OFF); - led_trigger_event(data->txtrigger, LED_OFF); } static int wbcir_suspend(struct pnp_dev *device, pm_message_t state) { + struct wbcir_data *data = pnp_get_drvdata(device); + led_classdev_suspend(&data->led); wbcir_shutdown(device); return 0; } @@ -1015,6 +1006,7 @@ wbcir_resume(struct pnp_dev *device) wbcir_init_hw(data); enable_irq(data->irq); + led_classdev_resume(&data->led); return 0; } @@ -1058,25 +1050,13 @@ wbcir_probe(struct pnp_dev *device, const struct pnp_device_id *dev_id) "(w: 0x%lX, e: 0x%lX, s: 0x%lX, i: %u)\n", data->wbase, data->ebase, data->sbase, data->irq); - led_trigger_register_simple("cir-tx", &data->txtrigger); - if (!data->txtrigger) { - err = -ENOMEM; - goto exit_free_data; - } - - led_trigger_register_simple("cir-rx", &data->rxtrigger); - if (!data->rxtrigger) { - err = -ENOMEM; - goto exit_unregister_txtrigger; - } - data->led.name = "cir::activity"; - data->led.default_trigger = "cir-rx"; + data->led.default_trigger = "rc-feedback"; data->led.brightness_set = wbcir_led_brightness_set; data->led.brightness_get = wbcir_led_brightness_get; err = led_classdev_register(&device->dev, &data->led); if (err) - goto exit_unregister_rxtrigger; + goto exit_free_data; data->dev = rc_allocate_device(); if (!data->dev) { @@ -1156,10 +1136,6 @@ exit_free_rc: rc_free_device(data->dev); exit_unregister_led: led_classdev_unregister(&data->led); -exit_unregister_rxtrigger: - led_trigger_unregister_simple(data->rxtrigger); -exit_unregister_txtrigger: - led_trigger_unregister_simple(data->txtrigger); exit_free_data: kfree(data); pnp_set_drvdata(device, NULL); @@ -1187,8 +1163,6 @@ wbcir_remove(struct pnp_dev *device) rc_unregister_device(data->dev); - led_trigger_unregister_simple(data->rxtrigger); - led_trigger_unregister_simple(data->txtrigger); led_classdev_unregister(&data->led); /* This is ok since &data->led isn't actually used */ diff --git a/drivers/media/tuners/e4000.c b/drivers/media/tuners/e4000.c index 1b33ed368abe..ad9309da4a91 100644 --- a/drivers/media/tuners/e4000.c +++ b/drivers/media/tuners/e4000.c @@ -41,8 +41,9 @@ static int e4000_wr_regs(struct e4000_priv *priv, u8 reg, u8 *val, int len) if (ret == 1) { ret = 0; } else { - dev_warn(&priv->i2c->dev, "%s: i2c wr failed=%d reg=%02x " \ - "len=%d\n", KBUILD_MODNAME, ret, reg, len); + dev_warn(&priv->i2c->dev, + "%s: i2c wr failed=%d reg=%02x len=%d\n", + KBUILD_MODNAME, ret, reg, len); ret = -EREMOTEIO; } return ret; @@ -72,8 +73,9 @@ static int e4000_rd_regs(struct e4000_priv *priv, u8 reg, u8 *val, int len) memcpy(val, buf, len); ret = 0; } else { - dev_warn(&priv->i2c->dev, "%s: i2c rd failed=%d reg=%02x " \ - "len=%d\n", KBUILD_MODNAME, ret, reg, len); + dev_warn(&priv->i2c->dev, + "%s: i2c rd failed=%d reg=%02x len=%d\n", + KBUILD_MODNAME, ret, reg, len); ret = -EREMOTEIO; } @@ -140,14 +142,12 @@ static int e4000_init(struct dvb_frontend *fe) if (ret < 0) goto err; - /* - * TODO: Implement DC offset control correctly. - * DC offsets has quite much effect for received signal quality in case - * of direct conversion tuners (Zero-IF). Surely we will now lose few - * decimals or even decibels from SNR... - */ /* DC offset control */ - ret = e4000_wr_reg(priv, 0x2d, 0x0c); + ret = e4000_wr_reg(priv, 0x2d, 0x1f); + if (ret < 0) + goto err; + + ret = e4000_wr_regs(priv, 0x70, "\x01\x01", 2); if (ret < 0) goto err; @@ -203,12 +203,13 @@ static int e4000_set_params(struct dvb_frontend *fe) struct e4000_priv *priv = fe->tuner_priv; struct dtv_frontend_properties *c = &fe->dtv_property_cache; int ret, i, sigma_delta; - unsigned int f_VCO; - u8 buf[5]; + unsigned int f_vco; + u8 buf[5], i_data[4], q_data[4]; - dev_dbg(&priv->i2c->dev, "%s: delivery_system=%d frequency=%d " \ - "bandwidth_hz=%d\n", __func__, - c->delivery_system, c->frequency, c->bandwidth_hz); + dev_dbg(&priv->i2c->dev, + "%s: delivery_system=%d frequency=%d bandwidth_hz=%d\n", + __func__, c->delivery_system, c->frequency, + c->bandwidth_hz); if (fe->ops.i2c_gate_ctrl) fe->ops.i2c_gate_ctrl(fe, 1); @@ -228,19 +229,19 @@ static int e4000_set_params(struct dvb_frontend *fe) goto err; /* - * Note: Currently f_VCO overflows when c->frequency is 1 073 741 824 Hz + * Note: Currently f_vco overflows when c->frequency is 1 073 741 824 Hz * or more. */ - f_VCO = c->frequency * e4000_pll_lut[i].mul; - sigma_delta = 0x10000UL * (f_VCO % priv->cfg->clock) / priv->cfg->clock; - buf[0] = f_VCO / priv->cfg->clock; + f_vco = c->frequency * e4000_pll_lut[i].mul; + sigma_delta = 0x10000UL * (f_vco % priv->cfg->clock) / priv->cfg->clock; + buf[0] = f_vco / priv->cfg->clock; buf[1] = (sigma_delta >> 0) & 0xff; buf[2] = (sigma_delta >> 8) & 0xff; buf[3] = 0x00; buf[4] = e4000_pll_lut[i].div; - dev_dbg(&priv->i2c->dev, "%s: f_VCO=%u pll div=%d sigma_delta=%04x\n", - __func__, f_VCO, buf[0], sigma_delta); + dev_dbg(&priv->i2c->dev, "%s: f_vco=%u pll div=%d sigma_delta=%04x\n", + __func__, f_vco, buf[0], sigma_delta); ret = e4000_wr_regs(priv, 0x09, buf, 5); if (ret < 0) @@ -292,6 +293,43 @@ static int e4000_set_params(struct dvb_frontend *fe) if (ret < 0) goto err; + /* DC offset */ + for (i = 0; i < 4; i++) { + if (i == 0) + ret = e4000_wr_regs(priv, 0x15, "\x00\x7e\x24", 3); + else if (i == 1) + ret = e4000_wr_regs(priv, 0x15, "\x00\x7f", 2); + else if (i == 2) + ret = e4000_wr_regs(priv, 0x15, "\x01", 1); + else + ret = e4000_wr_regs(priv, 0x16, "\x7e", 1); + + if (ret < 0) + goto err; + + ret = e4000_wr_reg(priv, 0x29, 0x01); + if (ret < 0) + goto err; + + ret = e4000_rd_regs(priv, 0x2a, buf, 3); + if (ret < 0) + goto err; + + i_data[i] = (((buf[2] >> 0) & 0x3) << 6) | (buf[0] & 0x3f); + q_data[i] = (((buf[2] >> 4) & 0x3) << 6) | (buf[1] & 0x3f); + } + + swap(q_data[2], q_data[3]); + swap(i_data[2], i_data[3]); + + ret = e4000_wr_regs(priv, 0x50, q_data, 4); + if (ret < 0) + goto err; + + ret = e4000_wr_regs(priv, 0x60, i_data, 4); + if (ret < 0) + goto err; + /* gain control auto */ ret = e4000_wr_reg(priv, 0x1a, 0x17); if (ret < 0) diff --git a/drivers/media/tuners/e4000.h b/drivers/media/tuners/e4000.h index 3783a0bdf855..25ee7c07abff 100644 --- a/drivers/media/tuners/e4000.h +++ b/drivers/media/tuners/e4000.h @@ -44,7 +44,7 @@ extern struct dvb_frontend *e4000_attach(struct dvb_frontend *fe, static inline struct dvb_frontend *e4000_attach(struct dvb_frontend *fe, struct i2c_adapter *i2c, const struct e4000_config *cfg) { - pr_warn("%s: driver disabled by Kconfig\n", __func__); + dev_warn(&i2c->dev, "%s: driver disabled by Kconfig\n", __func__); return NULL; } #endif diff --git a/drivers/media/usb/cx231xx/cx231xx-cards.c b/drivers/media/usb/cx231xx/cx231xx-cards.c index 27948e1798eb..a384f80f595e 100644 --- a/drivers/media/usb/cx231xx/cx231xx-cards.c +++ b/drivers/media/usb/cx231xx/cx231xx-cards.c @@ -443,6 +443,44 @@ struct cx231xx_board cx231xx_boards[] = { .gpio = NULL, } }, }, + [CX231XX_BOARD_KWORLD_UB445_USB_HYBRID] = { + .name = "Kworld UB445 USB Hybrid", + .tuner_type = TUNER_NXP_TDA18271, + .tuner_addr = 0x60, + .decoder = CX231XX_AVDECODER, + .output_mode = OUT_MODE_VIP11, + .demod_xfer_mode = 0, + .ctl_pin_status_mask = 0xFFFFFFC4, + .agc_analog_digital_select_gpio = 0x11, /* According with PV cxPolaris.inf file */ + .tuner_sif_gpio = -1, + .tuner_scl_gpio = -1, + .tuner_sda_gpio = -1, + .gpio_pin_status_mask = 0x4001000, + .tuner_i2c_master = 2, + .demod_i2c_master = 1, + .ir_i2c_master = 2, + .has_dvb = 1, + .demod_addr = 0x10, + .norm = V4L2_STD_NTSC_M, + .input = {{ + .type = CX231XX_VMUX_TELEVISION, + .vmux = CX231XX_VIN_3_1, + .amux = CX231XX_AMUX_VIDEO, + .gpio = NULL, + }, { + .type = CX231XX_VMUX_COMPOSITE1, + .vmux = CX231XX_VIN_2_1, + .amux = CX231XX_AMUX_LINE_IN, + .gpio = NULL, + }, { + .type = CX231XX_VMUX_SVIDEO, + .vmux = CX231XX_VIN_1_1 | + (CX231XX_VIN_1_2 << 8) | + CX25840_SVIDEO_ON, + .amux = CX231XX_AMUX_LINE_IN, + .gpio = NULL, + } }, + }, [CX231XX_BOARD_PV_PLAYTV_USB_HYBRID] = { .name = "Pixelview PlayTV USB Hybrid", .tuner_type = TUNER_NXP_TDA18271, @@ -703,6 +741,8 @@ struct usb_device_id cx231xx_id_table[] = { .driver_info = CX231XX_BOARD_PV_XCAPTURE_USB}, {USB_DEVICE(0x1b80, 0xe424), .driver_info = CX231XX_BOARD_KWORLD_UB430_USB_HYBRID}, + {USB_DEVICE(0x1b80, 0xe421), + .driver_info = CX231XX_BOARD_KWORLD_UB445_USB_HYBRID}, {USB_DEVICE(0x1f4d, 0x0237), .driver_info = CX231XX_BOARD_ICONBIT_U100}, {USB_DEVICE(0x0fd9, 0x0037), diff --git a/drivers/media/usb/cx231xx/cx231xx-dvb.c b/drivers/media/usb/cx231xx/cx231xx-dvb.c index 14e26106fd72..4504bc6a700b 100644 --- a/drivers/media/usb/cx231xx/cx231xx-dvb.c +++ b/drivers/media/usb/cx231xx/cx231xx-dvb.c @@ -657,6 +657,7 @@ static int dvb_init(struct cx231xx *dev) } break; case CX231XX_BOARD_CNXT_RDU_253S: + case CX231XX_BOARD_KWORLD_UB445_USB_HYBRID: dev->dvb->frontend = dvb_attach(s5h1411_attach, &tda18271_s5h1411_config, diff --git a/drivers/media/usb/cx231xx/cx231xx.h b/drivers/media/usb/cx231xx/cx231xx.h index e812119ea7a8..babca7fb85e2 100644 --- a/drivers/media/usb/cx231xx/cx231xx.h +++ b/drivers/media/usb/cx231xx/cx231xx.h @@ -72,6 +72,7 @@ #define CX231XX_BOARD_HAUPPAUGE_USB2_FM_NTSC 15 #define CX231XX_BOARD_ELGATO_VIDEO_CAPTURE_V2 16 #define CX231XX_BOARD_OTG102 17 +#define CX231XX_BOARD_KWORLD_UB445_USB_HYBRID 18 /* Limits minimum and default number of buffers */ #define CX231XX_MIN_BUF 4 diff --git a/drivers/media/usb/dvb-usb-v2/Kconfig b/drivers/media/usb/dvb-usb-v2/Kconfig index a3c8ecf22078..2059d0c86ad3 100644 --- a/drivers/media/usb/dvb-usb-v2/Kconfig +++ b/drivers/media/usb/dvb-usb-v2/Kconfig @@ -1,6 +1,6 @@ config DVB_USB_V2 tristate "Support for various USB DVB devices v2" - depends on DVB_CORE && USB && I2C + depends on DVB_CORE && USB && I2C && (RC_CORE || RC_CORE=n) help By enabling this you will be able to choose the various supported USB1.1 and USB2.0 DVB devices. diff --git a/drivers/media/usb/dvb-usb-v2/dvb_usb.h b/drivers/media/usb/dvb-usb-v2/dvb_usb.h index 399916bd588f..124b4baa7e97 100644 --- a/drivers/media/usb/dvb-usb-v2/dvb_usb.h +++ b/drivers/media/usb/dvb-usb-v2/dvb_usb.h @@ -352,9 +352,7 @@ struct dvb_usb_adapter { * @rc_map: name of rc codes table * @rc_polling_active: set when RC polling is active * @udev: pointer to the device's struct usb_device - * @intf: pointer to the device's usb interface * @rc: remote controller configuration - * @probe_work: work to defer .probe() * @powered: indicated whether the device is power or not * @usb_mutex: mutex for usb control messages * @i2c_mutex: mutex for i2c-transfers @@ -370,10 +368,7 @@ struct dvb_usb_device { const char *rc_map; bool rc_polling_active; struct usb_device *udev; - struct usb_interface *intf; struct dvb_usb_rc rc; - struct work_struct probe_work; - pid_t work_pid; int powered; /* locking */ diff --git a/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c b/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c index 19f6737d9817..8a054d66e708 100644 --- a/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c +++ b/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c @@ -833,20 +833,44 @@ err: return ret; } -/* - * udev, which is used for the firmware downloading, requires we cannot - * block during module_init(). module_init() calls USB probe() which - * is this routine. Due to that we delay actual operation using workqueue - * and return always success here. - */ -static void dvb_usbv2_init_work(struct work_struct *work) +int dvb_usbv2_probe(struct usb_interface *intf, + const struct usb_device_id *id) { int ret; - struct dvb_usb_device *d = - container_of(work, struct dvb_usb_device, probe_work); + struct dvb_usb_device *d; + struct usb_device *udev = interface_to_usbdev(intf); + struct dvb_usb_driver_info *driver_info = + (struct dvb_usb_driver_info *) id->driver_info; + + dev_dbg(&udev->dev, "%s: bInterfaceNumber=%d\n", __func__, + intf->cur_altsetting->desc.bInterfaceNumber); + + if (!id->driver_info) { + dev_err(&udev->dev, "%s: driver_info failed\n", KBUILD_MODNAME); + ret = -ENODEV; + goto err; + } + + d = kzalloc(sizeof(struct dvb_usb_device), GFP_KERNEL); + if (!d) { + dev_err(&udev->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME); + ret = -ENOMEM; + goto err; + } - d->work_pid = current->pid; - dev_dbg(&d->udev->dev, "%s: work_pid=%d\n", __func__, d->work_pid); + d->name = driver_info->name; + d->rc_map = driver_info->rc_map; + d->udev = udev; + d->props = driver_info->props; + + if (intf->cur_altsetting->desc.bInterfaceNumber != + d->props->bInterfaceNumber) { + ret = -ENODEV; + goto err_free_all; + } + + mutex_init(&d->usb_mutex); + mutex_init(&d->i2c_mutex); if (d->props->size_of_priv) { d->priv = kzalloc(d->props->size_of_priv, GFP_KERNEL); @@ -854,7 +878,7 @@ static void dvb_usbv2_init_work(struct work_struct *work) dev_err(&d->udev->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME); ret = -ENOMEM; - goto err_usb_driver_release_interface; + goto err_free_all; } } @@ -884,20 +908,12 @@ static void dvb_usbv2_init_work(struct work_struct *work) * device. As 'new' device is warm we should * never go here again. */ - return; + goto exit; } else { - /* - * Unexpected error. We must unregister driver - * manually from the device, because device is - * already register by returning from probe() - * with success. usb_driver_release_interface() - * finally calls disconnect() in order to free - * resources. - */ - goto err_usb_driver_release_interface; + goto err_free_all; } } else { - goto err_usb_driver_release_interface; + goto err_free_all; } } @@ -906,73 +922,17 @@ static void dvb_usbv2_init_work(struct work_struct *work) ret = dvb_usbv2_init(d); if (ret < 0) - goto err_usb_driver_release_interface; + goto err_free_all; dev_info(&d->udev->dev, "%s: '%s' successfully initialized and connected\n", KBUILD_MODNAME, d->name); - - return; -err_usb_driver_release_interface: - dev_info(&d->udev->dev, "%s: '%s' error while loading driver (%d)\n", - KBUILD_MODNAME, d->name, ret); - usb_driver_release_interface(to_usb_driver(d->intf->dev.driver), - d->intf); - dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); - return; -} - -int dvb_usbv2_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - int ret; - struct dvb_usb_device *d; - struct usb_device *udev = interface_to_usbdev(intf); - struct dvb_usb_driver_info *driver_info = - (struct dvb_usb_driver_info *) id->driver_info; - - dev_dbg(&udev->dev, "%s: bInterfaceNumber=%d\n", __func__, - intf->cur_altsetting->desc.bInterfaceNumber); - - if (!id->driver_info) { - dev_err(&udev->dev, "%s: driver_info failed\n", KBUILD_MODNAME); - ret = -ENODEV; - goto err; - } - - d = kzalloc(sizeof(struct dvb_usb_device), GFP_KERNEL); - if (!d) { - dev_err(&udev->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME); - ret = -ENOMEM; - goto err; - } - - d->name = driver_info->name; - d->rc_map = driver_info->rc_map; - d->udev = udev; - d->intf = intf; - d->props = driver_info->props; - - if (d->intf->cur_altsetting->desc.bInterfaceNumber != - d->props->bInterfaceNumber) { - ret = -ENODEV; - goto err_kfree; - } - - mutex_init(&d->usb_mutex); - mutex_init(&d->i2c_mutex); - INIT_WORK(&d->probe_work, dvb_usbv2_init_work); +exit: usb_set_intfdata(intf, d); - ret = schedule_work(&d->probe_work); - if (ret < 0) { - dev_err(&d->udev->dev, "%s: schedule_work() failed\n", - KBUILD_MODNAME); - goto err_kfree; - } return 0; -err_kfree: - kfree(d); +err_free_all: + dvb_usbv2_exit(d); err: dev_dbg(&udev->dev, "%s: failed=%d\n", __func__, ret); return ret; @@ -984,12 +944,8 @@ void dvb_usbv2_disconnect(struct usb_interface *intf) struct dvb_usb_device *d = usb_get_intfdata(intf); const char *name = d->name; struct device dev = d->udev->dev; - dev_dbg(&d->udev->dev, "%s: pid=%d work_pid=%d\n", __func__, - current->pid, d->work_pid); - - /* ensure initialization work is finished until release resources */ - if (d->work_pid != current->pid) - cancel_work_sync(&d->probe_work); + dev_dbg(&d->udev->dev, "%s: bInterfaceNumber=%d\n", __func__, + intf->cur_altsetting->desc.bInterfaceNumber); if (d->props->exit) d->props->exit(d); diff --git a/drivers/media/usb/dvb-usb-v2/lmedm04.c b/drivers/media/usb/dvb-usb-v2/lmedm04.c index b3fd0ffa3c3f..f674dc024d06 100644 --- a/drivers/media/usb/dvb-usb-v2/lmedm04.c +++ b/drivers/media/usb/dvb-usb-v2/lmedm04.c @@ -1225,7 +1225,7 @@ static int lme2510_identify_state(struct dvb_usb_device *d, const char **name) usb_reset_configuration(d->udev); usb_set_interface(d->udev, - d->intf->cur_altsetting->desc.bInterfaceNumber, 1); + d->props->bInterfaceNumber, 1); st->dvb_usb_lme2510_firmware = dvb_usb_lme2510_firmware; diff --git a/drivers/media/usb/dvb-usb/dib0700_devices.c b/drivers/media/usb/dvb-usb/dib0700_devices.c index f08136052f9c..829323e42ca0 100644 --- a/drivers/media/usb/dvb-usb/dib0700_devices.c +++ b/drivers/media/usb/dvb-usb/dib0700_devices.c @@ -3589,6 +3589,8 @@ struct usb_device_id dib0700_usb_id_table[] = { { USB_DEVICE(USB_VID_DIBCOM, USB_PID_DIBCOM_TFE7790P) }, { USB_DEVICE(USB_VID_DIBCOM, USB_PID_DIBCOM_TFE8096P) }, /* 80 */{ USB_DEVICE(USB_VID_ELGATO, USB_PID_ELGATO_EYETV_DTT_2) }, + { USB_DEVICE(USB_VID_PCTV, USB_PID_PCTV_2002E) }, + { USB_DEVICE(USB_VID_PCTV, USB_PID_PCTV_2002E_SE) }, { 0 } /* Terminating entry */ }; MODULE_DEVICE_TABLE(usb, dib0700_usb_id_table); @@ -3993,12 +3995,20 @@ struct dvb_usb_device_properties dib0700_devices[] = { } }, - .num_device_descs = 1, + .num_device_descs = 3, .devices = { { "Hauppauge Nova-TD Stick (52009)", { &dib0700_usb_id_table[35], NULL }, { NULL }, }, + { "PCTV 2002e", + { &dib0700_usb_id_table[81], NULL }, + { NULL }, + }, + { "PCTV 2002e SE", + { &dib0700_usb_id_table[82], NULL }, + { NULL }, + }, }, .rc.core = { diff --git a/drivers/media/usb/dvb-usb/m920x.c b/drivers/media/usb/dvb-usb/m920x.c index c2b635d6a17a..0306cb778df4 100644 --- a/drivers/media/usb/dvb-usb/m920x.c +++ b/drivers/media/usb/dvb-usb/m920x.c @@ -1212,7 +1212,7 @@ static struct dvb_usb_device_properties vp7049_properties = { .rc_interval = 150, .rc_codes = RC_MAP_TWINHAN_VP1027_DVBS, .rc_query = m920x_rc_core_query, - .allowed_protos = RC_TYPE_UNKNOWN, + .allowed_protos = RC_BIT_UNKNOWN, }, .size_of_priv = sizeof(struct m920x_state), diff --git a/drivers/media/usb/em28xx/em28xx-video.c b/drivers/media/usb/em28xx/em28xx-video.c index 1a577ed8ea0c..9d103344f34a 100644 --- a/drivers/media/usb/em28xx/em28xx-video.c +++ b/drivers/media/usb/em28xx/em28xx-video.c @@ -1008,6 +1008,7 @@ static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, else f->fmt.pix.field = dev->interlaced ? V4L2_FIELD_INTERLACED : V4L2_FIELD_TOP; + f->fmt.pix.priv = 0; return 0; } diff --git a/drivers/media/usb/gspca/Kconfig b/drivers/media/usb/gspca/Kconfig index 6345f9331e7f..4f0c6d566c85 100644 --- a/drivers/media/usb/gspca/Kconfig +++ b/drivers/media/usb/gspca/Kconfig @@ -338,6 +338,15 @@ config USB_GSPCA_STK014 To compile this driver as a module, choose M here: the module will be called gspca_stk014. +config USB_GSPCA_STK1135 + tristate "Syntek STK1135 USB Camera Driver" + depends on VIDEO_V4L2 && USB_GSPCA + help + Say Y here if you want support for cameras based on the STK1135 chip. + + To compile this driver as a module, choose M here: the + module will be called gspca_stk1135. + config USB_GSPCA_STV0680 tristate "STV0680 USB Camera Driver" depends on VIDEO_V4L2 && USB_GSPCA diff --git a/drivers/media/usb/gspca/Makefile b/drivers/media/usb/gspca/Makefile index c901da0bd657..5855131ab8b6 100644 --- a/drivers/media/usb/gspca/Makefile +++ b/drivers/media/usb/gspca/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_USB_GSPCA_SQ905C) += gspca_sq905c.o obj-$(CONFIG_USB_GSPCA_SQ930X) += gspca_sq930x.o obj-$(CONFIG_USB_GSPCA_SUNPLUS) += gspca_sunplus.o obj-$(CONFIG_USB_GSPCA_STK014) += gspca_stk014.o +obj-$(CONFIG_USB_GSPCA_STK1135) += gspca_stk1135.o obj-$(CONFIG_USB_GSPCA_STV0680) += gspca_stv0680.o obj-$(CONFIG_USB_GSPCA_T613) += gspca_t613.o obj-$(CONFIG_USB_GSPCA_TOPRO) += gspca_topro.o @@ -78,6 +79,7 @@ gspca_sq905-objs := sq905.o gspca_sq905c-objs := sq905c.o gspca_sq930x-objs := sq930x.o gspca_stk014-objs := stk014.o +gspca_stk1135-objs := stk1135.o gspca_stv0680-objs := stv0680.o gspca_sunplus-objs := sunplus.o gspca_t613-objs := t613.o diff --git a/drivers/media/usb/gspca/gspca.c b/drivers/media/usb/gspca/gspca.c index b7ae8721b847..048507b27bb2 100644 --- a/drivers/media/usb/gspca/gspca.c +++ b/drivers/media/usb/gspca/gspca.c @@ -1266,6 +1266,7 @@ static void gspca_release(struct v4l2_device *v4l2_device) static int dev_open(struct file *file) { struct gspca_dev *gspca_dev = video_drvdata(file); + int ret; PDEBUG(D_STREAM, "[%s] open", current->comm); @@ -1273,7 +1274,10 @@ static int dev_open(struct file *file) if (!try_module_get(gspca_dev->module)) return -ENODEV; - return v4l2_fh_open(file); + ret = v4l2_fh_open(file); + if (ret) + module_put(gspca_dev->module); + return ret; } static int dev_close(struct file *file) diff --git a/drivers/media/usb/gspca/ov519.c b/drivers/media/usb/gspca/ov519.c index a3958ee86816..8937d79fd176 100644 --- a/drivers/media/usb/gspca/ov519.c +++ b/drivers/media/usb/gspca/ov519.c @@ -75,6 +75,8 @@ struct sd { struct v4l2_ctrl *brightness; }; + u8 revision; + u8 packet_nr; char bridge; @@ -3080,8 +3082,8 @@ static void ov518_configure(struct gspca_dev *gspca_dev) }; /* First 5 bits of custom ID reg are a revision ID on OV518 */ - PDEBUG(D_PROBE, "Device revision %d", - 0x1f & reg_r(sd, R51x_SYS_CUST_ID)); + sd->revision = reg_r(sd, R51x_SYS_CUST_ID) & 0x1f; + PDEBUG(D_PROBE, "Device revision %d", sd->revision); write_regvals(sd, init_518, ARRAY_SIZE(init_518)); @@ -3657,7 +3659,11 @@ static void ov518_mode_init_regs(struct sd *sd) reg_w(sd, 0x2f, 0x80); /******** Set the framerate ********/ - sd->clockdiv = 1; + if (sd->bridge == BRIDGE_OV518PLUS && sd->revision == 0 && + sd->sensor == SEN_OV7620AE) + sd->clockdiv = 0; + else + sd->clockdiv = 1; /* Mode independent, but framerate dependent, regs */ /* 0x51: Clock divider; Only works on some cams which use 2 crystals */ @@ -3668,12 +3674,24 @@ static void ov518_mode_init_regs(struct sd *sd) if (sd->bridge == BRIDGE_OV518PLUS) { switch (sd->sensor) { case SEN_OV7620AE: - if (sd->gspca_dev.width == 320) { - reg_w(sd, 0x20, 0x00); - reg_w(sd, 0x21, 0x19); - } else { + /* + * HdG: 640x480 needs special handling on device + * revision 2, we check for device revison > 0 to + * avoid regressions, as we don't know the correct + * thing todo for revision 1. + * + * Also this likely means we don't need to + * differentiate between the OV7620 and OV7620AE, + * earlier testing hitting this same problem likely + * happened to be with revision < 2 cams using an + * OV7620 and revision 2 cams using an OV7620AE. + */ + if (sd->revision > 0 && sd->gspca_dev.width == 640) { reg_w(sd, 0x20, 0x60); reg_w(sd, 0x21, 0x1f); + } else { + reg_w(sd, 0x20, 0x00); + reg_w(sd, 0x21, 0x19); } break; case SEN_OV7620: diff --git a/drivers/media/usb/gspca/ov534.c b/drivers/media/usb/gspca/ov534.c index 2e28c81a03ab..03a33c46ca2c 100644 --- a/drivers/media/usb/gspca/ov534.c +++ b/drivers/media/usb/gspca/ov534.c @@ -1305,8 +1305,7 @@ static int sd_init(struct gspca_dev *gspca_dev) ov534_set_led(gspca_dev, 1); sccb_w_array(gspca_dev, sensor_init[sd->sensor].val, sensor_init[sd->sensor].len); - if (sd->sensor == SENSOR_OV767x) - sd_start(gspca_dev); + sd_stopN(gspca_dev); /* set_frame_rate(gspca_dev); */ diff --git a/drivers/media/usb/gspca/stk1135.c b/drivers/media/usb/gspca/stk1135.c new file mode 100644 index 000000000000..585868835ace --- /dev/null +++ b/drivers/media/usb/gspca/stk1135.c @@ -0,0 +1,685 @@ +/* + * Syntek STK1135 subdriver + * + * Copyright (c) 2013 Ondrej Zary + * + * Based on Syntekdriver (stk11xx) by Nicolas VIVIEN: + * http://syntekdriver.sourceforge.net + * + * 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 + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define MODULE_NAME "stk1135" + +#include "gspca.h" +#include "stk1135.h" + +MODULE_AUTHOR("Ondrej Zary"); +MODULE_DESCRIPTION("Syntek STK1135 USB Camera Driver"); +MODULE_LICENSE("GPL"); + + +/* specific webcam descriptor */ +struct sd { + struct gspca_dev gspca_dev; /* !! must be the first item */ + + u8 pkt_seq; + u8 sensor_page; + + bool flip_status; + u8 flip_debounce; + + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; +}; + +static const struct v4l2_pix_format stk1135_modes[] = { + {160, 120, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE, + .bytesperline = 160, + .sizeimage = 160 * 120, + .colorspace = V4L2_COLORSPACE_SRGB}, + {176, 144, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE, + .bytesperline = 176, + .sizeimage = 176 * 144, + .colorspace = V4L2_COLORSPACE_SRGB}, + {320, 240, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE, + .bytesperline = 320, + .sizeimage = 320 * 240, + .colorspace = V4L2_COLORSPACE_SRGB}, + {352, 288, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE, + .bytesperline = 352, + .sizeimage = 352 * 288, + .colorspace = V4L2_COLORSPACE_SRGB}, + {640, 480, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE, + .bytesperline = 640, + .sizeimage = 640 * 480, + .colorspace = V4L2_COLORSPACE_SRGB}, + {720, 576, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE, + .bytesperline = 720, + .sizeimage = 720 * 576, + .colorspace = V4L2_COLORSPACE_SRGB}, + {800, 600, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE, + .bytesperline = 800, + .sizeimage = 800 * 600, + .colorspace = V4L2_COLORSPACE_SRGB}, + {1024, 768, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE, + .bytesperline = 1024, + .sizeimage = 1024 * 768, + .colorspace = V4L2_COLORSPACE_SRGB}, + {1280, 1024, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE, + .bytesperline = 1280, + .sizeimage = 1280 * 1024, + .colorspace = V4L2_COLORSPACE_SRGB}, +}; + +/* -- read a register -- */ +static u8 reg_r(struct gspca_dev *gspca_dev, u16 index) +{ + struct usb_device *dev = gspca_dev->dev; + int ret; + + if (gspca_dev->usb_err < 0) + return 0; + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + 0x00, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x00, + index, + gspca_dev->usb_buf, 1, + 500); + + PDEBUG(D_USBI, "reg_r 0x%x=0x%02x", index, gspca_dev->usb_buf[0]); + if (ret < 0) { + pr_err("reg_r 0x%x err %d\n", index, ret); + gspca_dev->usb_err = ret; + return 0; + } + + return gspca_dev->usb_buf[0]; +} + +/* -- write a register -- */ +static void reg_w(struct gspca_dev *gspca_dev, u16 index, u8 val) +{ + int ret; + struct usb_device *dev = gspca_dev->dev; + + if (gspca_dev->usb_err < 0) + return; + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + 0x01, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + val, + index, + NULL, + 0, + 500); + PDEBUG(D_USBO, "reg_w 0x%x:=0x%02x", index, val); + if (ret < 0) { + pr_err("reg_w 0x%x err %d\n", index, ret); + gspca_dev->usb_err = ret; + } +} + +static void reg_w_mask(struct gspca_dev *gspca_dev, u16 index, u8 val, u8 mask) +{ + val = (reg_r(gspca_dev, index) & ~mask) | (val & mask); + reg_w(gspca_dev, index, val); +} + +/* this function is called at probe time */ +static int sd_config(struct gspca_dev *gspca_dev, + const struct usb_device_id *id) +{ + gspca_dev->cam.cam_mode = stk1135_modes; + gspca_dev->cam.nmodes = ARRAY_SIZE(stk1135_modes); + return 0; +} + +static int stk1135_serial_wait_ready(struct gspca_dev *gspca_dev) +{ + int i = 0; + u8 val; + + do { + val = reg_r(gspca_dev, STK1135_REG_SICTL + 1); + if (i++ > 500) { /* maximum retry count */ + pr_err("serial bus timeout: status=0x%02x\n", val); + return -1; + } + /* repeat if BUSY or WRITE/READ not finished */ + } while ((val & 0x10) || !(val & 0x05)); + + return 0; +} + +static u8 sensor_read_8(struct gspca_dev *gspca_dev, u8 addr) +{ + reg_w(gspca_dev, STK1135_REG_SBUSR, addr); + /* begin read */ + reg_w(gspca_dev, STK1135_REG_SICTL, 0x20); + /* wait until finished */ + if (stk1135_serial_wait_ready(gspca_dev)) { + pr_err("Sensor read failed\n"); + return 0; + } + + return reg_r(gspca_dev, STK1135_REG_SBUSR + 1); +} + +static u16 sensor_read_16(struct gspca_dev *gspca_dev, u8 addr) +{ + return (sensor_read_8(gspca_dev, addr) << 8) | + sensor_read_8(gspca_dev, 0xf1); +} + +static void sensor_write_8(struct gspca_dev *gspca_dev, u8 addr, u8 data) +{ + /* load address and data registers */ + reg_w(gspca_dev, STK1135_REG_SBUSW, addr); + reg_w(gspca_dev, STK1135_REG_SBUSW + 1, data); + /* begin write */ + reg_w(gspca_dev, STK1135_REG_SICTL, 0x01); + /* wait until finished */ + if (stk1135_serial_wait_ready(gspca_dev)) { + pr_err("Sensor write failed\n"); + return; + } +} + +static void sensor_write_16(struct gspca_dev *gspca_dev, u8 addr, u16 data) +{ + sensor_write_8(gspca_dev, addr, data >> 8); + sensor_write_8(gspca_dev, 0xf1, data & 0xff); +} + +static void sensor_set_page(struct gspca_dev *gspca_dev, u8 page) +{ + struct sd *sd = (struct sd *) gspca_dev; + + if (page != sd->sensor_page) { + sensor_write_16(gspca_dev, 0xf0, page); + sd->sensor_page = page; + } +} + +static u16 sensor_read(struct gspca_dev *gspca_dev, u16 reg) +{ + sensor_set_page(gspca_dev, reg >> 8); + return sensor_read_16(gspca_dev, reg & 0xff); +} + +static void sensor_write(struct gspca_dev *gspca_dev, u16 reg, u16 val) +{ + sensor_set_page(gspca_dev, reg >> 8); + sensor_write_16(gspca_dev, reg & 0xff, val); +} + +static void sensor_write_mask(struct gspca_dev *gspca_dev, + u16 reg, u16 val, u16 mask) +{ + val = (sensor_read(gspca_dev, reg) & ~mask) | (val & mask); + sensor_write(gspca_dev, reg, val); +} + +struct sensor_val { + u16 reg; + u16 val; +}; + +/* configure MT9M112 sensor */ +static void stk1135_configure_mt9m112(struct gspca_dev *gspca_dev) +{ + static const struct sensor_val cfg[] = { + /* restart&reset, chip enable, reserved */ + { 0x00d, 0x000b }, { 0x00d, 0x0008 }, { 0x035, 0x0022 }, + /* mode ctl: AWB on, AE both, clip aper corr, defect corr, AE */ + { 0x106, 0x700e }, + + { 0x2dd, 0x18e0 }, /* B-R thresholds, */ + + /* AWB */ + { 0x21f, 0x0180 }, /* Cb and Cr limits */ + { 0x220, 0xc814 }, { 0x221, 0x8080 }, /* lum limits, RGB gain */ + { 0x222, 0xa078 }, { 0x223, 0xa078 }, /* R, B limit */ + { 0x224, 0x5f20 }, { 0x228, 0xea02 }, /* mtx adj lim, adv ctl */ + { 0x229, 0x867a }, /* wide gates */ + + /* Color correction */ + /* imager gains base, delta, delta signs */ + { 0x25e, 0x594c }, { 0x25f, 0x4d51 }, { 0x260, 0x0002 }, + /* AWB adv ctl 2, gain offs */ + { 0x2ef, 0x0008 }, { 0x2f2, 0x0000 }, + /* base matrix signs, scale K1-5, K6-9 */ + { 0x202, 0x00ee }, { 0x203, 0x3923 }, { 0x204, 0x0724 }, + /* base matrix coef */ + { 0x209, 0x00cd }, { 0x20a, 0x0093 }, { 0x20b, 0x0004 },/*K1-3*/ + { 0x20c, 0x005c }, { 0x20d, 0x00d9 }, { 0x20e, 0x0053 },/*K4-6*/ + { 0x20f, 0x0008 }, { 0x210, 0x0091 }, { 0x211, 0x00cf },/*K7-9*/ + { 0x215, 0x0000 }, /* delta mtx signs */ + /* delta matrix coef */ + { 0x216, 0x0000 }, { 0x217, 0x0000 }, { 0x218, 0x0000 },/*D1-3*/ + { 0x219, 0x0000 }, { 0x21a, 0x0000 }, { 0x21b, 0x0000 },/*D4-6*/ + { 0x21c, 0x0000 }, { 0x21d, 0x0000 }, { 0x21e, 0x0000 },/*D7-9*/ + /* enable & disable manual WB to apply color corr. settings */ + { 0x106, 0xf00e }, { 0x106, 0x700e }, + + /* Lens shading correction */ + { 0x180, 0x0007 }, /* control */ + /* vertical knee 0, 2+1, 4+3 */ + { 0x181, 0xde13 }, { 0x182, 0xebe2 }, { 0x183, 0x00f6 }, /* R */ + { 0x184, 0xe114 }, { 0x185, 0xeadd }, { 0x186, 0xfdf6 }, /* G */ + { 0x187, 0xe511 }, { 0x188, 0xede6 }, { 0x189, 0xfbf7 }, /* B */ + /* horizontal knee 0, 2+1, 4+3, 5 */ + { 0x18a, 0xd613 }, { 0x18b, 0xedec }, /* R .. */ + { 0x18c, 0xf9f2 }, { 0x18d, 0x0000 }, /* .. R */ + { 0x18e, 0xd815 }, { 0x18f, 0xe9ea }, /* G .. */ + { 0x190, 0xf9f1 }, { 0x191, 0x0002 }, /* .. G */ + { 0x192, 0xde10 }, { 0x193, 0xefef }, /* B .. */ + { 0x194, 0xfbf4 }, { 0x195, 0x0002 }, /* .. B */ + /* vertical knee 6+5, 8+7 */ + { 0x1b6, 0x0e06 }, { 0x1b7, 0x2713 }, /* R */ + { 0x1b8, 0x1106 }, { 0x1b9, 0x2713 }, /* G */ + { 0x1ba, 0x0c03 }, { 0x1bb, 0x2a0f }, /* B */ + /* horizontal knee 7+6, 9+8, 10 */ + { 0x1bc, 0x1208 }, { 0x1bd, 0x1a16 }, { 0x1be, 0x0022 }, /* R */ + { 0x1bf, 0x150a }, { 0x1c0, 0x1c1a }, { 0x1c1, 0x002d }, /* G */ + { 0x1c2, 0x1109 }, { 0x1c3, 0x1414 }, { 0x1c4, 0x002a }, /* B */ + { 0x106, 0x740e }, /* enable lens shading correction */ + + /* Gamma correction - context A */ + { 0x153, 0x0b03 }, { 0x154, 0x4722 }, { 0x155, 0xac82 }, + { 0x156, 0xdac7 }, { 0x157, 0xf5e9 }, { 0x158, 0xff00 }, + /* Gamma correction - context B */ + { 0x1dc, 0x0b03 }, { 0x1dd, 0x4722 }, { 0x1de, 0xac82 }, + { 0x1df, 0xdac7 }, { 0x1e0, 0xf5e9 }, { 0x1e1, 0xff00 }, + + /* output format: RGB, invert output pixclock, output bayer */ + { 0x13a, 0x4300 }, { 0x19b, 0x4300 }, /* for context A, B */ + { 0x108, 0x0180 }, /* format control - enable bayer row flip */ + + { 0x22f, 0xd100 }, { 0x29c, 0xd100 }, /* AE A, B */ + + /* default prg conf, prg ctl - by 0x2d2, prg advance - PA1 */ + { 0x2d2, 0x0000 }, { 0x2cc, 0x0004 }, { 0x2cb, 0x0001 }, + + { 0x22e, 0x0c3c }, { 0x267, 0x1010 }, /* AE tgt ctl, gain lim */ + + /* PLL */ + { 0x065, 0xa000 }, /* clk ctl - enable PLL (clear bit 14) */ + { 0x066, 0x2003 }, { 0x067, 0x0501 }, /* PLL M=128, N=3, P=1 */ + { 0x065, 0x2000 }, /* disable PLL bypass (clear bit 15) */ + + { 0x005, 0x01b8 }, { 0x007, 0x00d8 }, /* horiz blanking B, A */ + + /* AE line size, shutter delay limit */ + { 0x239, 0x06c0 }, { 0x23b, 0x040e }, /* for context A */ + { 0x23a, 0x06c0 }, { 0x23c, 0x0564 }, /* for context B */ + /* shutter width basis 60Hz, 50Hz */ + { 0x257, 0x0208 }, { 0x258, 0x0271 }, /* for context A */ + { 0x259, 0x0209 }, { 0x25a, 0x0271 }, /* for context B */ + + { 0x25c, 0x120d }, { 0x25d, 0x1712 }, /* flicker 60Hz, 50Hz */ + { 0x264, 0x5e1c }, /* reserved */ + /* flicker, AE gain limits, gain zone limits */ + { 0x25b, 0x0003 }, { 0x236, 0x7810 }, { 0x237, 0x8304 }, + + { 0x008, 0x0021 }, /* vert blanking A */ + }; + int i; + u16 width, height; + + for (i = 0; i < ARRAY_SIZE(cfg); i++) + sensor_write(gspca_dev, cfg[i].reg, cfg[i].val); + + /* set output size */ + width = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].width; + height = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].height; + if (width <= 640) { /* use context A (half readout speed by default) */ + sensor_write(gspca_dev, 0x1a7, width); + sensor_write(gspca_dev, 0x1aa, height); + /* set read mode context A */ + sensor_write(gspca_dev, 0x0c8, 0x0000); + /* set resize, read mode, vblank, hblank context A */ + sensor_write(gspca_dev, 0x2c8, 0x0000); + } else { /* use context B (full readout speed by default) */ + sensor_write(gspca_dev, 0x1a1, width); + sensor_write(gspca_dev, 0x1a4, height); + /* set read mode context B */ + sensor_write(gspca_dev, 0x0c8, 0x0008); + /* set resize, read mode, vblank, hblank context B */ + sensor_write(gspca_dev, 0x2c8, 0x040b); + } +} + +static void stk1135_configure_clock(struct gspca_dev *gspca_dev) +{ + /* configure SCLKOUT */ + reg_w(gspca_dev, STK1135_REG_TMGEN, 0x12); + /* set 1 clock per pixel */ + /* and positive edge clocked pulse high when pixel counter = 0 */ + reg_w(gspca_dev, STK1135_REG_TCP1 + 0, 0x41); + reg_w(gspca_dev, STK1135_REG_TCP1 + 1, 0x00); + reg_w(gspca_dev, STK1135_REG_TCP1 + 2, 0x00); + reg_w(gspca_dev, STK1135_REG_TCP1 + 3, 0x00); + + /* enable CLKOUT for sensor */ + reg_w(gspca_dev, STK1135_REG_SENSO + 0, 0x10); + /* disable STOP clock */ + reg_w(gspca_dev, STK1135_REG_SENSO + 1, 0x00); + /* set lower 8 bits of PLL feedback divider */ + reg_w(gspca_dev, STK1135_REG_SENSO + 3, 0x07); + /* set other PLL parameters */ + reg_w(gspca_dev, STK1135_REG_PLLFD, 0x06); + /* enable timing generator */ + reg_w(gspca_dev, STK1135_REG_TMGEN, 0x80); + /* enable PLL */ + reg_w(gspca_dev, STK1135_REG_SENSO + 2, 0x04); + + /* set serial interface clock divider (30MHz/0x1f*16+2) = 60240 kHz) */ + reg_w(gspca_dev, STK1135_REG_SICTL + 2, 0x1f); +} + +static void stk1135_camera_disable(struct gspca_dev *gspca_dev) +{ + /* set capture end Y position to 0 */ + reg_w(gspca_dev, STK1135_REG_CIEPO + 2, 0x00); + reg_w(gspca_dev, STK1135_REG_CIEPO + 3, 0x00); + /* disable capture */ + reg_w_mask(gspca_dev, STK1135_REG_SCTRL, 0x00, 0x80); + + /* enable sensor standby and diasble chip enable */ + sensor_write_mask(gspca_dev, 0x00d, 0x0004, 0x000c); + + /* disable PLL */ + reg_w_mask(gspca_dev, STK1135_REG_SENSO + 2, 0x00, 0x01); + /* disable timing generator */ + reg_w(gspca_dev, STK1135_REG_TMGEN, 0x00); + /* enable STOP clock */ + reg_w(gspca_dev, STK1135_REG_SENSO + 1, 0x20); + /* disable CLKOUT for sensor */ + reg_w(gspca_dev, STK1135_REG_SENSO, 0x00); + + /* disable sensor (GPIO5) and enable GPIO0,3,6 (?) - sensor standby? */ + reg_w(gspca_dev, STK1135_REG_GCTRL, 0x49); +} + +/* this function is called at probe and resume time */ +static int sd_init(struct gspca_dev *gspca_dev) +{ + u16 sensor_id; + char *sensor_name; + struct sd *sd = (struct sd *) gspca_dev; + + /* set GPIO3,4,5,6 direction to output */ + reg_w(gspca_dev, STK1135_REG_GCTRL + 2, 0x78); + /* enable sensor (GPIO5) */ + reg_w(gspca_dev, STK1135_REG_GCTRL, (1 << 5)); + /* disable ROM interface */ + reg_w(gspca_dev, STK1135_REG_GCTRL + 3, 0x80); + /* enable interrupts from GPIO8 (flip sensor) and GPIO9 (???) */ + reg_w(gspca_dev, STK1135_REG_ICTRL + 1, 0x00); + reg_w(gspca_dev, STK1135_REG_ICTRL + 3, 0x03); + /* enable remote wakeup from GPIO9 (???) */ + reg_w(gspca_dev, STK1135_REG_RMCTL + 1, 0x00); + reg_w(gspca_dev, STK1135_REG_RMCTL + 3, 0x02); + + /* reset serial interface */ + reg_w(gspca_dev, STK1135_REG_SICTL, 0x80); + reg_w(gspca_dev, STK1135_REG_SICTL, 0x00); + /* set sensor address */ + reg_w(gspca_dev, STK1135_REG_SICTL + 3, 0xba); + /* disable alt 2-wire serial interface */ + reg_w(gspca_dev, STK1135_REG_ASIC + 3, 0x00); + + stk1135_configure_clock(gspca_dev); + + /* read sensor ID */ + sd->sensor_page = 0xff; + sensor_id = sensor_read(gspca_dev, 0x000); + + switch (sensor_id) { + case 0x148c: + sensor_name = "MT9M112"; + break; + default: + sensor_name = "unknown"; + } + pr_info("Detected sensor type %s (0x%x)\n", sensor_name, sensor_id); + + stk1135_camera_disable(gspca_dev); + + return gspca_dev->usb_err; +} + +/* -- start the camera -- */ +static int sd_start(struct gspca_dev *gspca_dev) +{ + struct sd *sd = (struct sd *) gspca_dev; + u16 width, height; + + /* enable sensor (GPIO5) */ + reg_w(gspca_dev, STK1135_REG_GCTRL, (1 << 5)); + + stk1135_configure_clock(gspca_dev); + + /* set capture start position X = 0, Y = 0 */ + reg_w(gspca_dev, STK1135_REG_CISPO + 0, 0x00); + reg_w(gspca_dev, STK1135_REG_CISPO + 1, 0x00); + reg_w(gspca_dev, STK1135_REG_CISPO + 2, 0x00); + reg_w(gspca_dev, STK1135_REG_CISPO + 3, 0x00); + + /* set capture end position */ + width = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].width; + height = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].height; + reg_w(gspca_dev, STK1135_REG_CIEPO + 0, width & 0xff); + reg_w(gspca_dev, STK1135_REG_CIEPO + 1, width >> 8); + reg_w(gspca_dev, STK1135_REG_CIEPO + 2, height & 0xff); + reg_w(gspca_dev, STK1135_REG_CIEPO + 3, height >> 8); + + /* set 8-bit mode */ + reg_w(gspca_dev, STK1135_REG_SCTRL, 0x20); + + stk1135_configure_mt9m112(gspca_dev); + + /* enable capture */ + reg_w_mask(gspca_dev, STK1135_REG_SCTRL, 0x80, 0x80); + + if (gspca_dev->usb_err >= 0) + PDEBUG(D_STREAM, "camera started alt: 0x%02x", + gspca_dev->alt); + + sd->pkt_seq = 0; + + return gspca_dev->usb_err; +} + +static void sd_stopN(struct gspca_dev *gspca_dev) +{ + struct usb_device *dev = gspca_dev->dev; + + usb_set_interface(dev, gspca_dev->iface, 0); + + stk1135_camera_disable(gspca_dev); + + PDEBUG(D_STREAM, "camera stopped"); +} + +static void sd_pkt_scan(struct gspca_dev *gspca_dev, + u8 *data, /* isoc packet */ + int len) /* iso packet length */ +{ + struct sd *sd = (struct sd *) gspca_dev; + int skip = sizeof(struct stk1135_pkt_header); + bool flip; + enum gspca_packet_type pkt_type = INTER_PACKET; + struct stk1135_pkt_header *hdr = (void *)data; + u8 seq; + + if (len < 4) { + PDEBUG(D_PACK, "received short packet (less than 4 bytes)"); + return; + } + + /* GPIO 8 is flip sensor (1 = normal position, 0 = flipped to back) */ + flip = !(le16_to_cpu(hdr->gpio) & (1 << 8)); + /* it's a switch, needs software debounce */ + if (sd->flip_status != flip) + sd->flip_debounce++; + else + sd->flip_debounce = 0; + + /* check sequence number (not present in new frame packets) */ + if (!(hdr->flags & STK1135_HDR_FRAME_START)) { + seq = hdr->seq & STK1135_HDR_SEQ_MASK; + if (seq != sd->pkt_seq) { + PDEBUG(D_PACK, "received out-of-sequence packet"); + /* resync sequence and discard packet */ + sd->pkt_seq = seq; + gspca_dev->last_packet_type = DISCARD_PACKET; + return; + } + } + sd->pkt_seq++; + if (sd->pkt_seq > STK1135_HDR_SEQ_MASK) + sd->pkt_seq = 0; + + if (len == sizeof(struct stk1135_pkt_header)) + return; + + if (hdr->flags & STK1135_HDR_FRAME_START) { /* new frame */ + skip = 8; /* the header is longer */ + gspca_frame_add(gspca_dev, LAST_PACKET, data, 0); + pkt_type = FIRST_PACKET; + } + gspca_frame_add(gspca_dev, pkt_type, data + skip, len - skip); +} + +static void sethflip(struct gspca_dev *gspca_dev, s32 val) +{ + struct sd *sd = (struct sd *) gspca_dev; + + if (sd->flip_status) + val = !val; + sensor_write_mask(gspca_dev, 0x020, val ? 0x0002 : 0x0000 , 0x0002); +} + +static void setvflip(struct gspca_dev *gspca_dev, s32 val) +{ + struct sd *sd = (struct sd *) gspca_dev; + + if (sd->flip_status) + val = !val; + sensor_write_mask(gspca_dev, 0x020, val ? 0x0001 : 0x0000 , 0x0001); +} + +static void stk1135_dq_callback(struct gspca_dev *gspca_dev) +{ + struct sd *sd = (struct sd *) gspca_dev; + + if (sd->flip_debounce > 100) { + sd->flip_status = !sd->flip_status; + sethflip(gspca_dev, v4l2_ctrl_g_ctrl(sd->hflip)); + setvflip(gspca_dev, v4l2_ctrl_g_ctrl(sd->vflip)); + } +} + +static int sd_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct gspca_dev *gspca_dev = + container_of(ctrl->handler, struct gspca_dev, ctrl_handler); + + gspca_dev->usb_err = 0; + + if (!gspca_dev->streaming) + return 0; + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + sethflip(gspca_dev, ctrl->val); + break; + case V4L2_CID_VFLIP: + setvflip(gspca_dev, ctrl->val); + break; + } + + return gspca_dev->usb_err; +} + +static const struct v4l2_ctrl_ops sd_ctrl_ops = { + .s_ctrl = sd_s_ctrl, +}; + +static int sd_init_controls(struct gspca_dev *gspca_dev) +{ + struct sd *sd = (struct sd *) gspca_dev; + struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler; + + gspca_dev->vdev.ctrl_handler = hdl; + v4l2_ctrl_handler_init(hdl, 2); + sd->hflip = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + sd->vflip = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + if (hdl->error) { + pr_err("Could not initialize controls\n"); + return hdl->error; + } + return 0; +} + +/* sub-driver description */ +static const struct sd_desc sd_desc = { + .name = MODULE_NAME, + .config = sd_config, + .init = sd_init, + .init_controls = sd_init_controls, + .start = sd_start, + .stopN = sd_stopN, + .pkt_scan = sd_pkt_scan, + .dq_callback = stk1135_dq_callback, +}; + +/* -- module initialisation -- */ +static const struct usb_device_id device_table[] = { + {USB_DEVICE(0x174f, 0x6a31)}, /* ASUS laptop, MT9M112 sensor */ + {} +}; +MODULE_DEVICE_TABLE(usb, device_table); + +/* -- device connect -- */ +static int sd_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd), + THIS_MODULE); +} + +static struct usb_driver sd_driver = { + .name = MODULE_NAME, + .id_table = device_table, + .probe = sd_probe, + .disconnect = gspca_disconnect, +#ifdef CONFIG_PM + .suspend = gspca_suspend, + .resume = gspca_resume, + .reset_resume = gspca_resume, +#endif +}; + +module_usb_driver(sd_driver); diff --git a/drivers/media/usb/gspca/stk1135.h b/drivers/media/usb/gspca/stk1135.h new file mode 100644 index 000000000000..e1dd92ab49bb --- /dev/null +++ b/drivers/media/usb/gspca/stk1135.h @@ -0,0 +1,57 @@ +/* + * STK1135 registers + * + * Copyright (c) 2013 Ondrej Zary + * + * 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 + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define STK1135_REG_GCTRL 0x000 /* GPIO control */ +#define STK1135_REG_ICTRL 0x004 /* Interrupt control */ +#define STK1135_REG_IDATA 0x008 /* Interrupt data */ +#define STK1135_REG_RMCTL 0x00c /* Remote wakeup control */ +#define STK1135_REG_POSVA 0x010 /* Power-on strapping data */ + +#define STK1135_REG_SENSO 0x018 /* Sensor select options */ +#define STK1135_REG_PLLFD 0x01c /* PLL frequency divider */ + +#define STK1135_REG_SCTRL 0x100 /* Sensor control register */ +#define STK1135_REG_DCTRL 0x104 /* Decimation control register */ +#define STK1135_REG_CISPO 0x110 /* Capture image starting position */ +#define STK1135_REG_CIEPO 0x114 /* Capture image ending position */ +#define STK1135_REG_TCTRL 0x120 /* Test data control */ + +#define STK1135_REG_SICTL 0x200 /* Serial interface control register */ +#define STK1135_REG_SBUSW 0x204 /* Serial bus write */ +#define STK1135_REG_SBUSR 0x208 /* Serial bus read */ +#define STK1135_REG_SCSI 0x20c /* Software control serial interface */ +#define STK1135_REG_GSBWP 0x210 /* General serial bus write port */ +#define STK1135_REG_GSBRP 0x214 /* General serial bus read port */ +#define STK1135_REG_ASIC 0x2fc /* Alternate serial interface control */ + +#define STK1135_REG_TMGEN 0x300 /* Timing generator */ +#define STK1135_REG_TCP1 0x350 /* Timing control parameter 1 */ + +struct stk1135_pkt_header { + u8 flags; + u8 seq; + __le16 gpio; +} __packed; + +#define STK1135_HDR_FRAME_START (1 << 7) +#define STK1135_HDR_ODD (1 << 6) +#define STK1135_HDR_I2C_VBLANK (1 << 5) + +#define STK1135_HDR_SEQ_MASK 0x3f diff --git a/drivers/media/usb/hdpvr/hdpvr-video.c b/drivers/media/usb/hdpvr/hdpvr-video.c index 4f8567aa99d8..0500c4175d5f 100644 --- a/drivers/media/usb/hdpvr/hdpvr-video.c +++ b/drivers/media/usb/hdpvr/hdpvr-video.c @@ -24,6 +24,7 @@ #include <linux/v4l2-dv-timings.h> #include <media/v4l2-dev.h> #include <media/v4l2-common.h> +#include <media/v4l2-dv-timings.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-event.h> #include "hdpvr.h" @@ -641,7 +642,7 @@ static int vidioc_s_dv_timings(struct file *file, void *_fh, if (dev->status != STATUS_IDLE) return -EBUSY; for (i = 0; i < ARRAY_SIZE(hdpvr_dv_timings); i++) - if (v4l_match_dv_timings(timings, hdpvr_dv_timings + i, 0)) + if (v4l2_match_dv_timings(timings, hdpvr_dv_timings + i, 0)) break; if (i == ARRAY_SIZE(hdpvr_dv_timings)) return -EINVAL; @@ -689,10 +690,8 @@ static int vidioc_query_dv_timings(struct file *file, void *_fh, unsigned vsize; unsigned fps; - hsize = bt->hfrontporch + bt->hsync + bt->hbackporch + bt->width; - vsize = bt->vfrontporch + bt->vsync + bt->vbackporch + - bt->il_vfrontporch + bt->il_vsync + bt->il_vbackporch + - bt->height; + hsize = V4L2_DV_BT_FRAME_WIDTH(bt); + vsize = V4L2_DV_BT_FRAME_HEIGHT(bt); fps = (unsigned)bt->pixelclock / (hsize * vsize); if (bt->width != vid_info.width || bt->height != vid_info.height || diff --git a/drivers/media/usb/s2255/s2255drv.c b/drivers/media/usb/s2255/s2255drv.c index ab97e7d0b4f2..6bc9b8e19e20 100644 --- a/drivers/media/usb/s2255/s2255drv.c +++ b/drivers/media/usb/s2255/s2255drv.c @@ -1,7 +1,7 @@ /* * s2255drv.c - a driver for the Sensoray 2255 USB video capture device * - * Copyright (C) 2007-2010 by Sensoray Company Inc. + * Copyright (C) 2007-2013 by Sensoray Company Inc. * Dean Anderson * * Some video buffer code based on vivi driver: @@ -52,7 +52,7 @@ #include <media/v4l2-ctrls.h> #include <media/v4l2-event.h> -#define S2255_VERSION "1.22.1" +#define S2255_VERSION "1.23.1" #define FIRMWARE_FILE_NAME "f2255usb.bin" /* default JPEG quality */ @@ -1303,11 +1303,6 @@ static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id i) int ret = 0; mutex_lock(&q->vb_lock); - if (videobuf_queue_is_busy(q)) { - dprintk(1, "queue busy\n"); - ret = -EBUSY; - goto out_s_std; - } if (res_locked(fh)) { dprintk(1, "can't change standard after started\n"); ret = -EBUSY; diff --git a/drivers/media/usb/stk1160/Kconfig b/drivers/media/usb/stk1160/Kconfig index 1c3a1ec00237..95584c15dc5a 100644 --- a/drivers/media/usb/stk1160/Kconfig +++ b/drivers/media/usb/stk1160/Kconfig @@ -1,8 +1,6 @@ -config VIDEO_STK1160 +config VIDEO_STK1160_COMMON tristate "STK1160 USB video capture support" depends on VIDEO_DEV && I2C - select VIDEOBUF2_VMALLOC - select VIDEO_SAA711X ---help--- This is a video4linux driver for STK1160 based video capture devices. @@ -12,9 +10,15 @@ config VIDEO_STK1160 config VIDEO_STK1160_AC97 bool "STK1160 AC97 codec support" - depends on VIDEO_STK1160 && SND - select SND_AC97_CODEC + depends on VIDEO_STK1160_COMMON && SND ---help--- Enables AC97 codec support for stk1160 driver. -. + +config VIDEO_STK1160 + tristate + depends on (!VIDEO_STK1160_AC97 || (SND='n') || SND) && VIDEO_STK1160_COMMON + default y + select VIDEOBUF2_VMALLOC + select VIDEO_SAA711X + select SND_AC97_CODEC if SND diff --git a/drivers/media/usb/stk1160/stk1160-v4l.c b/drivers/media/usb/stk1160/stk1160-v4l.c index 876fc26565e3..c45c9881bb5f 100644 --- a/drivers/media/usb/stk1160/stk1160-v4l.c +++ b/drivers/media/usb/stk1160/stk1160-v4l.c @@ -379,6 +379,9 @@ static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id norm) struct stk1160 *dev = video_drvdata(file); struct vb2_queue *q = &dev->vb_vidq; + if (dev->norm == norm) + return 0; + if (vb2_is_busy(q)) return -EBUSY; @@ -440,9 +443,6 @@ static int vidioc_s_input(struct file *file, void *priv, unsigned int i) { struct stk1160 *dev = video_drvdata(file); - if (vb2_is_busy(&dev->vb_vidq)) - return -EBUSY; - if (i > STK1160_MAX_INPUT) return -EINVAL; diff --git a/drivers/media/usb/tlg2300/pd-main.c b/drivers/media/usb/tlg2300/pd-main.c index e07e4c699cc2..95f94e5aa66d 100644 --- a/drivers/media/usb/tlg2300/pd-main.c +++ b/drivers/media/usb/tlg2300/pd-main.c @@ -375,7 +375,7 @@ static inline void set_map_flags(struct poseidon *pd, struct usb_device *udev) } #endif -static int check_firmware(struct usb_device *udev, int *down_firmware) +static int check_firmware(struct usb_device *udev) { void *buf; int ret; @@ -395,10 +395,8 @@ static int check_firmware(struct usb_device *udev, int *down_firmware) USB_CTRL_GET_TIMEOUT); kfree(buf); - if (ret < 0) { - *down_firmware = 1; + if (ret < 0) return firmware_download(udev); - } return 0; } @@ -411,9 +409,9 @@ static int poseidon_probe(struct usb_interface *interface, int new_one = 0; /* download firmware */ - check_firmware(udev, &ret); + ret = check_firmware(udev); if (ret) - return 0; + return ret; /* Do I recovery from the hibernate ? */ pd = find_old_poseidon(udev); @@ -436,12 +434,22 @@ static int poseidon_probe(struct usb_interface *interface, /* register v4l2 device */ ret = v4l2_device_register(&interface->dev, &pd->v4l2_dev); + if (ret) + goto err_v4l2; /* register devices in directory /dev */ ret = pd_video_init(pd); - poseidon_audio_init(pd); - poseidon_fm_init(pd); - pd_dvb_usb_device_init(pd); + if (ret) + goto err_video; + ret = poseidon_audio_init(pd); + if (ret) + goto err_audio; + ret = poseidon_fm_init(pd); + if (ret) + goto err_fm; + ret = pd_dvb_usb_device_init(pd); + if (ret) + goto err_dvb; INIT_LIST_HEAD(&pd->device_list); list_add_tail(&pd->device_list, &pd_device_list); @@ -459,6 +467,17 @@ static int poseidon_probe(struct usb_interface *interface, } #endif return 0; +err_dvb: + poseidon_fm_exit(pd); +err_fm: + poseidon_audio_free(pd); +err_audio: + pd_video_exit(pd); +err_video: + v4l2_device_unregister(&pd->v4l2_dev); +err_v4l2: + kfree(pd); + return ret; } static void poseidon_disconnect(struct usb_interface *interface) diff --git a/drivers/media/usb/usbtv/usbtv.c b/drivers/media/usb/usbtv/usbtv.c index 91650173941a..8a505a90d318 100644 --- a/drivers/media/usb/usbtv/usbtv.c +++ b/drivers/media/usb/usbtv/usbtv.c @@ -33,7 +33,6 @@ #include <linux/module.h> #include <linux/slab.h> #include <linux/usb.h> -#include <linux/version.h> #include <linux/videodev2.h> #include <media/v4l2-device.h> @@ -91,17 +90,78 @@ struct usbtv { u32 frame_id; int chunks_done; + enum { + USBTV_COMPOSITE_INPUT, + USBTV_SVIDEO_INPUT, + } input; int iso_size; unsigned int sequence; struct urb *isoc_urbs[USBTV_ISOC_TRANSFERS]; }; -static int usbtv_setup_capture(struct usbtv *usbtv) +static int usbtv_set_regs(struct usbtv *usbtv, const u16 regs[][2], int size) { int ret; int pipe = usb_rcvctrlpipe(usbtv->udev, 0); int i; - static const u16 protoregs[][2] = { + + for (i = 0; i < size; i++) { + u16 index = regs[i][0]; + u16 value = regs[i][1]; + + ret = usb_control_msg(usbtv->udev, pipe, USBTV_REQUEST_REG, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, index, NULL, 0, 0); + if (ret < 0) + return ret; + } + + return 0; +} + +static int usbtv_select_input(struct usbtv *usbtv, int input) +{ + int ret; + + static const u16 composite[][2] = { + { USBTV_BASE + 0x0105, 0x0060 }, + { USBTV_BASE + 0x011f, 0x00f2 }, + { USBTV_BASE + 0x0127, 0x0060 }, + { USBTV_BASE + 0x00ae, 0x0010 }, + { USBTV_BASE + 0x0284, 0x00aa }, + { USBTV_BASE + 0x0239, 0x0060 }, + }; + + static const u16 svideo[][2] = { + { USBTV_BASE + 0x0105, 0x0010 }, + { USBTV_BASE + 0x011f, 0x00ff }, + { USBTV_BASE + 0x0127, 0x0060 }, + { USBTV_BASE + 0x00ae, 0x0030 }, + { USBTV_BASE + 0x0284, 0x0088 }, + { USBTV_BASE + 0x0239, 0x0060 }, + }; + + switch (input) { + case USBTV_COMPOSITE_INPUT: + ret = usbtv_set_regs(usbtv, composite, ARRAY_SIZE(composite)); + break; + case USBTV_SVIDEO_INPUT: + ret = usbtv_set_regs(usbtv, svideo, ARRAY_SIZE(svideo)); + break; + default: + ret = -EINVAL; + } + + if (!ret) + usbtv->input = input; + + return ret; +} + +static int usbtv_setup_capture(struct usbtv *usbtv) +{ + int ret; + static const u16 setup[][2] = { /* These seem to enable the device. */ { USBTV_BASE + 0x0008, 0x0001 }, { USBTV_BASE + 0x01d0, 0x00ff }, @@ -189,16 +249,13 @@ static int usbtv_setup_capture(struct usbtv *usbtv) { USBTV_BASE + 0x024f, 0x0002 }, }; - for (i = 0; i < ARRAY_SIZE(protoregs); i++) { - u16 index = protoregs[i][0]; - u16 value = protoregs[i][1]; + ret = usbtv_set_regs(usbtv, setup, ARRAY_SIZE(setup)); + if (ret) + return ret; - ret = usb_control_msg(usbtv->udev, pipe, USBTV_REQUEST_REG, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - value, index, NULL, 0, 0); - if (ret < 0) - return ret; - } + ret = usbtv_select_input(usbtv, usbtv->input); + if (ret) + return ret; return 0; } @@ -443,10 +500,17 @@ static int usbtv_querycap(struct file *file, void *priv, static int usbtv_enum_input(struct file *file, void *priv, struct v4l2_input *i) { - if (i->index > 0) + switch (i->index) { + case USBTV_COMPOSITE_INPUT: + strlcpy(i->name, "Composite", sizeof(i->name)); + break; + case USBTV_SVIDEO_INPUT: + strlcpy(i->name, "S-Video", sizeof(i->name)); + break; + default: return -EINVAL; + } - strlcpy(i->name, "Composite", sizeof(i->name)); i->type = V4L2_INPUT_TYPE_CAMERA; i->std = V4L2_STD_525_60; return 0; @@ -486,15 +550,15 @@ static int usbtv_g_std(struct file *file, void *priv, v4l2_std_id *norm) static int usbtv_g_input(struct file *file, void *priv, unsigned int *i) { - *i = 0; + struct usbtv *usbtv = video_drvdata(file); + *i = usbtv->input; return 0; } static int usbtv_s_input(struct file *file, void *priv, unsigned int i) { - if (i > 0) - return -EINVAL; - return 0; + struct usbtv *usbtv = video_drvdata(file); + return usbtv_select_input(usbtv, i); } static int usbtv_s_std(struct file *file, void *priv, v4l2_std_id norm) diff --git a/drivers/media/v4l2-core/Makefile b/drivers/media/v4l2-core/Makefile index 4c33b8d6520c..1a85eee581f8 100644 --- a/drivers/media/v4l2-core/Makefile +++ b/drivers/media/v4l2-core/Makefile @@ -17,6 +17,7 @@ endif obj-$(CONFIG_VIDEO_V4L2) += videodev.o obj-$(CONFIG_VIDEO_V4L2_INT_DEVICE) += v4l2-int-device.o obj-$(CONFIG_VIDEO_V4L2) += v4l2-common.o +obj-$(CONFIG_VIDEO_V4L2) += v4l2-dv-timings.o obj-$(CONFIG_VIDEO_TUNER) += tuner.o diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c index aae241730caa..c85d69da35bd 100644 --- a/drivers/media/v4l2-core/v4l2-async.c +++ b/drivers/media/v4l2-core/v4l2-async.c @@ -27,7 +27,6 @@ static bool match_i2c(struct device *dev, struct v4l2_async_subdev *asd) #if IS_ENABLED(CONFIG_I2C) struct i2c_client *client = i2c_verify_client(dev); return client && - asd->bus_type == V4L2_ASYNC_BUS_I2C && asd->match.i2c.adapter_id == client->adapter->nr && asd->match.i2c.address == client->addr; #else @@ -35,10 +34,14 @@ static bool match_i2c(struct device *dev, struct v4l2_async_subdev *asd) #endif } -static bool match_platform(struct device *dev, struct v4l2_async_subdev *asd) +static bool match_devname(struct device *dev, struct v4l2_async_subdev *asd) { - return asd->bus_type == V4L2_ASYNC_BUS_PLATFORM && - !strcmp(asd->match.platform.name, dev_name(dev)); + return !strcmp(asd->match.device_name.name, dev_name(dev)); +} + +static bool match_of(struct device *dev, struct v4l2_async_subdev *asd) +{ + return dev->of_node == asd->match.of.node; } static LIST_HEAD(subdev_list); @@ -46,28 +49,29 @@ static LIST_HEAD(notifier_list); static DEFINE_MUTEX(list_lock); static struct v4l2_async_subdev *v4l2_async_belongs(struct v4l2_async_notifier *notifier, - struct v4l2_async_subdev_list *asdl) + struct v4l2_subdev *sd) { - struct v4l2_subdev *sd = v4l2_async_to_subdev(asdl); struct v4l2_async_subdev *asd; - bool (*match)(struct device *, - struct v4l2_async_subdev *); + bool (*match)(struct device *, struct v4l2_async_subdev *); list_for_each_entry(asd, ¬ifier->waiting, list) { /* bus_type has been verified valid before */ - switch (asd->bus_type) { - case V4L2_ASYNC_BUS_CUSTOM: + switch (asd->match_type) { + case V4L2_ASYNC_MATCH_CUSTOM: match = asd->match.custom.match; if (!match) /* Match always */ return asd; break; - case V4L2_ASYNC_BUS_PLATFORM: - match = match_platform; + case V4L2_ASYNC_MATCH_DEVNAME: + match = match_devname; break; - case V4L2_ASYNC_BUS_I2C: + case V4L2_ASYNC_MATCH_I2C: match = match_i2c; break; + case V4L2_ASYNC_MATCH_OF: + match = match_of; + break; default: /* Cannot happen, unless someone breaks us */ WARN_ON(true); @@ -83,16 +87,15 @@ static struct v4l2_async_subdev *v4l2_async_belongs(struct v4l2_async_notifier * } static int v4l2_async_test_notify(struct v4l2_async_notifier *notifier, - struct v4l2_async_subdev_list *asdl, + struct v4l2_subdev *sd, struct v4l2_async_subdev *asd) { - struct v4l2_subdev *sd = v4l2_async_to_subdev(asdl); int ret; /* Remove from the waiting list */ list_del(&asd->list); - asdl->asd = asd; - asdl->notifier = notifier; + sd->asd = asd; + sd->notifier = notifier; if (notifier->bound) { ret = notifier->bound(notifier, sd, asd); @@ -100,7 +103,7 @@ static int v4l2_async_test_notify(struct v4l2_async_notifier *notifier, return ret; } /* Move from the global subdevice list to notifier's done */ - list_move(&asdl->list, ¬ifier->done); + list_move(&sd->async_list, ¬ifier->done); ret = v4l2_device_register_subdev(notifier->v4l2_dev, sd); if (ret < 0) { @@ -115,21 +118,19 @@ static int v4l2_async_test_notify(struct v4l2_async_notifier *notifier, return 0; } -static void v4l2_async_cleanup(struct v4l2_async_subdev_list *asdl) +static void v4l2_async_cleanup(struct v4l2_subdev *sd) { - struct v4l2_subdev *sd = v4l2_async_to_subdev(asdl); - v4l2_device_unregister_subdev(sd); - /* Subdevice driver will reprobe and put asdl back onto the list */ - list_del_init(&asdl->list); - asdl->asd = NULL; + /* Subdevice driver will reprobe and put the subdev back onto the list */ + list_del_init(&sd->async_list); + sd->asd = NULL; sd->dev = NULL; } int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev, struct v4l2_async_notifier *notifier) { - struct v4l2_async_subdev_list *asdl, *tmp; + struct v4l2_subdev *sd, *tmp; struct v4l2_async_subdev *asd; int i; @@ -141,17 +142,18 @@ int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev, INIT_LIST_HEAD(¬ifier->done); for (i = 0; i < notifier->num_subdevs; i++) { - asd = notifier->subdev[i]; + asd = notifier->subdevs[i]; - switch (asd->bus_type) { - case V4L2_ASYNC_BUS_CUSTOM: - case V4L2_ASYNC_BUS_PLATFORM: - case V4L2_ASYNC_BUS_I2C: + switch (asd->match_type) { + case V4L2_ASYNC_MATCH_CUSTOM: + case V4L2_ASYNC_MATCH_DEVNAME: + case V4L2_ASYNC_MATCH_I2C: + case V4L2_ASYNC_MATCH_OF: break; default: dev_err(notifier->v4l2_dev ? notifier->v4l2_dev->dev : NULL, - "Invalid bus-type %u on %p\n", - asd->bus_type, asd); + "Invalid match type %u on %p\n", + asd->match_type, asd); return -EINVAL; } list_add_tail(&asd->list, ¬ifier->waiting); @@ -162,14 +164,14 @@ int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev, /* Keep also completed notifiers on the list */ list_add(¬ifier->list, ¬ifier_list); - list_for_each_entry_safe(asdl, tmp, &subdev_list, list) { + list_for_each_entry_safe(sd, tmp, &subdev_list, async_list) { int ret; - asd = v4l2_async_belongs(notifier, asdl); + asd = v4l2_async_belongs(notifier, sd); if (!asd) continue; - ret = v4l2_async_test_notify(notifier, asdl, asd); + ret = v4l2_async_test_notify(notifier, sd, asd); if (ret < 0) { mutex_unlock(&list_lock); return ret; @@ -184,28 +186,29 @@ EXPORT_SYMBOL(v4l2_async_notifier_register); void v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier) { - struct v4l2_async_subdev_list *asdl, *tmp; + struct v4l2_subdev *sd, *tmp; unsigned int notif_n_subdev = notifier->num_subdevs; unsigned int n_subdev = min(notif_n_subdev, V4L2_MAX_SUBDEVS); struct device *dev[n_subdev]; int i = 0; + if (!notifier->v4l2_dev) + return; + mutex_lock(&list_lock); list_del(¬ifier->list); - list_for_each_entry_safe(asdl, tmp, ¬ifier->done, list) { - struct v4l2_subdev *sd = v4l2_async_to_subdev(asdl); - + list_for_each_entry_safe(sd, tmp, ¬ifier->done, async_list) { dev[i] = get_device(sd->dev); - v4l2_async_cleanup(asdl); + v4l2_async_cleanup(sd); /* If we handled USB devices, we'd have to lock the parent too */ device_release_driver(dev[i++]); if (notifier->unbind) - notifier->unbind(notifier, sd, sd->asdl.asd); + notifier->unbind(notifier, sd, sd->asd); } mutex_unlock(&list_lock); @@ -225,6 +228,9 @@ void v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier) } put_device(d); } + + notifier->v4l2_dev = NULL; + /* * Don't care about the waiting list, it is initialised and populated * upon notifier registration. @@ -234,24 +240,23 @@ EXPORT_SYMBOL(v4l2_async_notifier_unregister); int v4l2_async_register_subdev(struct v4l2_subdev *sd) { - struct v4l2_async_subdev_list *asdl = &sd->asdl; struct v4l2_async_notifier *notifier; mutex_lock(&list_lock); - INIT_LIST_HEAD(&asdl->list); + INIT_LIST_HEAD(&sd->async_list); list_for_each_entry(notifier, ¬ifier_list, list) { - struct v4l2_async_subdev *asd = v4l2_async_belongs(notifier, asdl); + struct v4l2_async_subdev *asd = v4l2_async_belongs(notifier, sd); if (asd) { - int ret = v4l2_async_test_notify(notifier, asdl, asd); + int ret = v4l2_async_test_notify(notifier, sd, asd); mutex_unlock(&list_lock); return ret; } } /* None matched, wait for hot-plugging */ - list_add(&asdl->list, &subdev_list); + list_add(&sd->async_list, &subdev_list); mutex_unlock(&list_lock); @@ -261,23 +266,22 @@ EXPORT_SYMBOL(v4l2_async_register_subdev); void v4l2_async_unregister_subdev(struct v4l2_subdev *sd) { - struct v4l2_async_subdev_list *asdl = &sd->asdl; - struct v4l2_async_notifier *notifier = asdl->notifier; + struct v4l2_async_notifier *notifier = sd->notifier; - if (!asdl->asd) { - if (!list_empty(&asdl->list)) - v4l2_async_cleanup(asdl); + if (!sd->asd) { + if (!list_empty(&sd->async_list)) + v4l2_async_cleanup(sd); return; } mutex_lock(&list_lock); - list_add(&asdl->asd->list, ¬ifier->waiting); + list_add(&sd->asd->list, ¬ifier->waiting); - v4l2_async_cleanup(asdl); + v4l2_async_cleanup(sd); if (notifier->unbind) - notifier->unbind(notifier, sd, sd->asdl.asd); + notifier->unbind(notifier, sd, sd->asd); mutex_unlock(&list_lock); } diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c index a95e5e23403f..037d7a55aa8c 100644 --- a/drivers/media/v4l2-core/v4l2-common.c +++ b/drivers/media/v4l2-core/v4l2-common.c @@ -495,363 +495,6 @@ void v4l_bound_align_image(u32 *w, unsigned int wmin, unsigned int wmax, } EXPORT_SYMBOL_GPL(v4l_bound_align_image); -/** - * v4l_match_dv_timings - check if two timings match - * @t1 - compare this v4l2_dv_timings struct... - * @t2 - with this struct. - * @pclock_delta - the allowed pixelclock deviation. - * - * Compare t1 with t2 with a given margin of error for the pixelclock. - */ -bool v4l_match_dv_timings(const struct v4l2_dv_timings *t1, - const struct v4l2_dv_timings *t2, - unsigned pclock_delta) -{ - if (t1->type != t2->type || t1->type != V4L2_DV_BT_656_1120) - return false; - if (t1->bt.width == t2->bt.width && - t1->bt.height == t2->bt.height && - t1->bt.interlaced == t2->bt.interlaced && - t1->bt.polarities == t2->bt.polarities && - t1->bt.pixelclock >= t2->bt.pixelclock - pclock_delta && - t1->bt.pixelclock <= t2->bt.pixelclock + pclock_delta && - t1->bt.hfrontporch == t2->bt.hfrontporch && - t1->bt.vfrontporch == t2->bt.vfrontporch && - t1->bt.vsync == t2->bt.vsync && - t1->bt.vbackporch == t2->bt.vbackporch && - (!t1->bt.interlaced || - (t1->bt.il_vfrontporch == t2->bt.il_vfrontporch && - t1->bt.il_vsync == t2->bt.il_vsync && - t1->bt.il_vbackporch == t2->bt.il_vbackporch))) - return true; - return false; -} -EXPORT_SYMBOL_GPL(v4l_match_dv_timings); - -/* - * CVT defines - * Based on Coordinated Video Timings Standard - * version 1.1 September 10, 2003 - */ - -#define CVT_PXL_CLK_GRAN 250000 /* pixel clock granularity */ - -/* Normal blanking */ -#define CVT_MIN_V_BPORCH 7 /* lines */ -#define CVT_MIN_V_PORCH_RND 3 /* lines */ -#define CVT_MIN_VSYNC_BP 550 /* min time of vsync + back porch (us) */ - -/* Normal blanking for CVT uses GTF to calculate horizontal blanking */ -#define CVT_CELL_GRAN 8 /* character cell granularity */ -#define CVT_M 600 /* blanking formula gradient */ -#define CVT_C 40 /* blanking formula offset */ -#define CVT_K 128 /* blanking formula scaling factor */ -#define CVT_J 20 /* blanking formula scaling factor */ -#define CVT_C_PRIME (((CVT_C - CVT_J) * CVT_K / 256) + CVT_J) -#define CVT_M_PRIME (CVT_K * CVT_M / 256) - -/* Reduced Blanking */ -#define CVT_RB_MIN_V_BPORCH 7 /* lines */ -#define CVT_RB_V_FPORCH 3 /* lines */ -#define CVT_RB_MIN_V_BLANK 460 /* us */ -#define CVT_RB_H_SYNC 32 /* pixels */ -#define CVT_RB_H_BPORCH 80 /* pixels */ -#define CVT_RB_H_BLANK 160 /* pixels */ - -/** v4l2_detect_cvt - detect if the given timings follow the CVT standard - * @frame_height - the total height of the frame (including blanking) in lines. - * @hfreq - the horizontal frequency in Hz. - * @vsync - the height of the vertical sync in lines. - * @polarities - the horizontal and vertical polarities (same as struct - * v4l2_bt_timings polarities). - * @fmt - the resulting timings. - * - * This function will attempt to detect if the given values correspond to a - * valid CVT format. If so, then it will return true, and fmt will be filled - * in with the found CVT timings. - */ -bool v4l2_detect_cvt(unsigned frame_height, unsigned hfreq, unsigned vsync, - u32 polarities, struct v4l2_dv_timings *fmt) -{ - int v_fp, v_bp, h_fp, h_bp, hsync; - int frame_width, image_height, image_width; - bool reduced_blanking; - unsigned pix_clk; - - if (vsync < 4 || vsync > 7) - return false; - - if (polarities == V4L2_DV_VSYNC_POS_POL) - reduced_blanking = false; - else if (polarities == V4L2_DV_HSYNC_POS_POL) - reduced_blanking = true; - else - return false; - - /* Vertical */ - if (reduced_blanking) { - v_fp = CVT_RB_V_FPORCH; - v_bp = (CVT_RB_MIN_V_BLANK * hfreq + 999999) / 1000000; - v_bp -= vsync + v_fp; - - if (v_bp < CVT_RB_MIN_V_BPORCH) - v_bp = CVT_RB_MIN_V_BPORCH; - } else { - v_fp = CVT_MIN_V_PORCH_RND; - v_bp = (CVT_MIN_VSYNC_BP * hfreq + 999999) / 1000000 - vsync; - - if (v_bp < CVT_MIN_V_BPORCH) - v_bp = CVT_MIN_V_BPORCH; - } - image_height = (frame_height - v_fp - vsync - v_bp + 1) & ~0x1; - - /* Aspect ratio based on vsync */ - switch (vsync) { - case 4: - image_width = (image_height * 4) / 3; - break; - case 5: - image_width = (image_height * 16) / 9; - break; - case 6: - image_width = (image_height * 16) / 10; - break; - case 7: - /* special case */ - if (image_height == 1024) - image_width = (image_height * 5) / 4; - else if (image_height == 768) - image_width = (image_height * 15) / 9; - else - return false; - break; - default: - return false; - } - - image_width = image_width & ~7; - - /* Horizontal */ - if (reduced_blanking) { - pix_clk = (image_width + CVT_RB_H_BLANK) * hfreq; - pix_clk = (pix_clk / CVT_PXL_CLK_GRAN) * CVT_PXL_CLK_GRAN; - - h_bp = CVT_RB_H_BPORCH; - hsync = CVT_RB_H_SYNC; - h_fp = CVT_RB_H_BLANK - h_bp - hsync; - - frame_width = image_width + CVT_RB_H_BLANK; - } else { - int h_blank; - unsigned ideal_duty_cycle = CVT_C_PRIME - (CVT_M_PRIME * 1000) / hfreq; - - h_blank = (image_width * ideal_duty_cycle + (100 - ideal_duty_cycle) / 2) / - (100 - ideal_duty_cycle); - h_blank = h_blank - h_blank % (2 * CVT_CELL_GRAN); - - if (h_blank * 100 / image_width < 20) { - h_blank = image_width / 5; - h_blank = (h_blank + 0x7) & ~0x7; - } - - pix_clk = (image_width + h_blank) * hfreq; - pix_clk = (pix_clk / CVT_PXL_CLK_GRAN) * CVT_PXL_CLK_GRAN; - - h_bp = h_blank / 2; - frame_width = image_width + h_blank; - - hsync = (frame_width * 8 + 50) / 100; - hsync = hsync - hsync % CVT_CELL_GRAN; - h_fp = h_blank - hsync - h_bp; - } - - fmt->bt.polarities = polarities; - fmt->bt.width = image_width; - fmt->bt.height = image_height; - fmt->bt.hfrontporch = h_fp; - fmt->bt.vfrontporch = v_fp; - fmt->bt.hsync = hsync; - fmt->bt.vsync = vsync; - fmt->bt.hbackporch = frame_width - image_width - h_fp - hsync; - fmt->bt.vbackporch = frame_height - image_height - v_fp - vsync; - fmt->bt.pixelclock = pix_clk; - fmt->bt.standards = V4L2_DV_BT_STD_CVT; - if (reduced_blanking) - fmt->bt.flags |= V4L2_DV_FL_REDUCED_BLANKING; - return true; -} -EXPORT_SYMBOL_GPL(v4l2_detect_cvt); - -/* - * GTF defines - * Based on Generalized Timing Formula Standard - * Version 1.1 September 2, 1999 - */ - -#define GTF_PXL_CLK_GRAN 250000 /* pixel clock granularity */ - -#define GTF_MIN_VSYNC_BP 550 /* min time of vsync + back porch (us) */ -#define GTF_V_FP 1 /* vertical front porch (lines) */ -#define GTF_CELL_GRAN 8 /* character cell granularity */ - -/* Default */ -#define GTF_D_M 600 /* blanking formula gradient */ -#define GTF_D_C 40 /* blanking formula offset */ -#define GTF_D_K 128 /* blanking formula scaling factor */ -#define GTF_D_J 20 /* blanking formula scaling factor */ -#define GTF_D_C_PRIME ((((GTF_D_C - GTF_D_J) * GTF_D_K) / 256) + GTF_D_J) -#define GTF_D_M_PRIME ((GTF_D_K * GTF_D_M) / 256) - -/* Secondary */ -#define GTF_S_M 3600 /* blanking formula gradient */ -#define GTF_S_C 40 /* blanking formula offset */ -#define GTF_S_K 128 /* blanking formula scaling factor */ -#define GTF_S_J 35 /* blanking formula scaling factor */ -#define GTF_S_C_PRIME ((((GTF_S_C - GTF_S_J) * GTF_S_K) / 256) + GTF_S_J) -#define GTF_S_M_PRIME ((GTF_S_K * GTF_S_M) / 256) - -/** v4l2_detect_gtf - detect if the given timings follow the GTF standard - * @frame_height - the total height of the frame (including blanking) in lines. - * @hfreq - the horizontal frequency in Hz. - * @vsync - the height of the vertical sync in lines. - * @polarities - the horizontal and vertical polarities (same as struct - * v4l2_bt_timings polarities). - * @aspect - preferred aspect ratio. GTF has no method of determining the - * aspect ratio in order to derive the image width from the - * image height, so it has to be passed explicitly. Usually - * the native screen aspect ratio is used for this. If it - * is not filled in correctly, then 16:9 will be assumed. - * @fmt - the resulting timings. - * - * This function will attempt to detect if the given values correspond to a - * valid GTF format. If so, then it will return true, and fmt will be filled - * in with the found GTF timings. - */ -bool v4l2_detect_gtf(unsigned frame_height, - unsigned hfreq, - unsigned vsync, - u32 polarities, - struct v4l2_fract aspect, - struct v4l2_dv_timings *fmt) -{ - int pix_clk; - int v_fp, v_bp, h_fp, hsync; - int frame_width, image_height, image_width; - bool default_gtf; - int h_blank; - - if (vsync != 3) - return false; - - if (polarities == V4L2_DV_VSYNC_POS_POL) - default_gtf = true; - else if (polarities == V4L2_DV_HSYNC_POS_POL) - default_gtf = false; - else - return false; - - /* Vertical */ - v_fp = GTF_V_FP; - v_bp = (GTF_MIN_VSYNC_BP * hfreq + 999999) / 1000000 - vsync; - image_height = (frame_height - v_fp - vsync - v_bp + 1) & ~0x1; - - if (aspect.numerator == 0 || aspect.denominator == 0) { - aspect.numerator = 16; - aspect.denominator = 9; - } - image_width = ((image_height * aspect.numerator) / aspect.denominator); - - /* Horizontal */ - if (default_gtf) - h_blank = ((image_width * GTF_D_C_PRIME * hfreq) - - (image_width * GTF_D_M_PRIME * 1000) + - (hfreq * (100 - GTF_D_C_PRIME) + GTF_D_M_PRIME * 1000) / 2) / - (hfreq * (100 - GTF_D_C_PRIME) + GTF_D_M_PRIME * 1000); - else - h_blank = ((image_width * GTF_S_C_PRIME * hfreq) - - (image_width * GTF_S_M_PRIME * 1000) + - (hfreq * (100 - GTF_S_C_PRIME) + GTF_S_M_PRIME * 1000) / 2) / - (hfreq * (100 - GTF_S_C_PRIME) + GTF_S_M_PRIME * 1000); - - h_blank = h_blank - h_blank % (2 * GTF_CELL_GRAN); - frame_width = image_width + h_blank; - - pix_clk = (image_width + h_blank) * hfreq; - pix_clk = pix_clk / GTF_PXL_CLK_GRAN * GTF_PXL_CLK_GRAN; - - hsync = (frame_width * 8 + 50) / 100; - hsync = hsync - hsync % GTF_CELL_GRAN; - - h_fp = h_blank / 2 - hsync; - - fmt->bt.polarities = polarities; - fmt->bt.width = image_width; - fmt->bt.height = image_height; - fmt->bt.hfrontporch = h_fp; - fmt->bt.vfrontporch = v_fp; - fmt->bt.hsync = hsync; - fmt->bt.vsync = vsync; - fmt->bt.hbackporch = frame_width - image_width - h_fp - hsync; - fmt->bt.vbackporch = frame_height - image_height - v_fp - vsync; - fmt->bt.pixelclock = pix_clk; - fmt->bt.standards = V4L2_DV_BT_STD_GTF; - if (!default_gtf) - fmt->bt.flags |= V4L2_DV_FL_REDUCED_BLANKING; - return true; -} -EXPORT_SYMBOL_GPL(v4l2_detect_gtf); - -/** v4l2_calc_aspect_ratio - calculate the aspect ratio based on bytes - * 0x15 and 0x16 from the EDID. - * @hor_landscape - byte 0x15 from the EDID. - * @vert_portrait - byte 0x16 from the EDID. - * - * Determines the aspect ratio from the EDID. - * See VESA Enhanced EDID standard, release A, rev 2, section 3.6.2: - * "Horizontal and Vertical Screen Size or Aspect Ratio" - */ -struct v4l2_fract v4l2_calc_aspect_ratio(u8 hor_landscape, u8 vert_portrait) -{ - struct v4l2_fract aspect = { 16, 9 }; - u32 tmp; - u8 ratio; - - /* Nothing filled in, fallback to 16:9 */ - if (!hor_landscape && !vert_portrait) - return aspect; - /* Both filled in, so they are interpreted as the screen size in cm */ - if (hor_landscape && vert_portrait) { - aspect.numerator = hor_landscape; - aspect.denominator = vert_portrait; - return aspect; - } - /* Only one is filled in, so interpret them as a ratio: - (val + 99) / 100 */ - ratio = hor_landscape | vert_portrait; - /* Change some rounded values into the exact aspect ratio */ - if (ratio == 79) { - aspect.numerator = 16; - aspect.denominator = 9; - } else if (ratio == 34) { - aspect.numerator = 4; - aspect.numerator = 3; - } else if (ratio == 68) { - aspect.numerator = 15; - aspect.numerator = 9; - } else { - aspect.numerator = hor_landscape + 99; - aspect.denominator = 100; - } - if (hor_landscape) - return aspect; - /* The aspect ratio is for portrait, so swap numerator and denominator */ - tmp = aspect.denominator; - aspect.denominator = aspect.numerator; - aspect.numerator = tmp; - return aspect; -} -EXPORT_SYMBOL_GPL(v4l2_calc_aspect_ratio); - const struct v4l2_frmsize_discrete *v4l2_find_nearest_format( const struct v4l2_discrete_probe *probe, s32 width, s32 height) diff --git a/drivers/media/v4l2-core/v4l2-ctrls.c b/drivers/media/v4l2-core/v4l2-ctrls.c index fccd08b66d1a..c3f080388684 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls.c +++ b/drivers/media/v4l2-core/v4l2-ctrls.c @@ -424,6 +424,12 @@ const char * const *v4l2_ctrl_get_menu(u32 id) NULL, }; + static const char * const vpx_golden_frame_sel[] = { + "Use Previous Frame", + "Use Previous Specific Frame", + NULL, + }; + static const char * const flash_led_mode[] = { "Off", "Flash", @@ -538,6 +544,8 @@ const char * const *v4l2_ctrl_get_menu(u32 id) return mpeg_mpeg4_level; case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE: return mpeg4_profile; + case V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_SEL: + return vpx_golden_frame_sel; case V4L2_CID_JPEG_CHROMA_SUBSAMPLING: return jpeg_chroma_subsampling; case V4L2_CID_DV_TX_MODE: @@ -552,6 +560,33 @@ const char * const *v4l2_ctrl_get_menu(u32 id) } EXPORT_SYMBOL(v4l2_ctrl_get_menu); +#define __v4l2_qmenu_int_len(arr, len) ({ *(len) = ARRAY_SIZE(arr); arr; }) +/* + * Returns NULL or an s64 type array containing the menu for given + * control ID. The total number of the menu items is returned in @len. + */ +const s64 const *v4l2_ctrl_get_int_menu(u32 id, u32 *len) +{ + static const s64 const qmenu_int_vpx_num_partitions[] = { + 1, 2, 4, 8, + }; + + static const s64 const qmenu_int_vpx_num_ref_frames[] = { + 1, 2, 3, + }; + + switch (id) { + case V4L2_CID_MPEG_VIDEO_VPX_NUM_PARTITIONS: + return __v4l2_qmenu_int_len(qmenu_int_vpx_num_partitions, len); + case V4L2_CID_MPEG_VIDEO_VPX_NUM_REF_FRAMES: + return __v4l2_qmenu_int_len(qmenu_int_vpx_num_ref_frames, len); + default: + *len = 0; + return NULL; + }; +} +EXPORT_SYMBOL(v4l2_ctrl_get_int_menu); + /* Return the control name. */ const char *v4l2_ctrl_get_name(u32 id) { @@ -600,9 +635,11 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_ALPHA_COMPONENT: return "Alpha Component"; case V4L2_CID_COLORFX_CBCR: return "Color Effects, CbCr"; - /* MPEG controls */ + /* Codec controls */ + /* The MPEG controls are applicable to all codec controls + * and the 'MPEG' part of the define is historical */ /* Keep the order of the 'case's the same as in videodev2.h! */ - case V4L2_CID_MPEG_CLASS: return "MPEG Encoder Controls"; + case V4L2_CID_MPEG_CLASS: return "Codec Controls"; case V4L2_CID_MPEG_STREAM_TYPE: return "Stream Type"; case V4L2_CID_MPEG_STREAM_PID_PMT: return "Stream PMT Program ID"; case V4L2_CID_MPEG_STREAM_PID_AUDIO: return "Stream Audio Program ID"; @@ -700,6 +737,15 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_MPEG_VIDEO_VBV_DELAY: return "Initial Delay for VBV Control"; case V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER: return "Repeat Sequence Header"; + /* VPX controls */ + case V4L2_CID_MPEG_VIDEO_VPX_NUM_PARTITIONS: return "VPX Number of Partitions"; + case V4L2_CID_MPEG_VIDEO_VPX_IMD_DISABLE_4X4: return "VPX Intra Mode Decision Disable"; + case V4L2_CID_MPEG_VIDEO_VPX_NUM_REF_FRAMES: return "VPX No. of Refs for P Frame"; + case V4L2_CID_MPEG_VIDEO_VPX_FILTER_LEVEL: return "VPX Loop Filter Level Range"; + case V4L2_CID_MPEG_VIDEO_VPX_FILTER_SHARPNESS: return "VPX Deblocking Effect Control"; + case V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_REF_PERIOD: return "VPX Golden Frame Refresh Period"; + case V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_SEL: return "VPX Golden Frame Indicator"; + /* CAMERA controls */ /* Keep the order of the 'case's the same as in videodev2.h! */ case V4L2_CID_CAMERA_CLASS: return "Camera Controls"; @@ -914,6 +960,7 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, case V4L2_CID_DV_RX_RGB_RANGE: case V4L2_CID_TEST_PATTERN: case V4L2_CID_TUNE_DEEMPHASIS: + case V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_SEL: *type = V4L2_CTRL_TYPE_MENU; break; case V4L2_CID_LINK_FREQ: @@ -925,6 +972,8 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, break; case V4L2_CID_ISO_SENSITIVITY: case V4L2_CID_AUTO_EXPOSURE_BIAS: + case V4L2_CID_MPEG_VIDEO_VPX_NUM_PARTITIONS: + case V4L2_CID_MPEG_VIDEO_VPX_NUM_REF_FRAMES: *type = V4L2_CTRL_TYPE_INTEGER_MENU; break; case V4L2_CID_USER_CLASS: @@ -1712,7 +1761,9 @@ struct v4l2_ctrl *v4l2_ctrl_new_std_menu(struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ops, u32 id, s32 max, s32 mask, s32 def) { - const char * const *qmenu = v4l2_ctrl_get_menu(id); + const char * const *qmenu = NULL; + const s64 *qmenu_int = NULL; + unsigned int qmenu_int_len = 0; const char *name; enum v4l2_ctrl_type type; s32 min; @@ -1720,12 +1771,18 @@ struct v4l2_ctrl *v4l2_ctrl_new_std_menu(struct v4l2_ctrl_handler *hdl, u32 flags; v4l2_ctrl_fill(id, &name, &type, &min, &max, &step, &def, &flags); - if (type != V4L2_CTRL_TYPE_MENU) { + + if (type == V4L2_CTRL_TYPE_MENU) + qmenu = v4l2_ctrl_get_menu(id); + else if (type == V4L2_CTRL_TYPE_INTEGER_MENU) + qmenu_int = v4l2_ctrl_get_int_menu(id, &qmenu_int_len); + + if ((!qmenu && !qmenu_int) || (qmenu_int && max > qmenu_int_len)) { handler_set_err(hdl, -EINVAL); return NULL; } return v4l2_ctrl_new(hdl, ops, id, name, type, - 0, max, mask, def, flags, qmenu, NULL, NULL); + 0, max, mask, def, flags, qmenu, qmenu_int, NULL); } EXPORT_SYMBOL(v4l2_ctrl_new_std_menu); diff --git a/drivers/media/v4l2-core/v4l2-dev.c b/drivers/media/v4l2-core/v4l2-dev.c index b0f49b014bc5..b5aaaac427ad 100644 --- a/drivers/media/v4l2-core/v4l2-dev.c +++ b/drivers/media/v4l2-core/v4l2-dev.c @@ -872,6 +872,7 @@ int __video_register_device(struct video_device *vdev, int type, int nr, /* Should not happen since we thought this minor was free */ WARN_ON(video_device[vdev->minor] != NULL); + video_device[vdev->minor] = vdev; vdev->index = get_index(vdev); mutex_unlock(&videodev_lock); @@ -934,9 +935,6 @@ int __video_register_device(struct video_device *vdev, int type, int nr, #endif /* Part 6: Activate this minor. The char device can now be used. */ set_bit(V4L2_FL_REGISTERED, &vdev->flags); - mutex_lock(&videodev_lock); - video_device[vdev->minor] = vdev; - mutex_unlock(&videodev_lock); return 0; @@ -944,6 +942,7 @@ cleanup: mutex_lock(&videodev_lock); if (vdev->cdev) cdev_del(vdev->cdev); + video_device[vdev->minor] = NULL; devnode_clear(vdev); mutex_unlock(&videodev_lock); /* Mark this video device as never having been registered. */ diff --git a/drivers/media/v4l2-core/v4l2-dv-timings.c b/drivers/media/v4l2-core/v4l2-dv-timings.c new file mode 100644 index 000000000000..ee52b9f4a944 --- /dev/null +++ b/drivers/media/v4l2-core/v4l2-dv-timings.c @@ -0,0 +1,609 @@ +/* + * v4l2-dv-timings - dv-timings helper functions + * + * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/videodev2.h> +#include <linux/v4l2-dv-timings.h> +#include <media/v4l2-dv-timings.h> + +const struct v4l2_dv_timings v4l2_dv_timings_presets[] = { + V4L2_DV_BT_CEA_640X480P59_94, + V4L2_DV_BT_CEA_720X480I59_94, + V4L2_DV_BT_CEA_720X480P59_94, + V4L2_DV_BT_CEA_720X576I50, + V4L2_DV_BT_CEA_720X576P50, + V4L2_DV_BT_CEA_1280X720P24, + V4L2_DV_BT_CEA_1280X720P25, + V4L2_DV_BT_CEA_1280X720P30, + V4L2_DV_BT_CEA_1280X720P50, + V4L2_DV_BT_CEA_1280X720P60, + V4L2_DV_BT_CEA_1920X1080P24, + V4L2_DV_BT_CEA_1920X1080P25, + V4L2_DV_BT_CEA_1920X1080P30, + V4L2_DV_BT_CEA_1920X1080I50, + V4L2_DV_BT_CEA_1920X1080P50, + V4L2_DV_BT_CEA_1920X1080I60, + V4L2_DV_BT_CEA_1920X1080P60, + V4L2_DV_BT_DMT_640X350P85, + V4L2_DV_BT_DMT_640X400P85, + V4L2_DV_BT_DMT_720X400P85, + V4L2_DV_BT_DMT_640X480P72, + V4L2_DV_BT_DMT_640X480P75, + V4L2_DV_BT_DMT_640X480P85, + V4L2_DV_BT_DMT_800X600P56, + V4L2_DV_BT_DMT_800X600P60, + V4L2_DV_BT_DMT_800X600P72, + V4L2_DV_BT_DMT_800X600P75, + V4L2_DV_BT_DMT_800X600P85, + V4L2_DV_BT_DMT_800X600P120_RB, + V4L2_DV_BT_DMT_848X480P60, + V4L2_DV_BT_DMT_1024X768I43, + V4L2_DV_BT_DMT_1024X768P60, + V4L2_DV_BT_DMT_1024X768P70, + V4L2_DV_BT_DMT_1024X768P75, + V4L2_DV_BT_DMT_1024X768P85, + V4L2_DV_BT_DMT_1024X768P120_RB, + V4L2_DV_BT_DMT_1152X864P75, + V4L2_DV_BT_DMT_1280X768P60_RB, + V4L2_DV_BT_DMT_1280X768P60, + V4L2_DV_BT_DMT_1280X768P75, + V4L2_DV_BT_DMT_1280X768P85, + V4L2_DV_BT_DMT_1280X768P120_RB, + V4L2_DV_BT_DMT_1280X800P60_RB, + V4L2_DV_BT_DMT_1280X800P60, + V4L2_DV_BT_DMT_1280X800P75, + V4L2_DV_BT_DMT_1280X800P85, + V4L2_DV_BT_DMT_1280X800P120_RB, + V4L2_DV_BT_DMT_1280X960P60, + V4L2_DV_BT_DMT_1280X960P85, + V4L2_DV_BT_DMT_1280X960P120_RB, + V4L2_DV_BT_DMT_1280X1024P60, + V4L2_DV_BT_DMT_1280X1024P75, + V4L2_DV_BT_DMT_1280X1024P85, + V4L2_DV_BT_DMT_1280X1024P120_RB, + V4L2_DV_BT_DMT_1360X768P60, + V4L2_DV_BT_DMT_1360X768P120_RB, + V4L2_DV_BT_DMT_1366X768P60, + V4L2_DV_BT_DMT_1366X768P60_RB, + V4L2_DV_BT_DMT_1400X1050P60_RB, + V4L2_DV_BT_DMT_1400X1050P60, + V4L2_DV_BT_DMT_1400X1050P75, + V4L2_DV_BT_DMT_1400X1050P85, + V4L2_DV_BT_DMT_1400X1050P120_RB, + V4L2_DV_BT_DMT_1440X900P60_RB, + V4L2_DV_BT_DMT_1440X900P60, + V4L2_DV_BT_DMT_1440X900P75, + V4L2_DV_BT_DMT_1440X900P85, + V4L2_DV_BT_DMT_1440X900P120_RB, + V4L2_DV_BT_DMT_1600X900P60_RB, + V4L2_DV_BT_DMT_1600X1200P60, + V4L2_DV_BT_DMT_1600X1200P65, + V4L2_DV_BT_DMT_1600X1200P70, + V4L2_DV_BT_DMT_1600X1200P75, + V4L2_DV_BT_DMT_1600X1200P85, + V4L2_DV_BT_DMT_1600X1200P120_RB, + V4L2_DV_BT_DMT_1680X1050P60_RB, + V4L2_DV_BT_DMT_1680X1050P60, + V4L2_DV_BT_DMT_1680X1050P75, + V4L2_DV_BT_DMT_1680X1050P85, + V4L2_DV_BT_DMT_1680X1050P120_RB, + V4L2_DV_BT_DMT_1792X1344P60, + V4L2_DV_BT_DMT_1792X1344P75, + V4L2_DV_BT_DMT_1792X1344P120_RB, + V4L2_DV_BT_DMT_1856X1392P60, + V4L2_DV_BT_DMT_1856X1392P75, + V4L2_DV_BT_DMT_1856X1392P120_RB, + V4L2_DV_BT_DMT_1920X1200P60_RB, + V4L2_DV_BT_DMT_1920X1200P60, + V4L2_DV_BT_DMT_1920X1200P75, + V4L2_DV_BT_DMT_1920X1200P85, + V4L2_DV_BT_DMT_1920X1200P120_RB, + V4L2_DV_BT_DMT_1920X1440P60, + V4L2_DV_BT_DMT_1920X1440P75, + V4L2_DV_BT_DMT_1920X1440P120_RB, + V4L2_DV_BT_DMT_2048X1152P60_RB, + V4L2_DV_BT_DMT_2560X1600P60_RB, + V4L2_DV_BT_DMT_2560X1600P60, + V4L2_DV_BT_DMT_2560X1600P75, + V4L2_DV_BT_DMT_2560X1600P85, + V4L2_DV_BT_DMT_2560X1600P120_RB, + { } +}; +EXPORT_SYMBOL_GPL(v4l2_dv_timings_presets); + +bool v4l2_valid_dv_timings(const struct v4l2_dv_timings *t, + const struct v4l2_dv_timings_cap *dvcap, + v4l2_check_dv_timings_fnc fnc, + void *fnc_handle) +{ + const struct v4l2_bt_timings *bt = &t->bt; + const struct v4l2_bt_timings_cap *cap = &dvcap->bt; + u32 caps = cap->capabilities; + + if (t->type != V4L2_DV_BT_656_1120) + return false; + if (t->type != dvcap->type || + bt->height < cap->min_height || + bt->height > cap->max_height || + bt->width < cap->min_width || + bt->width > cap->max_width || + bt->pixelclock < cap->min_pixelclock || + bt->pixelclock > cap->max_pixelclock || + (cap->standards && !(bt->standards & cap->standards)) || + (bt->interlaced && !(caps & V4L2_DV_BT_CAP_INTERLACED)) || + (!bt->interlaced && !(caps & V4L2_DV_BT_CAP_PROGRESSIVE))) + return false; + return fnc == NULL || fnc(t, fnc_handle); +} +EXPORT_SYMBOL_GPL(v4l2_valid_dv_timings); + +int v4l2_enum_dv_timings_cap(struct v4l2_enum_dv_timings *t, + const struct v4l2_dv_timings_cap *cap, + v4l2_check_dv_timings_fnc fnc, + void *fnc_handle) +{ + u32 i, idx; + + memset(t->reserved, 0, sizeof(t->reserved)); + for (i = idx = 0; v4l2_dv_timings_presets[i].bt.width; i++) { + if (v4l2_valid_dv_timings(v4l2_dv_timings_presets + i, cap, + fnc, fnc_handle) && + idx++ == t->index) { + t->timings = v4l2_dv_timings_presets[i]; + return 0; + } + } + return -EINVAL; +} +EXPORT_SYMBOL_GPL(v4l2_enum_dv_timings_cap); + +bool v4l2_find_dv_timings_cap(struct v4l2_dv_timings *t, + const struct v4l2_dv_timings_cap *cap, + unsigned pclock_delta, + v4l2_check_dv_timings_fnc fnc, + void *fnc_handle) +{ + int i; + + if (!v4l2_valid_dv_timings(t, cap, fnc, fnc_handle)) + return false; + + for (i = 0; i < v4l2_dv_timings_presets[i].bt.width; i++) { + if (v4l2_valid_dv_timings(v4l2_dv_timings_presets + i, cap, + fnc, fnc_handle) && + v4l2_match_dv_timings(t, v4l2_dv_timings_presets + i, + pclock_delta)) { + *t = v4l2_dv_timings_presets[i]; + return true; + } + } + return false; +} +EXPORT_SYMBOL_GPL(v4l2_find_dv_timings_cap); + +/** + * v4l2_match_dv_timings - check if two timings match + * @t1 - compare this v4l2_dv_timings struct... + * @t2 - with this struct. + * @pclock_delta - the allowed pixelclock deviation. + * + * Compare t1 with t2 with a given margin of error for the pixelclock. + */ +bool v4l2_match_dv_timings(const struct v4l2_dv_timings *t1, + const struct v4l2_dv_timings *t2, + unsigned pclock_delta) +{ + if (t1->type != t2->type || t1->type != V4L2_DV_BT_656_1120) + return false; + if (t1->bt.width == t2->bt.width && + t1->bt.height == t2->bt.height && + t1->bt.interlaced == t2->bt.interlaced && + t1->bt.polarities == t2->bt.polarities && + t1->bt.pixelclock >= t2->bt.pixelclock - pclock_delta && + t1->bt.pixelclock <= t2->bt.pixelclock + pclock_delta && + t1->bt.hfrontporch == t2->bt.hfrontporch && + t1->bt.vfrontporch == t2->bt.vfrontporch && + t1->bt.vsync == t2->bt.vsync && + t1->bt.vbackporch == t2->bt.vbackporch && + (!t1->bt.interlaced || + (t1->bt.il_vfrontporch == t2->bt.il_vfrontporch && + t1->bt.il_vsync == t2->bt.il_vsync && + t1->bt.il_vbackporch == t2->bt.il_vbackporch))) + return true; + return false; +} +EXPORT_SYMBOL_GPL(v4l2_match_dv_timings); + +void v4l2_print_dv_timings(const char *dev_prefix, const char *prefix, + const struct v4l2_dv_timings *t, bool detailed) +{ + const struct v4l2_bt_timings *bt = &t->bt; + u32 htot, vtot; + + if (t->type != V4L2_DV_BT_656_1120) + return; + + htot = V4L2_DV_BT_FRAME_WIDTH(bt); + vtot = V4L2_DV_BT_FRAME_HEIGHT(bt); + + if (prefix == NULL) + prefix = ""; + + pr_info("%s: %s%ux%u%s%u (%ux%u)\n", dev_prefix, prefix, + bt->width, bt->height, bt->interlaced ? "i" : "p", + (htot * vtot) > 0 ? ((u32)bt->pixelclock / (htot * vtot)) : 0, + htot, vtot); + + if (!detailed) + return; + + pr_info("%s: horizontal: fp = %u, %ssync = %u, bp = %u\n", + dev_prefix, bt->hfrontporch, + (bt->polarities & V4L2_DV_HSYNC_POS_POL) ? "+" : "-", + bt->hsync, bt->hbackporch); + pr_info("%s: vertical: fp = %u, %ssync = %u, bp = %u\n", + dev_prefix, bt->vfrontporch, + (bt->polarities & V4L2_DV_VSYNC_POS_POL) ? "+" : "-", + bt->vsync, bt->vbackporch); + pr_info("%s: pixelclock: %llu\n", dev_prefix, bt->pixelclock); + pr_info("%s: flags (0x%x):%s%s%s%s\n", dev_prefix, bt->flags, + (bt->flags & V4L2_DV_FL_REDUCED_BLANKING) ? + " REDUCED_BLANKING" : "", + (bt->flags & V4L2_DV_FL_CAN_REDUCE_FPS) ? + " CAN_REDUCE_FPS" : "", + (bt->flags & V4L2_DV_FL_REDUCED_FPS) ? + " REDUCED_FPS" : "", + (bt->flags & V4L2_DV_FL_HALF_LINE) ? + " HALF_LINE" : ""); + pr_info("%s: standards (0x%x):%s%s%s%s\n", dev_prefix, bt->standards, + (bt->standards & V4L2_DV_BT_STD_CEA861) ? " CEA" : "", + (bt->standards & V4L2_DV_BT_STD_DMT) ? " DMT" : "", + (bt->standards & V4L2_DV_BT_STD_CVT) ? " CVT" : "", + (bt->standards & V4L2_DV_BT_STD_GTF) ? " GTF" : ""); +} +EXPORT_SYMBOL_GPL(v4l2_print_dv_timings); + +/* + * CVT defines + * Based on Coordinated Video Timings Standard + * version 1.1 September 10, 2003 + */ + +#define CVT_PXL_CLK_GRAN 250000 /* pixel clock granularity */ + +/* Normal blanking */ +#define CVT_MIN_V_BPORCH 7 /* lines */ +#define CVT_MIN_V_PORCH_RND 3 /* lines */ +#define CVT_MIN_VSYNC_BP 550 /* min time of vsync + back porch (us) */ + +/* Normal blanking for CVT uses GTF to calculate horizontal blanking */ +#define CVT_CELL_GRAN 8 /* character cell granularity */ +#define CVT_M 600 /* blanking formula gradient */ +#define CVT_C 40 /* blanking formula offset */ +#define CVT_K 128 /* blanking formula scaling factor */ +#define CVT_J 20 /* blanking formula scaling factor */ +#define CVT_C_PRIME (((CVT_C - CVT_J) * CVT_K / 256) + CVT_J) +#define CVT_M_PRIME (CVT_K * CVT_M / 256) + +/* Reduced Blanking */ +#define CVT_RB_MIN_V_BPORCH 7 /* lines */ +#define CVT_RB_V_FPORCH 3 /* lines */ +#define CVT_RB_MIN_V_BLANK 460 /* us */ +#define CVT_RB_H_SYNC 32 /* pixels */ +#define CVT_RB_H_BPORCH 80 /* pixels */ +#define CVT_RB_H_BLANK 160 /* pixels */ + +/** v4l2_detect_cvt - detect if the given timings follow the CVT standard + * @frame_height - the total height of the frame (including blanking) in lines. + * @hfreq - the horizontal frequency in Hz. + * @vsync - the height of the vertical sync in lines. + * @polarities - the horizontal and vertical polarities (same as struct + * v4l2_bt_timings polarities). + * @fmt - the resulting timings. + * + * This function will attempt to detect if the given values correspond to a + * valid CVT format. If so, then it will return true, and fmt will be filled + * in with the found CVT timings. + */ +bool v4l2_detect_cvt(unsigned frame_height, unsigned hfreq, unsigned vsync, + u32 polarities, struct v4l2_dv_timings *fmt) +{ + int v_fp, v_bp, h_fp, h_bp, hsync; + int frame_width, image_height, image_width; + bool reduced_blanking; + unsigned pix_clk; + + if (vsync < 4 || vsync > 7) + return false; + + if (polarities == V4L2_DV_VSYNC_POS_POL) + reduced_blanking = false; + else if (polarities == V4L2_DV_HSYNC_POS_POL) + reduced_blanking = true; + else + return false; + + /* Vertical */ + if (reduced_blanking) { + v_fp = CVT_RB_V_FPORCH; + v_bp = (CVT_RB_MIN_V_BLANK * hfreq + 1999999) / 1000000; + v_bp -= vsync + v_fp; + + if (v_bp < CVT_RB_MIN_V_BPORCH) + v_bp = CVT_RB_MIN_V_BPORCH; + } else { + v_fp = CVT_MIN_V_PORCH_RND; + v_bp = (CVT_MIN_VSYNC_BP * hfreq + 1999999) / 1000000 - vsync; + + if (v_bp < CVT_MIN_V_BPORCH) + v_bp = CVT_MIN_V_BPORCH; + } + image_height = (frame_height - v_fp - vsync - v_bp + 1) & ~0x1; + + /* Aspect ratio based on vsync */ + switch (vsync) { + case 4: + image_width = (image_height * 4) / 3; + break; + case 5: + image_width = (image_height * 16) / 9; + break; + case 6: + image_width = (image_height * 16) / 10; + break; + case 7: + /* special case */ + if (image_height == 1024) + image_width = (image_height * 5) / 4; + else if (image_height == 768) + image_width = (image_height * 15) / 9; + else + return false; + break; + default: + return false; + } + + image_width = image_width & ~7; + + /* Horizontal */ + if (reduced_blanking) { + pix_clk = (image_width + CVT_RB_H_BLANK) * hfreq; + pix_clk = (pix_clk / CVT_PXL_CLK_GRAN) * CVT_PXL_CLK_GRAN; + + h_bp = CVT_RB_H_BPORCH; + hsync = CVT_RB_H_SYNC; + h_fp = CVT_RB_H_BLANK - h_bp - hsync; + + frame_width = image_width + CVT_RB_H_BLANK; + } else { + unsigned ideal_duty_cycle_per_myriad = + 100 * CVT_C_PRIME - (CVT_M_PRIME * 100000) / hfreq; + int h_blank; + + if (ideal_duty_cycle_per_myriad < 2000) + ideal_duty_cycle_per_myriad = 2000; + + h_blank = image_width * ideal_duty_cycle_per_myriad / + (10000 - ideal_duty_cycle_per_myriad); + h_blank = (h_blank / (2 * CVT_CELL_GRAN)) * 2 * CVT_CELL_GRAN; + + pix_clk = (image_width + h_blank) * hfreq; + pix_clk = (pix_clk / CVT_PXL_CLK_GRAN) * CVT_PXL_CLK_GRAN; + + h_bp = h_blank / 2; + frame_width = image_width + h_blank; + + hsync = (frame_width * 8 + 50) / 100; + hsync = hsync - hsync % CVT_CELL_GRAN; + h_fp = h_blank - hsync - h_bp; + } + + fmt->type = V4L2_DV_BT_656_1120; + fmt->bt.polarities = polarities; + fmt->bt.width = image_width; + fmt->bt.height = image_height; + fmt->bt.hfrontporch = h_fp; + fmt->bt.vfrontporch = v_fp; + fmt->bt.hsync = hsync; + fmt->bt.vsync = vsync; + fmt->bt.hbackporch = frame_width - image_width - h_fp - hsync; + fmt->bt.vbackporch = frame_height - image_height - v_fp - vsync; + fmt->bt.pixelclock = pix_clk; + fmt->bt.standards = V4L2_DV_BT_STD_CVT; + if (reduced_blanking) + fmt->bt.flags |= V4L2_DV_FL_REDUCED_BLANKING; + return true; +} +EXPORT_SYMBOL_GPL(v4l2_detect_cvt); + +/* + * GTF defines + * Based on Generalized Timing Formula Standard + * Version 1.1 September 2, 1999 + */ + +#define GTF_PXL_CLK_GRAN 250000 /* pixel clock granularity */ + +#define GTF_MIN_VSYNC_BP 550 /* min time of vsync + back porch (us) */ +#define GTF_V_FP 1 /* vertical front porch (lines) */ +#define GTF_CELL_GRAN 8 /* character cell granularity */ + +/* Default */ +#define GTF_D_M 600 /* blanking formula gradient */ +#define GTF_D_C 40 /* blanking formula offset */ +#define GTF_D_K 128 /* blanking formula scaling factor */ +#define GTF_D_J 20 /* blanking formula scaling factor */ +#define GTF_D_C_PRIME ((((GTF_D_C - GTF_D_J) * GTF_D_K) / 256) + GTF_D_J) +#define GTF_D_M_PRIME ((GTF_D_K * GTF_D_M) / 256) + +/* Secondary */ +#define GTF_S_M 3600 /* blanking formula gradient */ +#define GTF_S_C 40 /* blanking formula offset */ +#define GTF_S_K 128 /* blanking formula scaling factor */ +#define GTF_S_J 35 /* blanking formula scaling factor */ +#define GTF_S_C_PRIME ((((GTF_S_C - GTF_S_J) * GTF_S_K) / 256) + GTF_S_J) +#define GTF_S_M_PRIME ((GTF_S_K * GTF_S_M) / 256) + +/** v4l2_detect_gtf - detect if the given timings follow the GTF standard + * @frame_height - the total height of the frame (including blanking) in lines. + * @hfreq - the horizontal frequency in Hz. + * @vsync - the height of the vertical sync in lines. + * @polarities - the horizontal and vertical polarities (same as struct + * v4l2_bt_timings polarities). + * @aspect - preferred aspect ratio. GTF has no method of determining the + * aspect ratio in order to derive the image width from the + * image height, so it has to be passed explicitly. Usually + * the native screen aspect ratio is used for this. If it + * is not filled in correctly, then 16:9 will be assumed. + * @fmt - the resulting timings. + * + * This function will attempt to detect if the given values correspond to a + * valid GTF format. If so, then it will return true, and fmt will be filled + * in with the found GTF timings. + */ +bool v4l2_detect_gtf(unsigned frame_height, + unsigned hfreq, + unsigned vsync, + u32 polarities, + struct v4l2_fract aspect, + struct v4l2_dv_timings *fmt) +{ + int pix_clk; + int v_fp, v_bp, h_fp, hsync; + int frame_width, image_height, image_width; + bool default_gtf; + int h_blank; + + if (vsync != 3) + return false; + + if (polarities == V4L2_DV_VSYNC_POS_POL) + default_gtf = true; + else if (polarities == V4L2_DV_HSYNC_POS_POL) + default_gtf = false; + else + return false; + + /* Vertical */ + v_fp = GTF_V_FP; + v_bp = (GTF_MIN_VSYNC_BP * hfreq + 999999) / 1000000 - vsync; + image_height = (frame_height - v_fp - vsync - v_bp + 1) & ~0x1; + + if (aspect.numerator == 0 || aspect.denominator == 0) { + aspect.numerator = 16; + aspect.denominator = 9; + } + image_width = ((image_height * aspect.numerator) / aspect.denominator); + + /* Horizontal */ + if (default_gtf) + h_blank = ((image_width * GTF_D_C_PRIME * hfreq) - + (image_width * GTF_D_M_PRIME * 1000) + + (hfreq * (100 - GTF_D_C_PRIME) + GTF_D_M_PRIME * 1000) / 2) / + (hfreq * (100 - GTF_D_C_PRIME) + GTF_D_M_PRIME * 1000); + else + h_blank = ((image_width * GTF_S_C_PRIME * hfreq) - + (image_width * GTF_S_M_PRIME * 1000) + + (hfreq * (100 - GTF_S_C_PRIME) + GTF_S_M_PRIME * 1000) / 2) / + (hfreq * (100 - GTF_S_C_PRIME) + GTF_S_M_PRIME * 1000); + + h_blank = h_blank - h_blank % (2 * GTF_CELL_GRAN); + frame_width = image_width + h_blank; + + pix_clk = (image_width + h_blank) * hfreq; + pix_clk = pix_clk / GTF_PXL_CLK_GRAN * GTF_PXL_CLK_GRAN; + + hsync = (frame_width * 8 + 50) / 100; + hsync = hsync - hsync % GTF_CELL_GRAN; + + h_fp = h_blank / 2 - hsync; + + fmt->type = V4L2_DV_BT_656_1120; + fmt->bt.polarities = polarities; + fmt->bt.width = image_width; + fmt->bt.height = image_height; + fmt->bt.hfrontporch = h_fp; + fmt->bt.vfrontporch = v_fp; + fmt->bt.hsync = hsync; + fmt->bt.vsync = vsync; + fmt->bt.hbackporch = frame_width - image_width - h_fp - hsync; + fmt->bt.vbackporch = frame_height - image_height - v_fp - vsync; + fmt->bt.pixelclock = pix_clk; + fmt->bt.standards = V4L2_DV_BT_STD_GTF; + if (!default_gtf) + fmt->bt.flags |= V4L2_DV_FL_REDUCED_BLANKING; + return true; +} +EXPORT_SYMBOL_GPL(v4l2_detect_gtf); + +/** v4l2_calc_aspect_ratio - calculate the aspect ratio based on bytes + * 0x15 and 0x16 from the EDID. + * @hor_landscape - byte 0x15 from the EDID. + * @vert_portrait - byte 0x16 from the EDID. + * + * Determines the aspect ratio from the EDID. + * See VESA Enhanced EDID standard, release A, rev 2, section 3.6.2: + * "Horizontal and Vertical Screen Size or Aspect Ratio" + */ +struct v4l2_fract v4l2_calc_aspect_ratio(u8 hor_landscape, u8 vert_portrait) +{ + struct v4l2_fract aspect = { 16, 9 }; + u32 tmp; + u8 ratio; + + /* Nothing filled in, fallback to 16:9 */ + if (!hor_landscape && !vert_portrait) + return aspect; + /* Both filled in, so they are interpreted as the screen size in cm */ + if (hor_landscape && vert_portrait) { + aspect.numerator = hor_landscape; + aspect.denominator = vert_portrait; + return aspect; + } + /* Only one is filled in, so interpret them as a ratio: + (val + 99) / 100 */ + ratio = hor_landscape | vert_portrait; + /* Change some rounded values into the exact aspect ratio */ + if (ratio == 79) { + aspect.numerator = 16; + aspect.denominator = 9; + } else if (ratio == 34) { + aspect.numerator = 4; + aspect.numerator = 3; + } else if (ratio == 68) { + aspect.numerator = 15; + aspect.numerator = 9; + } else { + aspect.numerator = hor_landscape + 99; + aspect.denominator = 100; + } + if (hor_landscape) + return aspect; + /* The aspect ratio is for portrait, so swap numerator and denominator */ + tmp = aspect.denominator; + aspect.denominator = aspect.numerator; + aspect.numerator = tmp; + return aspect; +} +EXPORT_SYMBOL_GPL(v4l2_calc_aspect_ratio); diff --git a/drivers/media/v4l2-core/v4l2-mem2mem.c b/drivers/media/v4l2-core/v4l2-mem2mem.c index e96497f7c3ed..7c4371288215 100644 --- a/drivers/media/v4l2-core/v4l2-mem2mem.c +++ b/drivers/media/v4l2-core/v4l2-mem2mem.c @@ -196,6 +196,10 @@ static void v4l2_m2m_try_run(struct v4l2_m2m_dev *m2m_dev) * 2) at least one destination buffer has to be queued, * 3) streaming has to be on. * + * If a queue is buffered (for example a decoder hardware ringbuffer that has + * to be drained before doing streamoff), allow scheduling without v4l2 buffers + * on that queue. + * * There may also be additional, custom requirements. In such case the driver * should supply a custom callback (job_ready in v4l2_m2m_ops) that should * return 1 if the instance is ready. @@ -224,7 +228,8 @@ static void v4l2_m2m_try_schedule(struct v4l2_m2m_ctx *m2m_ctx) } spin_lock_irqsave(&m2m_ctx->out_q_ctx.rdy_spinlock, flags_out); - if (list_empty(&m2m_ctx->out_q_ctx.rdy_queue)) { + if (list_empty(&m2m_ctx->out_q_ctx.rdy_queue) + && !m2m_ctx->out_q_ctx.buffered) { spin_unlock_irqrestore(&m2m_ctx->out_q_ctx.rdy_spinlock, flags_out); spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags_job); @@ -232,7 +237,8 @@ static void v4l2_m2m_try_schedule(struct v4l2_m2m_ctx *m2m_ctx) return; } spin_lock_irqsave(&m2m_ctx->cap_q_ctx.rdy_spinlock, flags_cap); - if (list_empty(&m2m_ctx->cap_q_ctx.rdy_queue)) { + if (list_empty(&m2m_ctx->cap_q_ctx.rdy_queue) + && !m2m_ctx->cap_q_ctx.buffered) { spin_unlock_irqrestore(&m2m_ctx->cap_q_ctx.rdy_spinlock, flags_cap); spin_unlock_irqrestore(&m2m_ctx->out_q_ctx.rdy_spinlock, @@ -260,6 +266,39 @@ static void v4l2_m2m_try_schedule(struct v4l2_m2m_ctx *m2m_ctx) } /** + * v4l2_m2m_cancel_job() - cancel pending jobs for the context + * + * In case of streamoff or release called on any context, + * 1] If the context is currently running, then abort job will be called + * 2] If the context is queued, then the context will be removed from + * the job_queue + */ +static void v4l2_m2m_cancel_job(struct v4l2_m2m_ctx *m2m_ctx) +{ + struct v4l2_m2m_dev *m2m_dev; + unsigned long flags; + + m2m_dev = m2m_ctx->m2m_dev; + spin_lock_irqsave(&m2m_dev->job_spinlock, flags); + if (m2m_ctx->job_flags & TRANS_RUNNING) { + spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); + m2m_dev->m2m_ops->job_abort(m2m_ctx->priv); + dprintk("m2m_ctx %p running, will wait to complete", m2m_ctx); + wait_event(m2m_ctx->finished, + !(m2m_ctx->job_flags & TRANS_RUNNING)); + } else if (m2m_ctx->job_flags & TRANS_QUEUED) { + list_del(&m2m_ctx->queue); + m2m_ctx->job_flags &= ~(TRANS_QUEUED | TRANS_RUNNING); + spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); + dprintk("m2m_ctx: %p had been on queue and was removed\n", + m2m_ctx); + } else { + /* Do nothing, was not on queue/running */ + spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); + } +} + +/** * v4l2_m2m_job_finish() - inform the framework that a job has been finished * and have it clean up * @@ -430,6 +469,9 @@ int v4l2_m2m_streamoff(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, unsigned long flags_job, flags; int ret; + /* wait until the current context is dequeued from job_queue */ + v4l2_m2m_cancel_job(m2m_ctx); + q_ctx = get_queue_ctx(m2m_ctx, type); ret = vb2_streamoff(&q_ctx->q, type); if (ret) @@ -652,27 +694,8 @@ EXPORT_SYMBOL_GPL(v4l2_m2m_ctx_init); */ void v4l2_m2m_ctx_release(struct v4l2_m2m_ctx *m2m_ctx) { - struct v4l2_m2m_dev *m2m_dev; - unsigned long flags; - - m2m_dev = m2m_ctx->m2m_dev; - - spin_lock_irqsave(&m2m_dev->job_spinlock, flags); - if (m2m_ctx->job_flags & TRANS_RUNNING) { - spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); - m2m_dev->m2m_ops->job_abort(m2m_ctx->priv); - dprintk("m2m_ctx %p running, will wait to complete", m2m_ctx); - wait_event(m2m_ctx->finished, !(m2m_ctx->job_flags & TRANS_RUNNING)); - } else if (m2m_ctx->job_flags & TRANS_QUEUED) { - list_del(&m2m_ctx->queue); - m2m_ctx->job_flags &= ~(TRANS_QUEUED | TRANS_RUNNING); - spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); - dprintk("m2m_ctx: %p had been on queue and was removed\n", - m2m_ctx); - } else { - /* Do nothing, was not on queue/running */ - spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); - } + /* wait until the current context is dequeued from job_queue */ + v4l2_m2m_cancel_job(m2m_ctx); vb2_queue_release(&m2m_ctx->cap_q_ctx.q); vb2_queue_release(&m2m_ctx->out_q_ctx.q); diff --git a/drivers/media/v4l2-core/v4l2-of.c b/drivers/media/v4l2-core/v4l2-of.c index aa59639d013c..a6478dca0cde 100644 --- a/drivers/media/v4l2-core/v4l2-of.c +++ b/drivers/media/v4l2-core/v4l2-of.c @@ -100,6 +100,10 @@ static void v4l2_of_parse_parallel_bus(const struct device_node *node, if (!of_property_read_u32(node, "data-shift", &v)) bus->data_shift = v; + if (!of_property_read_u32(node, "sync-on-green-active", &v)) + flags |= v ? V4L2_MBUS_VIDEO_SOG_ACTIVE_HIGH : + V4L2_MBUS_VIDEO_SOG_ACTIVE_LOW; + bus->flags = flags; } @@ -173,12 +177,8 @@ struct device_node *v4l2_of_get_next_endpoint(const struct device_node *parent, if (node) parent = node; - for_each_child_of_node(parent, node) { - if (!of_node_cmp(node->name, "port")) { - port = node; - break; - } - } + port = of_get_child_by_name(parent, "port"); + if (port) { /* Found a port, get an endpoint. */ endpoint = of_get_next_child(port, NULL); @@ -190,6 +190,7 @@ struct device_node *v4l2_of_get_next_endpoint(const struct device_node *parent, if (!endpoint) pr_err("%s(): no endpoint nodes specified for %s\n", __func__, parent->full_name); + of_node_put(node); } else { port = of_get_parent(prev); if (!port) diff --git a/drivers/media/v4l2-core/videobuf2-core.c b/drivers/media/v4l2-core/videobuf2-core.c index 9fc4bab2da97..594c75eab5a5 100644 --- a/drivers/media/v4l2-core/videobuf2-core.c +++ b/drivers/media/v4l2-core/videobuf2-core.c @@ -334,6 +334,41 @@ static int __verify_planes_array(struct vb2_buffer *vb, const struct v4l2_buffer } /** + * __verify_length() - Verify that the bytesused value for each plane fits in + * the plane length and that the data offset doesn't exceed the bytesused value. + */ +static int __verify_length(struct vb2_buffer *vb, const struct v4l2_buffer *b) +{ + unsigned int length; + unsigned int plane; + + if (!V4L2_TYPE_IS_OUTPUT(b->type)) + return 0; + + if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) { + for (plane = 0; plane < vb->num_planes; ++plane) { + length = (b->memory == V4L2_MEMORY_USERPTR) + ? b->m.planes[plane].length + : vb->v4l2_planes[plane].length; + + if (b->m.planes[plane].bytesused > length) + return -EINVAL; + if (b->m.planes[plane].data_offset >= + b->m.planes[plane].bytesused) + return -EINVAL; + } + } else { + length = (b->memory == V4L2_MEMORY_USERPTR) + ? b->length : vb->v4l2_planes[0].length; + + if (b->bytesused > length) + return -EINVAL; + } + + return 0; +} + +/** * __buffer_in_use() - return true if the buffer is in use and * the queue cannot be freed (by the means of REQBUFS(0)) call */ @@ -1167,6 +1202,10 @@ static int __buf_prepare(struct vb2_buffer *vb, const struct v4l2_buffer *b) struct vb2_queue *q = vb->vb2_queue; int ret; + ret = __verify_length(vb, b); + if (ret < 0) + return ret; + switch (q->memory) { case V4L2_MEMORY_MMAP: ret = __qbuf_mmap(vb, b); @@ -1192,108 +1231,31 @@ static int __buf_prepare(struct vb2_buffer *vb, const struct v4l2_buffer *b) return ret; } -/** - * vb2_prepare_buf() - Pass ownership of a buffer from userspace to the kernel - * @q: videobuf2 queue - * @b: buffer structure passed from userspace to vidioc_prepare_buf - * handler in driver - * - * Should be called from vidioc_prepare_buf ioctl handler of a driver. - * This function: - * 1) verifies the passed buffer, - * 2) calls buf_prepare callback in the driver (if provided), in which - * driver-specific buffer initialization can be performed, - * - * The return values from this function are intended to be directly returned - * from vidioc_prepare_buf handler in driver. - */ -int vb2_prepare_buf(struct vb2_queue *q, struct v4l2_buffer *b) -{ - struct vb2_buffer *vb; - int ret; - - if (q->fileio) { - dprintk(1, "%s(): file io in progress\n", __func__); - return -EBUSY; - } - - if (b->type != q->type) { - dprintk(1, "%s(): invalid buffer type\n", __func__); - return -EINVAL; - } - - if (b->index >= q->num_buffers) { - dprintk(1, "%s(): buffer index out of range\n", __func__); - return -EINVAL; - } - - vb = q->bufs[b->index]; - if (NULL == vb) { - /* Should never happen */ - dprintk(1, "%s(): buffer is NULL\n", __func__); - return -EINVAL; - } - - if (b->memory != q->memory) { - dprintk(1, "%s(): invalid memory type\n", __func__); - return -EINVAL; - } - - if (vb->state != VB2_BUF_STATE_DEQUEUED) { - dprintk(1, "%s(): invalid buffer state %d\n", __func__, vb->state); - return -EINVAL; - } - ret = __verify_planes_array(vb, b); - if (ret < 0) - return ret; - ret = __buf_prepare(vb, b); - if (ret < 0) - return ret; - - __fill_v4l2_buffer(vb, b); - - return 0; -} -EXPORT_SYMBOL_GPL(vb2_prepare_buf); - -/** - * vb2_qbuf() - Queue a buffer from userspace - * @q: videobuf2 queue - * @b: buffer structure passed from userspace to vidioc_qbuf handler - * in driver - * - * Should be called from vidioc_qbuf ioctl handler of a driver. - * This function: - * 1) verifies the passed buffer, - * 2) if necessary, calls buf_prepare callback in the driver (if provided), in - * which driver-specific buffer initialization can be performed, - * 3) if streaming is on, queues the buffer in driver by the means of buf_queue - * callback for processing. - * - * The return values from this function are intended to be directly returned - * from vidioc_qbuf handler in driver. - */ -int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b) +static int vb2_queue_or_prepare_buf(struct vb2_queue *q, struct v4l2_buffer *b, + const char *opname, + int (*handler)(struct vb2_queue *, + struct v4l2_buffer *, + struct vb2_buffer *)) { struct rw_semaphore *mmap_sem = NULL; struct vb2_buffer *vb; - int ret = 0; + int ret; /* - * In case of user pointer buffers vb2 allocator needs to get direct - * access to userspace pages. This requires getting read access on - * mmap semaphore in the current process structure. The same - * semaphore is taken before calling mmap operation, while both mmap - * and qbuf are called by the driver or v4l2 core with driver's lock - * held. To avoid a AB-BA deadlock (mmap_sem then driver's lock in - * mmap and driver's lock then mmap_sem in qbuf) the videobuf2 core - * release driver's lock, takes mmap_sem and then takes again driver's - * lock. + * In case of user pointer buffers vb2 allocators need to get direct + * access to userspace pages. This requires getting the mmap semaphore + * for read access in the current process structure. The same semaphore + * is taken before calling mmap operation, while both qbuf/prepare_buf + * and mmap are called by the driver or v4l2 core with the driver's lock + * held. To avoid an AB-BA deadlock (mmap_sem then driver's lock in mmap + * and driver's lock then mmap_sem in qbuf/prepare_buf) the videobuf2 + * core releases the driver's lock, takes mmap_sem and then takes the + * driver's lock again. * - * To avoid race with other vb2 calls, which might be called after - * releasing driver's lock, this operation is performed at the - * beggining of qbuf processing. This way the queue status is - * consistent after getting driver's lock back. + * To avoid racing with other vb2 calls, which might be called after + * releasing the driver's lock, this operation is performed at the + * beginning of qbuf/prepare_buf processing. This way the queue status + * is consistent after getting the driver's lock back. */ if (q->memory == V4L2_MEMORY_USERPTR) { mmap_sem = ¤t->mm->mmap_sem; @@ -1303,19 +1265,19 @@ int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b) } if (q->fileio) { - dprintk(1, "qbuf: file io in progress\n"); + dprintk(1, "%s(): file io in progress\n", opname); ret = -EBUSY; goto unlock; } if (b->type != q->type) { - dprintk(1, "qbuf: invalid buffer type\n"); + dprintk(1, "%s(): invalid buffer type\n", opname); ret = -EINVAL; goto unlock; } if (b->index >= q->num_buffers) { - dprintk(1, "qbuf: buffer index out of range\n"); + dprintk(1, "%s(): buffer index out of range\n", opname); ret = -EINVAL; goto unlock; } @@ -1323,31 +1285,83 @@ int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b) vb = q->bufs[b->index]; if (NULL == vb) { /* Should never happen */ - dprintk(1, "qbuf: buffer is NULL\n"); + dprintk(1, "%s(): buffer is NULL\n", opname); ret = -EINVAL; goto unlock; } if (b->memory != q->memory) { - dprintk(1, "qbuf: invalid memory type\n"); + dprintk(1, "%s(): invalid memory type\n", opname); ret = -EINVAL; goto unlock; } + ret = __verify_planes_array(vb, b); if (ret) goto unlock; + ret = handler(q, b, vb); + if (ret) + goto unlock; + + /* Fill buffer information for the userspace */ + __fill_v4l2_buffer(vb, b); + + dprintk(1, "%s() of buffer %d succeeded\n", opname, vb->v4l2_buf.index); +unlock: + if (mmap_sem) + up_read(mmap_sem); + return ret; +} + +static int __vb2_prepare_buf(struct vb2_queue *q, struct v4l2_buffer *b, + struct vb2_buffer *vb) +{ + if (vb->state != VB2_BUF_STATE_DEQUEUED) { + dprintk(1, "%s(): invalid buffer state %d\n", __func__, + vb->state); + return -EINVAL; + } + + return __buf_prepare(vb, b); +} + +/** + * vb2_prepare_buf() - Pass ownership of a buffer from userspace to the kernel + * @q: videobuf2 queue + * @b: buffer structure passed from userspace to vidioc_prepare_buf + * handler in driver + * + * Should be called from vidioc_prepare_buf ioctl handler of a driver. + * This function: + * 1) verifies the passed buffer, + * 2) calls buf_prepare callback in the driver (if provided), in which + * driver-specific buffer initialization can be performed, + * + * The return values from this function are intended to be directly returned + * from vidioc_prepare_buf handler in driver. + */ +int vb2_prepare_buf(struct vb2_queue *q, struct v4l2_buffer *b) +{ + return vb2_queue_or_prepare_buf(q, b, "prepare_buf", __vb2_prepare_buf); +} +EXPORT_SYMBOL_GPL(vb2_prepare_buf); + +static int __vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b, + struct vb2_buffer *vb) +{ + int ret; + switch (vb->state) { case VB2_BUF_STATE_DEQUEUED: ret = __buf_prepare(vb, b); if (ret) - goto unlock; + return ret; case VB2_BUF_STATE_PREPARED: break; default: dprintk(1, "qbuf: buffer already in use\n"); - ret = -EINVAL; - goto unlock; + return -EINVAL; } /* @@ -1364,14 +1378,29 @@ int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b) if (q->streaming) __enqueue_in_driver(vb); - /* Fill buffer information for the userspace */ - __fill_v4l2_buffer(vb, b); + return 0; +} - dprintk(1, "qbuf of buffer %d succeeded\n", vb->v4l2_buf.index); -unlock: - if (mmap_sem) - up_read(mmap_sem); - return ret; +/** + * vb2_qbuf() - Queue a buffer from userspace + * @q: videobuf2 queue + * @b: buffer structure passed from userspace to vidioc_qbuf handler + * in driver + * + * Should be called from vidioc_qbuf ioctl handler of a driver. + * This function: + * 1) verifies the passed buffer, + * 2) if necessary, calls buf_prepare callback in the driver (if provided), in + * which driver-specific buffer initialization can be performed, + * 3) if streaming is on, queues the buffer in driver by the means of buf_queue + * callback for processing. + * + * The return values from this function are intended to be directly returned + * from vidioc_qbuf handler in driver. + */ +int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b) +{ + return vb2_queue_or_prepare_buf(q, b, "qbuf", __vb2_qbuf); } EXPORT_SYMBOL_GPL(vb2_qbuf); @@ -2578,8 +2607,15 @@ EXPORT_SYMBOL_GPL(vb2_ioctl_expbuf); int vb2_fop_mmap(struct file *file, struct vm_area_struct *vma) { struct video_device *vdev = video_devdata(file); + struct mutex *lock = vdev->queue->lock ? vdev->queue->lock : vdev->lock; + int err; - return vb2_mmap(vdev->queue, vma); + if (lock && mutex_lock_interruptible(lock)) + return -ERESTARTSYS; + err = vb2_mmap(vdev->queue, vma); + if (lock) + mutex_unlock(lock); + return err; } EXPORT_SYMBOL_GPL(vb2_fop_mmap); @@ -2685,8 +2721,15 @@ unsigned long vb2_fop_get_unmapped_area(struct file *file, unsigned long addr, unsigned long len, unsigned long pgoff, unsigned long flags) { struct video_device *vdev = video_devdata(file); + struct mutex *lock = vdev->queue->lock ? vdev->queue->lock : vdev->lock; + int ret; - return vb2_get_unmapped_area(vdev->queue, addr, len, pgoff, flags); + if (lock && mutex_lock_interruptible(lock)) + return -ERESTARTSYS; + ret = vb2_get_unmapped_area(vdev->queue, addr, len, pgoff, flags); + if (lock) + mutex_unlock(lock); + return ret; } EXPORT_SYMBOL_GPL(vb2_fop_get_unmapped_area); #endif diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig index ae0abc350e34..46f1e619cbd8 100644 --- a/drivers/staging/media/Kconfig +++ b/drivers/staging/media/Kconfig @@ -29,6 +29,8 @@ source "drivers/staging/media/dt3155v4l/Kconfig" source "drivers/staging/media/go7007/Kconfig" +source "drivers/staging/media/msi3101/Kconfig" + source "drivers/staging/media/solo6x10/Kconfig" # Keep LIRC at the end, as it has sub-menus diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile index 2b97cae99499..eb7f30b1ccd8 100644 --- a/drivers/staging/media/Makefile +++ b/drivers/staging/media/Makefile @@ -4,4 +4,5 @@ obj-$(CONFIG_LIRC_STAGING) += lirc/ obj-$(CONFIG_SOLO6X10) += solo6x10/ obj-$(CONFIG_VIDEO_DT3155) += dt3155v4l/ obj-$(CONFIG_VIDEO_GO7007) += go7007/ +obj-$(CONFIG_USB_MSI3101) += msi3101/ obj-$(CONFIG_VIDEO_DM365_VPFE) += davinci_vpfe/ diff --git a/drivers/staging/media/lirc/lirc_igorplugusb.c b/drivers/staging/media/lirc/lirc_igorplugusb.c index 2faa391006db..28c8b0bcf5b2 100644 --- a/drivers/staging/media/lirc/lirc_igorplugusb.c +++ b/drivers/staging/media/lirc/lirc_igorplugusb.c @@ -240,10 +240,6 @@ static int unregister_from_lirc(struct igorplug *ir) dprintk(DRIVER_NAME "[%d]: calling lirc_unregister_driver\n", devnum); lirc_unregister_driver(d->minor); - kfree(d); - ir->d = NULL; - kfree(ir); - return devnum; } @@ -377,20 +373,16 @@ static int igorplugusb_remote_poll(void *data, struct lirc_buffer *buf) return -ENODATA; } - - static int igorplugusb_remote_probe(struct usb_interface *intf, const struct usb_device_id *id) { - struct usb_device *dev = NULL; + struct usb_device *dev; struct usb_host_interface *idesc = NULL; struct usb_endpoint_descriptor *ep; struct igorplug *ir = NULL; struct lirc_driver *driver = NULL; int devnum, pipe, maxp; - int minor = 0; char buf[63], name[128] = ""; - int mem_failure = 0; int ret; dprintk(DRIVER_NAME ": usb probe called.\n"); @@ -416,24 +408,18 @@ static int igorplugusb_remote_probe(struct usb_interface *intf, dprintk(DRIVER_NAME "[%d]: bytes_in_key=%zu maxp=%d\n", devnum, CODE_LENGTH, maxp); - mem_failure = 0; - ir = kzalloc(sizeof(struct igorplug), GFP_KERNEL); - if (!ir) { - mem_failure = 1; - goto mem_failure_switch; - } - driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL); - if (!driver) { - mem_failure = 2; - goto mem_failure_switch; - } + ir = devm_kzalloc(&intf->dev, sizeof(*ir), GFP_KERNEL); + if (!ir) + return -ENOMEM; + + driver = devm_kzalloc(&intf->dev, sizeof(*driver), GFP_KERNEL); + if (!driver) + return -ENOMEM; ir->buf_in = usb_alloc_coherent(dev, DEVICE_BUFLEN + DEVICE_HEADERLEN, GFP_ATOMIC, &ir->dma_in); - if (!ir->buf_in) { - mem_failure = 3; - goto mem_failure_switch; - } + if (!ir->buf_in) + return -ENOMEM; strcpy(driver->name, DRIVER_NAME " "); driver->minor = -1; @@ -449,27 +435,14 @@ static int igorplugusb_remote_probe(struct usb_interface *intf, driver->dev = &intf->dev; driver->owner = THIS_MODULE; - minor = lirc_register_driver(driver); - if (minor < 0) - mem_failure = 9; - -mem_failure_switch: - - switch (mem_failure) { - case 9: + ret = lirc_register_driver(driver); + if (ret < 0) { usb_free_coherent(dev, DEVICE_BUFLEN + DEVICE_HEADERLEN, ir->buf_in, ir->dma_in); - case 3: - kfree(driver); - case 2: - kfree(ir); - case 1: - printk(DRIVER_NAME "[%d]: out of memory (code=%d)\n", - devnum, mem_failure); - return -ENOMEM; + return ret; } - driver->minor = minor; + driver->minor = ret; ir->d = driver; ir->devnum = devnum; ir->usbdev = dev; @@ -502,7 +475,6 @@ mem_failure_switch: return 0; } - static void igorplugusb_remote_disconnect(struct usb_interface *intf) { struct usb_device *usbdev = interface_to_usbdev(intf); diff --git a/drivers/staging/media/msi3101/Kconfig b/drivers/staging/media/msi3101/Kconfig new file mode 100644 index 000000000000..b94a95a597d6 --- /dev/null +++ b/drivers/staging/media/msi3101/Kconfig @@ -0,0 +1,3 @@ +config USB_MSI3101 + tristate "Mirics MSi3101 SDR Dongle" + depends on USB && VIDEO_DEV && VIDEO_V4L2 diff --git a/drivers/staging/media/msi3101/Makefile b/drivers/staging/media/msi3101/Makefile new file mode 100644 index 000000000000..3730654b0eb9 --- /dev/null +++ b/drivers/staging/media/msi3101/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_USB_MSI3101) += sdr-msi3101.o diff --git a/drivers/staging/media/msi3101/sdr-msi3101.c b/drivers/staging/media/msi3101/sdr-msi3101.c new file mode 100644 index 000000000000..24c7b70a6cbf --- /dev/null +++ b/drivers/staging/media/msi3101/sdr-msi3101.c @@ -0,0 +1,1931 @@ +/* + * Mirics MSi3101 SDR Dongle driver + * + * Copyright (C) 2013 Antti Palosaari <crope@iki.fi> + * + * 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. + * + * That driver is somehow based of pwc driver: + * (C) 1999-2004 Nemosoft Unv. + * (C) 2004-2006 Luc Saillard (luc@saillard.org) + * (C) 2011 Hans de Goede <hdegoede@redhat.com> + * + * Development tree of that driver will be on: + * http://git.linuxtv.org/anttip/media_tree.git/shortlog/refs/heads/mirics + * + * GNU Radio plugin "gr-kernel" for device usage will be on: + * http://git.linuxtv.org/anttip/gr-kernel.git + * + * TODO: + * Help is very highly welcome for these + all the others you could imagine: + * - split USB ADC interface and RF tuner to own drivers (msi2500 and msi001) + * - move controls to V4L2 API + * - use libv4l2 for stream format conversions + * - gr-kernel: switch to v4l2_mmap (current read eats a lot of cpu) + * - SDRSharp support + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/gcd.h> +#include <asm/div64.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> +#include <linux/usb.h> +#include <media/videobuf2-vmalloc.h> + +struct msi3101_gain { + u8 tot:7; + u8 baseband:6; + bool lna:1; + bool mixer:1; +}; + +/* 60 – 120 MHz band, lna 24dB, mixer 19dB */ +static const struct msi3101_gain msi3101_gain_lut_120[] = { + { 0, 0, 0, 0}, + { 1, 1, 0, 0}, + { 2, 2, 0, 0}, + { 3, 3, 0, 0}, + { 4, 4, 0, 0}, + { 5, 5, 0, 0}, + { 6, 6, 0, 0}, + { 7, 7, 0, 0}, + { 8, 8, 0, 0}, + { 9, 9, 0, 0}, + { 10, 10, 0, 0}, + { 11, 11, 0, 0}, + { 12, 12, 0, 0}, + { 13, 13, 0, 0}, + { 14, 14, 0, 0}, + { 15, 15, 0, 0}, + { 16, 16, 0, 0}, + { 17, 17, 0, 0}, + { 18, 18, 0, 0}, + { 19, 19, 0, 0}, + { 20, 20, 0, 0}, + { 21, 21, 0, 0}, + { 22, 22, 0, 0}, + { 23, 23, 0, 0}, + { 24, 24, 0, 0}, + { 25, 25, 0, 0}, + { 26, 26, 0, 0}, + { 27, 27, 0, 0}, + { 28, 28, 0, 0}, + { 29, 5, 1, 0}, + { 30, 6, 1, 0}, + { 31, 7, 1, 0}, + { 32, 8, 1, 0}, + { 33, 9, 1, 0}, + { 34, 10, 1, 0}, + { 35, 11, 1, 0}, + { 36, 12, 1, 0}, + { 37, 13, 1, 0}, + { 38, 14, 1, 0}, + { 39, 15, 1, 0}, + { 40, 16, 1, 0}, + { 41, 17, 1, 0}, + { 42, 18, 1, 0}, + { 43, 19, 1, 0}, + { 44, 20, 1, 0}, + { 45, 21, 1, 0}, + { 46, 22, 1, 0}, + { 47, 23, 1, 0}, + { 48, 24, 1, 0}, + { 49, 25, 1, 0}, + { 50, 26, 1, 0}, + { 51, 27, 1, 0}, + { 52, 28, 1, 0}, + { 53, 29, 1, 0}, + { 54, 30, 1, 0}, + { 55, 31, 1, 0}, + { 56, 32, 1, 0}, + { 57, 33, 1, 0}, + { 58, 34, 1, 0}, + { 59, 35, 1, 0}, + { 60, 36, 1, 0}, + { 61, 37, 1, 0}, + { 62, 38, 1, 0}, + { 63, 39, 1, 0}, + { 64, 40, 1, 0}, + { 65, 41, 1, 0}, + { 66, 42, 1, 0}, + { 67, 43, 1, 0}, + { 68, 44, 1, 0}, + { 69, 45, 1, 0}, + { 70, 46, 1, 0}, + { 71, 47, 1, 0}, + { 72, 48, 1, 0}, + { 73, 49, 1, 0}, + { 74, 50, 1, 0}, + { 75, 51, 1, 0}, + { 76, 52, 1, 0}, + { 77, 53, 1, 0}, + { 78, 54, 1, 0}, + { 79, 55, 1, 0}, + { 80, 56, 1, 0}, + { 81, 57, 1, 0}, + { 82, 58, 1, 0}, + { 83, 40, 1, 1}, + { 84, 41, 1, 1}, + { 85, 42, 1, 1}, + { 86, 43, 1, 1}, + { 87, 44, 1, 1}, + { 88, 45, 1, 1}, + { 89, 46, 1, 1}, + { 90, 47, 1, 1}, + { 91, 48, 1, 1}, + { 92, 49, 1, 1}, + { 93, 50, 1, 1}, + { 94, 51, 1, 1}, + { 95, 52, 1, 1}, + { 96, 53, 1, 1}, + { 97, 54, 1, 1}, + { 98, 55, 1, 1}, + { 99, 56, 1, 1}, + {100, 57, 1, 1}, + {101, 58, 1, 1}, + {102, 59, 1, 1}, +}; + +/* 120 – 245 MHz band, lna 24dB, mixer 19dB */ +static const struct msi3101_gain msi3101_gain_lut_245[] = { + { 0, 0, 0, 0}, + { 1, 1, 0, 0}, + { 2, 2, 0, 0}, + { 3, 3, 0, 0}, + { 4, 4, 0, 0}, + { 5, 5, 0, 0}, + { 6, 6, 0, 0}, + { 7, 7, 0, 0}, + { 8, 8, 0, 0}, + { 9, 9, 0, 0}, + { 10, 10, 0, 0}, + { 11, 11, 0, 0}, + { 12, 12, 0, 0}, + { 13, 13, 0, 0}, + { 14, 14, 0, 0}, + { 15, 15, 0, 0}, + { 16, 16, 0, 0}, + { 17, 17, 0, 0}, + { 18, 18, 0, 0}, + { 19, 19, 0, 0}, + { 20, 20, 0, 0}, + { 21, 21, 0, 0}, + { 22, 22, 0, 0}, + { 23, 23, 0, 0}, + { 24, 24, 0, 0}, + { 25, 25, 0, 0}, + { 26, 26, 0, 0}, + { 27, 27, 0, 0}, + { 28, 28, 0, 0}, + { 29, 5, 1, 0}, + { 30, 6, 1, 0}, + { 31, 7, 1, 0}, + { 32, 8, 1, 0}, + { 33, 9, 1, 0}, + { 34, 10, 1, 0}, + { 35, 11, 1, 0}, + { 36, 12, 1, 0}, + { 37, 13, 1, 0}, + { 38, 14, 1, 0}, + { 39, 15, 1, 0}, + { 40, 16, 1, 0}, + { 41, 17, 1, 0}, + { 42, 18, 1, 0}, + { 43, 19, 1, 0}, + { 44, 20, 1, 0}, + { 45, 21, 1, 0}, + { 46, 22, 1, 0}, + { 47, 23, 1, 0}, + { 48, 24, 1, 0}, + { 49, 25, 1, 0}, + { 50, 26, 1, 0}, + { 51, 27, 1, 0}, + { 52, 28, 1, 0}, + { 53, 29, 1, 0}, + { 54, 30, 1, 0}, + { 55, 31, 1, 0}, + { 56, 32, 1, 0}, + { 57, 33, 1, 0}, + { 58, 34, 1, 0}, + { 59, 35, 1, 0}, + { 60, 36, 1, 0}, + { 61, 37, 1, 0}, + { 62, 38, 1, 0}, + { 63, 39, 1, 0}, + { 64, 40, 1, 0}, + { 65, 41, 1, 0}, + { 66, 42, 1, 0}, + { 67, 43, 1, 0}, + { 68, 44, 1, 0}, + { 69, 45, 1, 0}, + { 70, 46, 1, 0}, + { 71, 47, 1, 0}, + { 72, 48, 1, 0}, + { 73, 49, 1, 0}, + { 74, 50, 1, 0}, + { 75, 51, 1, 0}, + { 76, 52, 1, 0}, + { 77, 53, 1, 0}, + { 78, 54, 1, 0}, + { 79, 55, 1, 0}, + { 80, 56, 1, 0}, + { 81, 57, 1, 0}, + { 82, 58, 1, 0}, + { 83, 40, 1, 1}, + { 84, 41, 1, 1}, + { 85, 42, 1, 1}, + { 86, 43, 1, 1}, + { 87, 44, 1, 1}, + { 88, 45, 1, 1}, + { 89, 46, 1, 1}, + { 90, 47, 1, 1}, + { 91, 48, 1, 1}, + { 92, 49, 1, 1}, + { 93, 50, 1, 1}, + { 94, 51, 1, 1}, + { 95, 52, 1, 1}, + { 96, 53, 1, 1}, + { 97, 54, 1, 1}, + { 98, 55, 1, 1}, + { 99, 56, 1, 1}, + {100, 57, 1, 1}, + {101, 58, 1, 1}, + {102, 59, 1, 1}, +}; + +/* 420 – 1000 MHz band, lna 7dB, mixer 19dB */ +static const struct msi3101_gain msi3101_gain_lut_1000[] = { + { 0, 0, 0, 0}, + { 1, 1, 0, 0}, + { 2, 2, 0, 0}, + { 3, 3, 0, 0}, + { 4, 4, 0, 0}, + { 5, 5, 0, 0}, + { 6, 6, 0, 0}, + { 7, 7, 0, 0}, + { 8, 8, 0, 0}, + { 9, 9, 0, 0}, + { 10, 10, 0, 0}, + { 11, 11, 0, 0}, + { 12, 5, 1, 0}, + { 13, 6, 1, 0}, + { 14, 7, 1, 0}, + { 15, 8, 1, 0}, + { 16, 9, 1, 0}, + { 17, 10, 1, 0}, + { 18, 11, 1, 0}, + { 19, 12, 1, 0}, + { 20, 13, 1, 0}, + { 21, 14, 1, 0}, + { 22, 15, 1, 0}, + { 23, 16, 1, 0}, + { 24, 17, 1, 0}, + { 25, 18, 1, 0}, + { 26, 19, 1, 0}, + { 27, 20, 1, 0}, + { 28, 21, 1, 0}, + { 29, 22, 1, 0}, + { 30, 23, 1, 0}, + { 31, 24, 1, 0}, + { 32, 25, 1, 0}, + { 33, 26, 1, 0}, + { 34, 27, 1, 0}, + { 35, 28, 1, 0}, + { 36, 29, 1, 0}, + { 37, 30, 1, 0}, + { 38, 31, 1, 0}, + { 39, 32, 1, 0}, + { 40, 33, 1, 0}, + { 41, 34, 1, 0}, + { 42, 35, 1, 0}, + { 43, 36, 1, 0}, + { 44, 37, 1, 0}, + { 45, 38, 1, 0}, + { 46, 39, 1, 0}, + { 47, 40, 1, 0}, + { 48, 41, 1, 0}, + { 49, 42, 1, 0}, + { 50, 43, 1, 0}, + { 51, 44, 1, 0}, + { 52, 45, 1, 0}, + { 53, 46, 1, 0}, + { 54, 47, 1, 0}, + { 55, 48, 1, 0}, + { 56, 49, 1, 0}, + { 57, 50, 1, 0}, + { 58, 51, 1, 0}, + { 59, 52, 1, 0}, + { 60, 53, 1, 0}, + { 61, 54, 1, 0}, + { 62, 55, 1, 0}, + { 63, 56, 1, 0}, + { 64, 57, 1, 0}, + { 65, 58, 1, 0}, + { 66, 40, 1, 1}, + { 67, 41, 1, 1}, + { 68, 42, 1, 1}, + { 69, 43, 1, 1}, + { 70, 44, 1, 1}, + { 71, 45, 1, 1}, + { 72, 46, 1, 1}, + { 73, 47, 1, 1}, + { 74, 48, 1, 1}, + { 75, 49, 1, 1}, + { 76, 50, 1, 1}, + { 77, 51, 1, 1}, + { 78, 52, 1, 1}, + { 79, 53, 1, 1}, + { 80, 54, 1, 1}, + { 81, 55, 1, 1}, + { 82, 56, 1, 1}, + { 83, 57, 1, 1}, + { 84, 58, 1, 1}, + { 85, 59, 1, 1}, +}; + +/* + * iConfiguration 0 + * bInterfaceNumber 0 + * bAlternateSetting 1 + * bNumEndpoints 1 + * bEndpointAddress 0x81 EP 1 IN + * bmAttributes 1 + * Transfer Type Isochronous + * wMaxPacketSize 0x1400 3x 1024 bytes + * bInterval 1 + */ +#define MAX_ISO_BUFS (8) +#define ISO_FRAMES_PER_DESC (8) +#define ISO_MAX_FRAME_SIZE (3 * 1024) +#define ISO_BUFFER_SIZE (ISO_FRAMES_PER_DESC * ISO_MAX_FRAME_SIZE) +#define MAX_ISOC_ERRORS 20 + +/* TODO: These should be moved to V4L2 API */ +#define MSI3101_CID_SAMPLING_MODE ((V4L2_CID_USER_BASE | 0xf000) + 0) +#define MSI3101_CID_SAMPLING_RATE ((V4L2_CID_USER_BASE | 0xf000) + 1) +#define MSI3101_CID_SAMPLING_RESOLUTION ((V4L2_CID_USER_BASE | 0xf000) + 2) +#define MSI3101_CID_TUNER_RF ((V4L2_CID_USER_BASE | 0xf000) + 10) +#define MSI3101_CID_TUNER_BW ((V4L2_CID_USER_BASE | 0xf000) + 11) +#define MSI3101_CID_TUNER_IF ((V4L2_CID_USER_BASE | 0xf000) + 12) +#define MSI3101_CID_TUNER_GAIN ((V4L2_CID_USER_BASE | 0xf000) + 13) + +/* intermediate buffers with raw data from the USB device */ +struct msi3101_frame_buf { + struct vb2_buffer vb; /* common v4l buffer stuff -- must be first */ + struct list_head list; +}; + +struct msi3101_state { + struct video_device vdev; + struct v4l2_device v4l2_dev; + + /* videobuf2 queue and queued buffers list */ + struct vb2_queue vb_queue; + struct list_head queued_bufs; + spinlock_t queued_bufs_lock; /* Protects queued_bufs */ + + /* Note if taking both locks v4l2_lock must always be locked first! */ + struct mutex v4l2_lock; /* Protects everything else */ + struct mutex vb_queue_lock; /* Protects vb_queue and capt_file */ + + /* Pointer to our usb_device, will be NULL after unplug */ + struct usb_device *udev; /* Both mutexes most be hold when setting! */ + + unsigned int isoc_errors; /* number of contiguous ISOC errors */ + unsigned int vb_full; /* vb is full and packets dropped */ + + struct urb *urbs[MAX_ISO_BUFS]; + int (*convert_stream) (struct msi3101_state *s, u32 *dst, u8 *src, + unsigned int src_len); + + /* Controls */ + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *ctrl_sampling_rate; + struct v4l2_ctrl *ctrl_tuner_rf; + struct v4l2_ctrl *ctrl_tuner_bw; + struct v4l2_ctrl *ctrl_tuner_if; + struct v4l2_ctrl *ctrl_tuner_gain; + + u32 next_sample; /* for track lost packets */ + u32 sample; /* for sample rate calc */ + unsigned long jiffies; + unsigned int sample_ctrl_bit[4]; +}; + +/* Private functions */ +static struct msi3101_frame_buf *msi3101_get_next_fill_buf( + struct msi3101_state *s) +{ + unsigned long flags = 0; + struct msi3101_frame_buf *buf = NULL; + + spin_lock_irqsave(&s->queued_bufs_lock, flags); + if (list_empty(&s->queued_bufs)) + goto leave; + + buf = list_entry(s->queued_bufs.next, struct msi3101_frame_buf, list); + list_del(&buf->list); +leave: + spin_unlock_irqrestore(&s->queued_bufs_lock, flags); + return buf; +} + +/* + * +=========================================================================== + * | 00-1023 | USB packet type '384' + * +=========================================================================== + * | 00- 03 | sequence number of first sample in that USB packet + * +--------------------------------------------------------------------------- + * | 04- 15 | garbage + * +--------------------------------------------------------------------------- + * | 16- 175 | samples + * +--------------------------------------------------------------------------- + * | 176- 179 | control bits for previous samples + * +--------------------------------------------------------------------------- + * | 180- 339 | samples + * +--------------------------------------------------------------------------- + * | 340- 343 | control bits for previous samples + * +--------------------------------------------------------------------------- + * | 344- 503 | samples + * +--------------------------------------------------------------------------- + * | 504- 507 | control bits for previous samples + * +--------------------------------------------------------------------------- + * | 508- 667 | samples + * +--------------------------------------------------------------------------- + * | 668- 671 | control bits for previous samples + * +--------------------------------------------------------------------------- + * | 672- 831 | samples + * +--------------------------------------------------------------------------- + * | 832- 835 | control bits for previous samples + * +--------------------------------------------------------------------------- + * | 836- 995 | samples + * +--------------------------------------------------------------------------- + * | 996- 999 | control bits for previous samples + * +--------------------------------------------------------------------------- + * | 1000-1023 | garbage + * +--------------------------------------------------------------------------- + * + * Bytes 4 - 7 could have some meaning? + * + * Control bits for previous samples is 32-bit field, containing 16 x 2-bit + * numbers. This results one 2-bit number for 8 samples. It is likely used for + * for bit shifting sample by given bits, increasing actual sampling resolution. + * Number 2 (0b10) was never seen. + * + * 6 * 16 * 2 * 4 = 768 samples. 768 * 4 = 3072 bytes + */ + +/* + * Integer to 32-bit IEEE floating point representation routine is taken + * from Radeon R600 driver (drivers/gpu/drm/radeon/r600_blit_kms.c). + * + * TODO: Currently we do conversion here in Kernel, but in future that will + * be moved to the libv4l2 library as video format conversions are. + */ +#define I2F_FRAC_BITS 23 +#define I2F_MASK ((1 << I2F_FRAC_BITS) - 1) + +/* + * Converts signed 8-bit integer into 32-bit IEEE floating point + * representation. + */ +static u32 msi3101_convert_sample_504(struct msi3101_state *s, u16 x) +{ + u32 msb, exponent, fraction, sign; + + /* Zero is special */ + if (!x) + return 0; + + /* Negative / positive value */ + if (x & (1 << 7)) { + x = -x; + x &= 0x7f; /* result is 7 bit ... + sign */ + sign = 1 << 31; + } else { + sign = 0 << 31; + } + + /* Get location of the most significant bit */ + msb = __fls(x); + + fraction = ror32(x, (msb - I2F_FRAC_BITS) & 0x1f) & I2F_MASK; + exponent = (127 + msb) << I2F_FRAC_BITS; + + return (fraction + exponent) | sign; +} + +static int msi3101_convert_stream_504(struct msi3101_state *s, u32 *dst, + u8 *src, unsigned int src_len) +{ + int i, j, i_max, dst_len = 0; + u16 sample[2]; + u32 sample_num[3]; + + /* There could be 1-3 1024 bytes URB frames */ + i_max = src_len / 1024; + + for (i = 0; i < i_max; i++) { + sample_num[i] = src[3] << 24 | src[2] << 16 | src[1] << 8 | src[0] << 0; + if (i == 0 && s->next_sample != sample_num[0]) { + dev_dbg_ratelimited(&s->udev->dev, + "%d samples lost, %d %08x:%08x\n", + sample_num[0] - s->next_sample, + src_len, s->next_sample, sample_num[0]); + } + + /* + * Dump all unknown 'garbage' data - maybe we will discover + * someday if there is something rational... + */ + dev_dbg_ratelimited(&s->udev->dev, "%*ph\n", 12, &src[4]); + + src += 16; + for (j = 0; j < 1008; j += 2) { + sample[0] = src[j + 0]; + sample[1] = src[j + 1]; + + *dst++ = msi3101_convert_sample_504(s, sample[0]); + *dst++ = msi3101_convert_sample_504(s, sample[1]); + } + /* 504 x I+Q 32bit float samples */ + dst_len += 504 * 2 * 4; + src += 1008; + } + + /* calculate samping rate and output it in 10 seconds intervals */ + if ((s->jiffies + msecs_to_jiffies(10000)) <= jiffies) { + unsigned long jiffies_now = jiffies; + unsigned long msecs = jiffies_to_msecs(jiffies_now) - jiffies_to_msecs(s->jiffies); + unsigned int samples = sample_num[i_max - 1] - s->sample; + s->jiffies = jiffies_now; + s->sample = sample_num[i_max - 1]; + dev_dbg(&s->udev->dev, + "slen=%d samples=%u msecs=%lu sampling rate=%lu\n", + src_len, samples, msecs, + samples * 1000UL / msecs); + } + + /* next sample (sample = sample + i * 504) */ + s->next_sample = sample_num[i_max - 1] + 504; + + return dst_len; +} + +/* + * Converts signed ~10+2-bit integer into 32-bit IEEE floating point + * representation. + */ +static u32 msi3101_convert_sample_384(struct msi3101_state *s, u16 x, int shift) +{ + u32 msb, exponent, fraction, sign; + s->sample_ctrl_bit[shift]++; + + /* Zero is special */ + if (!x) + return 0; + + if (shift == 3) + shift = 2; + + /* Convert 10-bit two's complement to 12-bit */ + if (x & (1 << 9)) { + x |= ~0U << 10; /* set all the rest bits to one */ + x <<= shift; + x = -x; + x &= 0x7ff; /* result is 11 bit ... + sign */ + sign = 1 << 31; + } else { + x <<= shift; + sign = 0 << 31; + } + + /* Get location of the most significant bit */ + msb = __fls(x); + + fraction = ror32(x, (msb - I2F_FRAC_BITS) & 0x1f) & I2F_MASK; + exponent = (127 + msb) << I2F_FRAC_BITS; + + return (fraction + exponent) | sign; +} + +static int msi3101_convert_stream_384(struct msi3101_state *s, u32 *dst, + u8 *src, unsigned int src_len) +{ + int i, j, k, l, i_max, dst_len = 0; + u16 sample[4]; + u32 bits; + u32 sample_num[3]; + + /* There could be 1-3 1024 bytes URB frames */ + i_max = src_len / 1024; + for (i = 0; i < i_max; i++) { + sample_num[i] = src[3] << 24 | src[2] << 16 | src[1] << 8 | src[0] << 0; + if (i == 0 && s->next_sample != sample_num[0]) { + dev_dbg_ratelimited(&s->udev->dev, + "%d samples lost, %d %08x:%08x\n", + sample_num[0] - s->next_sample, + src_len, s->next_sample, sample_num[0]); + } + + /* + * Dump all unknown 'garbage' data - maybe we will discover + * someday if there is something rational... + */ + dev_dbg_ratelimited(&s->udev->dev, + "%*ph %*ph\n", 12, &src[4], 24, &src[1000]); + + src += 16; + for (j = 0; j < 6; j++) { + bits = src[160 + 3] << 24 | src[160 + 2] << 16 | src[160 + 1] << 8 | src[160 + 0] << 0; + for (k = 0; k < 16; k++) { + for (l = 0; l < 10; l += 5) { + sample[0] = (src[l + 0] & 0xff) >> 0 | (src[l + 1] & 0x03) << 8; + sample[1] = (src[l + 1] & 0xfc) >> 2 | (src[l + 2] & 0x0f) << 6; + sample[2] = (src[l + 2] & 0xf0) >> 4 | (src[l + 3] & 0x3f) << 4; + sample[3] = (src[l + 3] & 0xc0) >> 6 | (src[l + 4] & 0xff) << 2; + + *dst++ = msi3101_convert_sample_384(s, sample[0], (bits >> (2 * k)) & 0x3); + *dst++ = msi3101_convert_sample_384(s, sample[1], (bits >> (2 * k)) & 0x3); + *dst++ = msi3101_convert_sample_384(s, sample[2], (bits >> (2 * k)) & 0x3); + *dst++ = msi3101_convert_sample_384(s, sample[3], (bits >> (2 * k)) & 0x3); + } + src += 10; + } + dev_dbg_ratelimited(&s->udev->dev, + "sample control bits %08x\n", bits); + src += 4; + } + /* 384 x I+Q 32bit float samples */ + dst_len += 384 * 2 * 4; + src += 24; + } + + /* calculate samping rate and output it in 10 seconds intervals */ + if ((s->jiffies + msecs_to_jiffies(10000)) <= jiffies) { + unsigned long jiffies_now = jiffies; + unsigned long msecs = jiffies_to_msecs(jiffies_now) - jiffies_to_msecs(s->jiffies); + unsigned int samples = sample_num[i_max - 1] - s->sample; + s->jiffies = jiffies_now; + s->sample = sample_num[i_max - 1]; + dev_dbg(&s->udev->dev, + "slen=%d samples=%u msecs=%lu sampling rate=%lu bits=%d.%d.%d.%d\n", + src_len, samples, msecs, + samples * 1000UL / msecs, + s->sample_ctrl_bit[0], s->sample_ctrl_bit[1], + s->sample_ctrl_bit[2], s->sample_ctrl_bit[3]); + } + + /* next sample (sample = sample + i * 384) */ + s->next_sample = sample_num[i_max - 1] + 384; + + return dst_len; +} + +/* + * Converts signed 12-bit integer into 32-bit IEEE floating point + * representation. + */ +static u32 msi3101_convert_sample_336(struct msi3101_state *s, u16 x) +{ + u32 msb, exponent, fraction, sign; + + /* Zero is special */ + if (!x) + return 0; + + /* Negative / positive value */ + if (x & (1 << 11)) { + x = -x; + x &= 0x7ff; /* result is 11 bit ... + sign */ + sign = 1 << 31; + } else { + sign = 0 << 31; + } + + /* Get location of the most significant bit */ + msb = __fls(x); + + fraction = ror32(x, (msb - I2F_FRAC_BITS) & 0x1f) & I2F_MASK; + exponent = (127 + msb) << I2F_FRAC_BITS; + + return (fraction + exponent) | sign; +} + +static int msi3101_convert_stream_336(struct msi3101_state *s, u32 *dst, + u8 *src, unsigned int src_len) +{ + int i, j, i_max, dst_len = 0; + u16 sample[2]; + u32 sample_num[3]; + + /* There could be 1-3 1024 bytes URB frames */ + i_max = src_len / 1024; + + for (i = 0; i < i_max; i++) { + sample_num[i] = src[3] << 24 | src[2] << 16 | src[1] << 8 | src[0] << 0; + if (i == 0 && s->next_sample != sample_num[0]) { + dev_dbg_ratelimited(&s->udev->dev, + "%d samples lost, %d %08x:%08x\n", + sample_num[0] - s->next_sample, + src_len, s->next_sample, sample_num[0]); + } + + /* + * Dump all unknown 'garbage' data - maybe we will discover + * someday if there is something rational... + */ + dev_dbg_ratelimited(&s->udev->dev, "%*ph\n", 12, &src[4]); + + src += 16; + for (j = 0; j < 1008; j += 3) { + sample[0] = (src[j + 0] & 0xff) >> 0 | (src[j + 1] & 0x0f) << 8; + sample[1] = (src[j + 1] & 0xf0) >> 4 | (src[j + 2] & 0xff) << 4; + + *dst++ = msi3101_convert_sample_336(s, sample[0]); + *dst++ = msi3101_convert_sample_336(s, sample[1]); + } + /* 336 x I+Q 32bit float samples */ + dst_len += 336 * 2 * 4; + src += 1008; + } + + /* calculate samping rate and output it in 10 seconds intervals */ + if ((s->jiffies + msecs_to_jiffies(10000)) <= jiffies) { + unsigned long jiffies_now = jiffies; + unsigned long msecs = jiffies_to_msecs(jiffies_now) - jiffies_to_msecs(s->jiffies); + unsigned int samples = sample_num[i_max - 1] - s->sample; + s->jiffies = jiffies_now; + s->sample = sample_num[i_max - 1]; + dev_dbg(&s->udev->dev, + "slen=%d samples=%u msecs=%lu sampling rate=%lu\n", + src_len, samples, msecs, + samples * 1000UL / msecs); + } + + /* next sample (sample = sample + i * 336) */ + s->next_sample = sample_num[i_max - 1] + 336; + + return dst_len; +} + +/* + * Converts signed 14-bit integer into 32-bit IEEE floating point + * representation. + */ +static u32 msi3101_convert_sample_252(struct msi3101_state *s, u16 x) +{ + u32 msb, exponent, fraction, sign; + + /* Zero is special */ + if (!x) + return 0; + + /* Negative / positive value */ + if (x & (1 << 13)) { + x = -x; + x &= 0x1fff; /* result is 13 bit ... + sign */ + sign = 1 << 31; + } else { + sign = 0 << 31; + } + + /* Get location of the most significant bit */ + msb = __fls(x); + + fraction = ror32(x, (msb - I2F_FRAC_BITS) & 0x1f) & I2F_MASK; + exponent = (127 + msb) << I2F_FRAC_BITS; + + return (fraction + exponent) | sign; +} + +static int msi3101_convert_stream_252(struct msi3101_state *s, u32 *dst, + u8 *src, unsigned int src_len) +{ + int i, j, i_max, dst_len = 0; + u16 sample[2]; + u32 sample_num[3]; + + /* There could be 1-3 1024 bytes URB frames */ + i_max = src_len / 1024; + + for (i = 0; i < i_max; i++) { + sample_num[i] = src[3] << 24 | src[2] << 16 | src[1] << 8 | src[0] << 0; + if (i == 0 && s->next_sample != sample_num[0]) { + dev_dbg_ratelimited(&s->udev->dev, + "%d samples lost, %d %08x:%08x\n", + sample_num[0] - s->next_sample, + src_len, s->next_sample, sample_num[0]); + } + + /* + * Dump all unknown 'garbage' data - maybe we will discover + * someday if there is something rational... + */ + dev_dbg_ratelimited(&s->udev->dev, "%*ph\n", 12, &src[4]); + + src += 16; + for (j = 0; j < 1008; j += 4) { + sample[0] = src[j + 0] >> 0 | src[j + 1] << 8; + sample[1] = src[j + 2] >> 0 | src[j + 3] << 8; + + *dst++ = msi3101_convert_sample_252(s, sample[0]); + *dst++ = msi3101_convert_sample_252(s, sample[1]); + } + /* 252 x I+Q 32bit float samples */ + dst_len += 252 * 2 * 4; + src += 1008; + } + + /* calculate samping rate and output it in 10 seconds intervals */ + if ((s->jiffies + msecs_to_jiffies(10000)) <= jiffies) { + unsigned long jiffies_now = jiffies; + unsigned long msecs = jiffies_to_msecs(jiffies_now) - jiffies_to_msecs(s->jiffies); + unsigned int samples = sample_num[i_max - 1] - s->sample; + s->jiffies = jiffies_now; + s->sample = sample_num[i_max - 1]; + dev_dbg(&s->udev->dev, + "slen=%d samples=%u msecs=%lu sampling rate=%lu\n", + src_len, samples, msecs, + samples * 1000UL / msecs); + } + + /* next sample (sample = sample + i * 252) */ + s->next_sample = sample_num[i_max - 1] + 252; + + return dst_len; +} + +/* + * This gets called for the Isochronous pipe (stream). This is done in interrupt + * time, so it has to be fast, not crash, and not stall. Neat. + */ +static void msi3101_isoc_handler(struct urb *urb) +{ + struct msi3101_state *s = (struct msi3101_state *)urb->context; + int i, flen, fstatus; + unsigned char *iso_buf = NULL; + struct msi3101_frame_buf *fbuf; + + if (urb->status == -ENOENT || urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN) { + dev_dbg(&s->udev->dev, "URB (%p) unlinked %ssynchronuously\n", + urb, urb->status == -ENOENT ? "" : "a"); + return; + } + + if (urb->status != 0) { + dev_dbg(&s->udev->dev, + "msi3101_isoc_handler() called with status %d\n", + urb->status); + /* Give up after a number of contiguous errors */ + if (++s->isoc_errors > MAX_ISOC_ERRORS) + dev_dbg(&s->udev->dev, + "Too many ISOC errors, bailing out\n"); + goto handler_end; + } else { + /* Reset ISOC error counter. We did get here, after all. */ + s->isoc_errors = 0; + } + + /* Compact data */ + for (i = 0; i < urb->number_of_packets; i++) { + void *ptr; + + /* Check frame error */ + fstatus = urb->iso_frame_desc[i].status; + if (fstatus) { + dev_dbg_ratelimited(&s->udev->dev, + "frame=%d/%d has error %d skipping\n", + i, urb->number_of_packets, fstatus); + goto skip; + } + + /* Check if that frame contains data */ + flen = urb->iso_frame_desc[i].actual_length; + if (flen == 0) + goto skip; + + iso_buf = urb->transfer_buffer + urb->iso_frame_desc[i].offset; + + /* Get free framebuffer */ + fbuf = msi3101_get_next_fill_buf(s); + if (fbuf == NULL) { + s->vb_full++; + dev_dbg_ratelimited(&s->udev->dev, + "videobuf is full, %d packets dropped\n", + s->vb_full); + goto skip; + } + + /* fill framebuffer */ + ptr = vb2_plane_vaddr(&fbuf->vb, 0); + flen = s->convert_stream(s, ptr, iso_buf, flen); + vb2_set_plane_payload(&fbuf->vb, 0, flen); + vb2_buffer_done(&fbuf->vb, VB2_BUF_STATE_DONE); +skip: + ; + } + +handler_end: + i = usb_submit_urb(urb, GFP_ATOMIC); + if (i != 0) + dev_dbg(&s->udev->dev, + "Error (%d) re-submitting urb in msi3101_isoc_handler\n", + i); +} + +static void msi3101_iso_stop(struct msi3101_state *s) +{ + int i; + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + /* Unlinking ISOC buffers one by one */ + for (i = 0; i < MAX_ISO_BUFS; i++) { + if (s->urbs[i]) { + dev_dbg(&s->udev->dev, "Unlinking URB %p\n", + s->urbs[i]); + usb_kill_urb(s->urbs[i]); + } + } +} + +static void msi3101_iso_free(struct msi3101_state *s) +{ + int i; + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + /* Freeing ISOC buffers one by one */ + for (i = 0; i < MAX_ISO_BUFS; i++) { + if (s->urbs[i]) { + dev_dbg(&s->udev->dev, "Freeing URB\n"); + if (s->urbs[i]->transfer_buffer) { + usb_free_coherent(s->udev, + s->urbs[i]->transfer_buffer_length, + s->urbs[i]->transfer_buffer, + s->urbs[i]->transfer_dma); + } + usb_free_urb(s->urbs[i]); + s->urbs[i] = NULL; + } + } +} + +/* Both v4l2_lock and vb_queue_lock should be locked when calling this */ +static void msi3101_isoc_cleanup(struct msi3101_state *s) +{ + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + msi3101_iso_stop(s); + msi3101_iso_free(s); +} + +/* Both v4l2_lock and vb_queue_lock should be locked when calling this */ +static int msi3101_isoc_init(struct msi3101_state *s) +{ + struct usb_device *udev; + struct urb *urb; + int i, j, ret; + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + s->isoc_errors = 0; + udev = s->udev; + + ret = usb_set_interface(s->udev, 0, 1); + if (ret < 0) + return ret; + + /* Allocate and init Isochronuous urbs */ + for (i = 0; i < MAX_ISO_BUFS; i++) { + urb = usb_alloc_urb(ISO_FRAMES_PER_DESC, GFP_KERNEL); + if (urb == NULL) { + dev_err(&s->udev->dev, + "Failed to allocate urb %d\n", i); + msi3101_isoc_cleanup(s); + return -ENOMEM; + } + s->urbs[i] = urb; + dev_dbg(&s->udev->dev, "Allocated URB at 0x%p\n", urb); + + urb->interval = 1; + urb->dev = udev; + urb->pipe = usb_rcvisocpipe(udev, 0x81); + urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + urb->transfer_buffer = usb_alloc_coherent(udev, ISO_BUFFER_SIZE, + GFP_KERNEL, &urb->transfer_dma); + if (urb->transfer_buffer == NULL) { + dev_err(&s->udev->dev, + "Failed to allocate urb buffer %d\n", + i); + msi3101_isoc_cleanup(s); + return -ENOMEM; + } + urb->transfer_buffer_length = ISO_BUFFER_SIZE; + urb->complete = msi3101_isoc_handler; + urb->context = s; + urb->start_frame = 0; + urb->number_of_packets = ISO_FRAMES_PER_DESC; + for (j = 0; j < ISO_FRAMES_PER_DESC; j++) { + urb->iso_frame_desc[j].offset = j * ISO_MAX_FRAME_SIZE; + urb->iso_frame_desc[j].length = ISO_MAX_FRAME_SIZE; + } + } + + /* link */ + for (i = 0; i < MAX_ISO_BUFS; i++) { + ret = usb_submit_urb(s->urbs[i], GFP_KERNEL); + if (ret) { + dev_err(&s->udev->dev, + "isoc_init() submit_urb %d failed with error %d\n", + i, ret); + msi3101_isoc_cleanup(s); + return ret; + } + dev_dbg(&s->udev->dev, "URB 0x%p submitted.\n", s->urbs[i]); + } + + /* All is done... */ + return 0; +} + +/* Must be called with vb_queue_lock hold */ +static void msi3101_cleanup_queued_bufs(struct msi3101_state *s) +{ + unsigned long flags = 0; + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + spin_lock_irqsave(&s->queued_bufs_lock, flags); + while (!list_empty(&s->queued_bufs)) { + struct msi3101_frame_buf *buf; + + buf = list_entry(s->queued_bufs.next, struct msi3101_frame_buf, + list); + list_del(&buf->list); + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&s->queued_bufs_lock, flags); +} + +/* The user yanked out the cable... */ +static void msi3101_disconnect(struct usb_interface *intf) +{ + struct v4l2_device *v = usb_get_intfdata(intf); + struct msi3101_state *s = + container_of(v, struct msi3101_state, v4l2_dev); + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + mutex_lock(&s->vb_queue_lock); + mutex_lock(&s->v4l2_lock); + /* No need to keep the urbs around after disconnection */ + s->udev = NULL; + + v4l2_device_disconnect(&s->v4l2_dev); + video_unregister_device(&s->vdev); + mutex_unlock(&s->v4l2_lock); + mutex_unlock(&s->vb_queue_lock); + + v4l2_device_put(&s->v4l2_dev); +} + +static int msi3101_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct msi3101_state *s = video_drvdata(file); + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + strlcpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strlcpy(cap->card, s->vdev.name, sizeof(cap->card)); + usb_make_path(s->udev, cap->bus_info, sizeof(cap->bus_info)); + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE; + cap->device_caps = V4L2_CAP_TUNER; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + + +/* Videobuf2 operations */ +static int msi3101_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], void *alloc_ctxs[]) +{ + struct msi3101_state *s = vb2_get_drv_priv(vq); + dev_dbg(&s->udev->dev, "%s: *nbuffers=%d\n", __func__, *nbuffers); + + /* Absolute min and max number of buffers available for mmap() */ + *nbuffers = 32; + *nplanes = 1; + sizes[0] = PAGE_ALIGN(3 * 3072); /* 3 * 768 * 4 */ + dev_dbg(&s->udev->dev, "%s: nbuffers=%d sizes[0]=%d\n", + __func__, *nbuffers, sizes[0]); + return 0; +} + +static int msi3101_buf_prepare(struct vb2_buffer *vb) +{ + struct msi3101_state *s = vb2_get_drv_priv(vb->vb2_queue); + + /* Don't allow queing new buffers after device disconnection */ + if (!s->udev) + return -ENODEV; + + return 0; +} + +static void msi3101_buf_queue(struct vb2_buffer *vb) +{ + struct msi3101_state *s = vb2_get_drv_priv(vb->vb2_queue); + struct msi3101_frame_buf *buf = + container_of(vb, struct msi3101_frame_buf, vb); + unsigned long flags = 0; + + /* Check the device has not disconnected between prep and queuing */ + if (!s->udev) { + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + return; + } + + spin_lock_irqsave(&s->queued_bufs_lock, flags); + list_add_tail(&buf->list, &s->queued_bufs); + spin_unlock_irqrestore(&s->queued_bufs_lock, flags); +} + +#define CMD_WREG 0x41 +#define CMD_START_STREAMING 0x43 +#define CMD_STOP_STREAMING 0x45 +#define CMD_READ_UNKNOW 0x48 + +#define msi3101_dbg_usb_control_msg(udev, r, t, v, _i, b, l) { \ + char *direction; \ + if (t == (USB_TYPE_VENDOR | USB_DIR_OUT)) \ + direction = ">>>"; \ + else \ + direction = "<<<"; \ + dev_dbg(&udev->dev, "%s: %02x %02x %02x %02x %02x %02x %02x %02x " \ + "%s %*ph\n", __func__, t, r, v & 0xff, v >> 8, \ + _i & 0xff, _i >> 8, l & 0xff, l >> 8, direction, l, b); \ +} + +static int msi3101_ctrl_msg(struct msi3101_state *s, u8 cmd, u32 data) +{ + int ret; + u8 request = cmd; + u8 requesttype = USB_DIR_OUT | USB_TYPE_VENDOR; + u16 value = (data >> 0) & 0xffff; + u16 index = (data >> 16) & 0xffff; + + msi3101_dbg_usb_control_msg(s->udev, + request, requesttype, value, index, NULL, 0); + + ret = usb_control_msg(s->udev, usb_sndctrlpipe(s->udev, 0), + request, requesttype, value, index, NULL, 0, 2000); + + if (ret) + dev_err(&s->udev->dev, "%s: failed %d, cmd %02x, data %04x\n", + __func__, ret, cmd, data); + + return ret; +}; + +static int msi3101_tuner_write(struct msi3101_state *s, u32 data) +{ + return msi3101_ctrl_msg(s, CMD_WREG, data << 8 | 0x09); +}; + +#define F_REF 24000000 +#define DIV_R_IN 2 +static int msi3101_set_usb_adc(struct msi3101_state *s) +{ + int ret, div_n, div_m, div_r_out, f_sr, f_vco, fract; + u32 reg3, reg4, reg7; + + f_sr = s->ctrl_sampling_rate->val64; + + /* select stream format */ + if (f_sr < 6000000) { + s->convert_stream = msi3101_convert_stream_252; + reg7 = 0x00009407; + } else if (f_sr < 8000000) { + s->convert_stream = msi3101_convert_stream_336; + reg7 = 0x00008507; + } else if (f_sr < 9000000) { + s->convert_stream = msi3101_convert_stream_384; + reg7 = 0x0000a507; + } else { + s->convert_stream = msi3101_convert_stream_504; + reg7 = 0x000c9407; + } + + /* + * Synthesizer config is just a educated guess... + * + * [7:0] 0x03, register address + * [8] 1, always + * [9] ? + * [12:10] output divider + * [13] 0 ? + * [14] 0 ? + * [15] fractional MSB, bit 20 + * [16:19] N + * [23:20] ? + * [24:31] 0x01 + * + * output divider + * val div + * 0 - (invalid) + * 1 4 + * 2 6 + * 3 8 + * 4 10 + * 5 12 + * 6 14 + * 7 16 + * + * VCO 202000000 - 720000000++ + */ + reg3 = 0x01000303; + reg4 = 0x00000004; + + /* XXX: Filters? AGC? */ + if (f_sr < 6000000) + reg3 |= 0x1 << 20; + else if (f_sr < 7000000) + reg3 |= 0x5 << 20; + else if (f_sr < 8500000) + reg3 |= 0x9 << 20; + else + reg3 |= 0xd << 20; + + for (div_r_out = 4; div_r_out < 16; div_r_out += 2) { + f_vco = f_sr * div_r_out * 12; + dev_dbg(&s->udev->dev, "%s: div_r_out=%d f_vco=%d\n", + __func__, div_r_out, f_vco); + if (f_vco >= 202000000) + break; + } + + div_n = f_vco / (F_REF * DIV_R_IN); + div_m = f_vco % (F_REF * DIV_R_IN); + fract = 0x200000ul * div_m / (F_REF * DIV_R_IN); + + reg3 |= div_n << 16; + reg3 |= (div_r_out / 2 - 1) << 10; + reg3 |= ((fract >> 20) & 0x000001) << 15; /* [20] */ + reg4 |= ((fract >> 0) & 0x0fffff) << 8; /* [19:0] */ + + dev_dbg(&s->udev->dev, + "%s: f_sr=%d f_vco=%d div_n=%d div_m=%d div_r_out=%d reg3=%08x reg4=%08x\n", + __func__, f_sr, f_vco, div_n, div_m, div_r_out, reg3, reg4); + + ret = msi3101_ctrl_msg(s, CMD_WREG, 0x00608008); + if (ret) + goto err; + + ret = msi3101_ctrl_msg(s, CMD_WREG, 0x00000c05); + if (ret) + goto err; + + ret = msi3101_ctrl_msg(s, CMD_WREG, 0x00020000); + if (ret) + goto err; + + ret = msi3101_ctrl_msg(s, CMD_WREG, 0x00480102); + if (ret) + goto err; + + ret = msi3101_ctrl_msg(s, CMD_WREG, 0x00f38008); + if (ret) + goto err; + + ret = msi3101_ctrl_msg(s, CMD_WREG, reg7); + if (ret) + goto err; + + ret = msi3101_ctrl_msg(s, CMD_WREG, reg4); + if (ret) + goto err; + + ret = msi3101_ctrl_msg(s, CMD_WREG, reg3); + if (ret) + goto err; +err: + return ret; +}; + +static int msi3101_set_tuner(struct msi3101_state *s) +{ + int ret, i, len; + unsigned int n, m, thresh, frac, vco_step, tmp, f_if1; + u32 reg; + u64 f_vco, tmp64; + u8 mode, filter_mode, lo_div; + const struct msi3101_gain *gain_lut; + static const struct { + u32 rf; + u8 mode; + u8 lo_div; + } band_lut[] = { + { 50000000, 0xe1, 16}, /* AM_MODE2, antenna 2 */ + {108000000, 0x42, 32}, /* VHF_MODE */ + {330000000, 0x44, 16}, /* B3_MODE */ + {960000000, 0x48, 4}, /* B45_MODE */ + { ~0U, 0x50, 2}, /* BL_MODE */ + }; + static const struct { + u32 freq; + u8 filter_mode; + } if_freq_lut[] = { + { 0, 0x03}, /* Zero IF */ + { 450000, 0x02}, /* 450 kHz IF */ + {1620000, 0x01}, /* 1.62 MHz IF */ + {2048000, 0x00}, /* 2.048 MHz IF */ + }; + static const struct { + u32 freq; + u8 val; + } bandwidth_lut[] = { + { 200000, 0x00}, /* 200 kHz */ + { 300000, 0x01}, /* 300 kHz */ + { 600000, 0x02}, /* 600 kHz */ + {1536000, 0x03}, /* 1.536 MHz */ + {5000000, 0x04}, /* 5 MHz */ + {6000000, 0x05}, /* 6 MHz */ + {7000000, 0x06}, /* 7 MHz */ + {8000000, 0x07}, /* 8 MHz */ + }; + + unsigned int f_rf = s->ctrl_tuner_rf->val64; + + /* + * bandwidth (Hz) + * 200000, 300000, 600000, 1536000, 5000000, 6000000, 7000000, 8000000 + */ + unsigned int bandwidth = s->ctrl_tuner_bw->val; + + /* + * intermediate frequency (Hz) + * 0, 450000, 1620000, 2048000 + */ + unsigned int f_if = s->ctrl_tuner_if->val; + + /* + * gain reduction (dB) + * 0 - 102 below 420 MHz + * 0 - 85 above 420 MHz + */ + int gain = s->ctrl_tuner_gain->val; + + dev_dbg(&s->udev->dev, + "%s: f_rf=%d bandwidth=%d f_if=%d gain=%d\n", + __func__, f_rf, bandwidth, f_if, gain); + + ret = -EINVAL; + + for (i = 0; i < ARRAY_SIZE(band_lut); i++) { + if (f_rf <= band_lut[i].rf) { + mode = band_lut[i].mode; + lo_div = band_lut[i].lo_div; + break; + } + } + + if (i == ARRAY_SIZE(band_lut)) + goto err; + + /* AM_MODE is upconverted */ + if ((mode >> 0) & 0x1) + f_if1 = 5 * F_REF; + else + f_if1 = 0; + + for (i = 0; i < ARRAY_SIZE(if_freq_lut); i++) { + if (f_if == if_freq_lut[i].freq) { + filter_mode = if_freq_lut[i].filter_mode; + break; + } + } + + if (i == ARRAY_SIZE(if_freq_lut)) + goto err; + + for (i = 0; i < ARRAY_SIZE(bandwidth_lut); i++) { + if (bandwidth == bandwidth_lut[i].freq) { + bandwidth = bandwidth_lut[i].val; + break; + } + } + + if (i == ARRAY_SIZE(bandwidth_lut)) + goto err; + +#define F_OUT_STEP 1 +#define R_REF 4 + f_vco = (f_rf + f_if + f_if1) * lo_div; + + tmp64 = f_vco; + m = do_div(tmp64, F_REF * R_REF); + n = (unsigned int) tmp64; + + vco_step = F_OUT_STEP * lo_div; + thresh = (F_REF * R_REF) / vco_step; + frac = 1ul * thresh * m / (F_REF * R_REF); + + /* Find out greatest common divisor and divide to smaller. */ + tmp = gcd(thresh, frac); + thresh /= tmp; + frac /= tmp; + + /* Force divide to reg max. Resolution will be reduced. */ + tmp = DIV_ROUND_UP(thresh, 4095); + thresh = DIV_ROUND_CLOSEST(thresh, tmp); + frac = DIV_ROUND_CLOSEST(frac, tmp); + + /* calc real RF set */ + tmp = 1ul * F_REF * R_REF * n; + tmp += 1ul * F_REF * R_REF * frac / thresh; + tmp /= lo_div; + + dev_dbg(&s->udev->dev, + "%s: rf=%u:%u n=%d thresh=%d frac=%d\n", + __func__, f_rf, tmp, n, thresh, frac); + + ret = msi3101_tuner_write(s, 0x00000e); + if (ret) + goto err; + + ret = msi3101_tuner_write(s, 0x000003); + if (ret) + goto err; + + reg = 0 << 0; + reg |= mode << 4; + reg |= filter_mode << 12; + reg |= bandwidth << 14; + reg |= 0x02 << 17; + reg |= 0x00 << 20; + ret = msi3101_tuner_write(s, reg); + if (ret) + goto err; + + reg = 5 << 0; + reg |= thresh << 4; + reg |= 1 << 19; + reg |= 1 << 21; + ret = msi3101_tuner_write(s, reg); + if (ret) + goto err; + + reg = 2 << 0; + reg |= frac << 4; + reg |= n << 16; + ret = msi3101_tuner_write(s, reg); + if (ret) + goto err; + + if (f_rf < 120000000) { + gain_lut = msi3101_gain_lut_120; + len = ARRAY_SIZE(msi3101_gain_lut_120); + } else if (f_rf < 245000000) { + gain_lut = msi3101_gain_lut_245; + len = ARRAY_SIZE(msi3101_gain_lut_120); + } else { + gain_lut = msi3101_gain_lut_1000; + len = ARRAY_SIZE(msi3101_gain_lut_1000); + } + + for (i = 0; i < len; i++) { + if (gain_lut[i].tot >= gain) + break; + } + + if (i == len) + goto err; + + dev_dbg(&s->udev->dev, + "%s: gain tot=%d baseband=%d lna=%d mixer=%d\n", + __func__, gain_lut[i].tot, gain_lut[i].baseband, + gain_lut[i].lna, gain_lut[i].mixer); + + reg = 1 << 0; + reg |= gain_lut[i].baseband << 4; + reg |= 0 << 10; + reg |= gain_lut[i].mixer << 12; + reg |= gain_lut[i].lna << 13; + reg |= 4 << 14; + reg |= 0 << 17; + ret = msi3101_tuner_write(s, reg); + if (ret) + goto err; + + reg = 6 << 0; + reg |= 63 << 4; + reg |= 4095 << 10; + ret = msi3101_tuner_write(s, reg); + if (ret) + goto err; + + return 0; +err: + dev_dbg(&s->udev->dev, "%s: failed %d\n", __func__, ret); + return ret; +}; + +static int msi3101_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct msi3101_state *s = vb2_get_drv_priv(vq); + int ret; + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + if (!s->udev) + return -ENODEV; + + if (mutex_lock_interruptible(&s->v4l2_lock)) + return -ERESTARTSYS; + + ret = msi3101_set_usb_adc(s); + + ret = msi3101_isoc_init(s); + if (ret) + msi3101_cleanup_queued_bufs(s); + + ret = msi3101_ctrl_msg(s, CMD_START_STREAMING, 0); + + mutex_unlock(&s->v4l2_lock); + + return ret; +} + +static int msi3101_stop_streaming(struct vb2_queue *vq) +{ + struct msi3101_state *s = vb2_get_drv_priv(vq); + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + if (mutex_lock_interruptible(&s->v4l2_lock)) + return -ERESTARTSYS; + + if (s->udev) + msi3101_isoc_cleanup(s); + + msi3101_cleanup_queued_bufs(s); + + /* according to tests, at least 700us delay is required */ + msleep(20); + msi3101_ctrl_msg(s, CMD_STOP_STREAMING, 0); + + mutex_unlock(&s->v4l2_lock); + + return 0; +} + +static struct vb2_ops msi3101_vb2_ops = { + .queue_setup = msi3101_queue_setup, + .buf_prepare = msi3101_buf_prepare, + .buf_queue = msi3101_buf_queue, + .start_streaming = msi3101_start_streaming, + .stop_streaming = msi3101_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int msi3101_enum_input(struct file *file, void *fh, struct v4l2_input *i) +{ + if (i->index != 0) + return -EINVAL; + + strlcpy(i->name, "SDR data", sizeof(i->name)); + i->type = V4L2_INPUT_TYPE_CAMERA; + + return 0; +} + +static int msi3101_g_input(struct file *file, void *fh, unsigned int *i) +{ + *i = 0; + + return 0; +} + +static int msi3101_s_input(struct file *file, void *fh, unsigned int i) +{ + return i ? -EINVAL : 0; +} + +static int vidioc_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *v) +{ + struct msi3101_state *s = video_drvdata(file); + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + return 0; +} + +static int vidioc_g_tuner(struct file *file, void *priv, struct v4l2_tuner *v) +{ + struct msi3101_state *s = video_drvdata(file); + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + strcpy(v->name, "SDR RX"); + v->capability = V4L2_TUNER_CAP_LOW; + + return 0; +} + +static int vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct msi3101_state *s = video_drvdata(file); + dev_dbg(&s->udev->dev, "%s: frequency=%lu Hz (%u)\n", + __func__, f->frequency * 625UL / 10UL, f->frequency); + + return v4l2_ctrl_s_ctrl_int64(s->ctrl_tuner_rf, + f->frequency * 625UL / 10UL); +} + +const struct v4l2_ioctl_ops msi3101_ioctl_ops = { + .vidioc_querycap = msi3101_querycap, + + .vidioc_enum_input = msi3101_enum_input, + .vidioc_g_input = msi3101_g_input, + .vidioc_s_input = msi3101_s_input, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_s_frequency = vidioc_s_frequency, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_log_status = v4l2_ctrl_log_status, +}; + +static const struct v4l2_file_operations msi3101_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static struct video_device msi3101_template = { + .name = "Mirics MSi3101 SDR Dongle", + .release = video_device_release_empty, + .fops = &msi3101_fops, + .ioctl_ops = &msi3101_ioctl_ops, +}; + +static int msi3101_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct msi3101_state *s = + container_of(ctrl->handler, struct msi3101_state, + ctrl_handler); + int ret; + dev_dbg(&s->udev->dev, + "%s: id=%d name=%s val=%d min=%d max=%d step=%d\n", + __func__, ctrl->id, ctrl->name, ctrl->val, + ctrl->minimum, ctrl->maximum, ctrl->step); + + switch (ctrl->id) { + case MSI3101_CID_SAMPLING_MODE: + case MSI3101_CID_SAMPLING_RATE: + case MSI3101_CID_SAMPLING_RESOLUTION: + ret = 0; + break; + case MSI3101_CID_TUNER_RF: + case MSI3101_CID_TUNER_BW: + case MSI3101_CID_TUNER_IF: + case MSI3101_CID_TUNER_GAIN: + ret = msi3101_set_tuner(s); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct v4l2_ctrl_ops msi3101_ctrl_ops = { + .s_ctrl = msi3101_s_ctrl, +}; + +static void msi3101_video_release(struct v4l2_device *v) +{ + struct msi3101_state *s = + container_of(v, struct msi3101_state, v4l2_dev); + + v4l2_ctrl_handler_free(&s->ctrl_handler); + v4l2_device_unregister(&s->v4l2_dev); + kfree(s); +} + +static int msi3101_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct msi3101_state *s = NULL; + int ret; + static const char * const ctrl_sampling_mode_qmenu_strings[] = { + "Quadrature Sampling", + NULL, + }; + static const struct v4l2_ctrl_config ctrl_sampling_mode = { + .ops = &msi3101_ctrl_ops, + .id = MSI3101_CID_SAMPLING_MODE, + .type = V4L2_CTRL_TYPE_MENU, + .flags = V4L2_CTRL_FLAG_INACTIVE, + .name = "Sampling Mode", + .qmenu = ctrl_sampling_mode_qmenu_strings, + }; + static const struct v4l2_ctrl_config ctrl_sampling_rate = { + .ops = &msi3101_ctrl_ops, + .id = MSI3101_CID_SAMPLING_RATE, + .type = V4L2_CTRL_TYPE_INTEGER64, + .name = "Sampling Rate", + .min = 500000, + .max = 12000000, + .def = 2048000, + .step = 1, + }; + static const struct v4l2_ctrl_config ctrl_sampling_resolution = { + .ops = &msi3101_ctrl_ops, + .id = MSI3101_CID_SAMPLING_RESOLUTION, + .type = V4L2_CTRL_TYPE_INTEGER, + .flags = V4L2_CTRL_FLAG_INACTIVE, + .name = "Sampling Resolution", + .min = 10, + .max = 10, + .def = 10, + .step = 1, + }; + static const struct v4l2_ctrl_config ctrl_tuner_rf = { + .ops = &msi3101_ctrl_ops, + .id = MSI3101_CID_TUNER_RF, + .type = V4L2_CTRL_TYPE_INTEGER64, + .name = "Tuner RF", + .min = 40000000, + .max = 2000000000, + .def = 100000000, + .step = 1, + }; + static const struct v4l2_ctrl_config ctrl_tuner_bw = { + .ops = &msi3101_ctrl_ops, + .id = MSI3101_CID_TUNER_BW, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Tuner BW", + .min = 200000, + .max = 8000000, + .def = 600000, + .step = 1, + }; + static const struct v4l2_ctrl_config ctrl_tuner_if = { + .ops = &msi3101_ctrl_ops, + .id = MSI3101_CID_TUNER_IF, + .type = V4L2_CTRL_TYPE_INTEGER, + .flags = V4L2_CTRL_FLAG_INACTIVE, + .name = "Tuner IF", + .min = 0, + .max = 2048000, + .def = 0, + .step = 1, + }; + static const struct v4l2_ctrl_config ctrl_tuner_gain = { + .ops = &msi3101_ctrl_ops, + .id = MSI3101_CID_TUNER_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Tuner Gain", + .min = 0, + .max = 102, + .def = 0, + .step = 1, + }; + + s = kzalloc(sizeof(struct msi3101_state), GFP_KERNEL); + if (s == NULL) { + pr_err("Could not allocate memory for msi3101_state\n"); + return -ENOMEM; + } + + mutex_init(&s->v4l2_lock); + mutex_init(&s->vb_queue_lock); + spin_lock_init(&s->queued_bufs_lock); + INIT_LIST_HEAD(&s->queued_bufs); + + s->udev = udev; + + /* Init videobuf2 queue structure */ + s->vb_queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + s->vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; + s->vb_queue.drv_priv = s; + s->vb_queue.buf_struct_size = sizeof(struct msi3101_frame_buf); + s->vb_queue.ops = &msi3101_vb2_ops; + s->vb_queue.mem_ops = &vb2_vmalloc_memops; + s->vb_queue.timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + ret = vb2_queue_init(&s->vb_queue); + if (ret < 0) { + dev_err(&s->udev->dev, "Could not initialize vb2 queue\n"); + goto err_free_mem; + } + + /* Init video_device structure */ + s->vdev = msi3101_template; + s->vdev.queue = &s->vb_queue; + s->vdev.queue->lock = &s->vb_queue_lock; + set_bit(V4L2_FL_USE_FH_PRIO, &s->vdev.flags); + video_set_drvdata(&s->vdev, s); + + /* Register controls */ + v4l2_ctrl_handler_init(&s->ctrl_handler, 7); + v4l2_ctrl_new_custom(&s->ctrl_handler, &ctrl_sampling_mode, NULL); + s->ctrl_sampling_rate = v4l2_ctrl_new_custom(&s->ctrl_handler, &ctrl_sampling_rate, NULL); + v4l2_ctrl_new_custom(&s->ctrl_handler, &ctrl_sampling_resolution, NULL); + s->ctrl_tuner_rf = v4l2_ctrl_new_custom(&s->ctrl_handler, &ctrl_tuner_rf, NULL); + s->ctrl_tuner_bw = v4l2_ctrl_new_custom(&s->ctrl_handler, &ctrl_tuner_bw, NULL); + s->ctrl_tuner_if = v4l2_ctrl_new_custom(&s->ctrl_handler, &ctrl_tuner_if, NULL); + s->ctrl_tuner_gain = v4l2_ctrl_new_custom(&s->ctrl_handler, &ctrl_tuner_gain, NULL); + if (s->ctrl_handler.error) { + ret = s->ctrl_handler.error; + dev_err(&s->udev->dev, "Could not initialize controls\n"); + goto err_free_controls; + } + + /* Register the v4l2_device structure */ + s->v4l2_dev.release = msi3101_video_release; + ret = v4l2_device_register(&intf->dev, &s->v4l2_dev); + if (ret) { + dev_err(&s->udev->dev, + "Failed to register v4l2-device (%d)\n", ret); + goto err_free_controls; + } + + s->v4l2_dev.ctrl_handler = &s->ctrl_handler; + s->vdev.v4l2_dev = &s->v4l2_dev; + s->vdev.lock = &s->v4l2_lock; + + ret = video_register_device(&s->vdev, VFL_TYPE_GRABBER, -1); + if (ret < 0) { + dev_err(&s->udev->dev, + "Failed to register as video device (%d)\n", + ret); + goto err_unregister_v4l2_dev; + } + dev_info(&s->udev->dev, "Registered as %s\n", + video_device_node_name(&s->vdev)); + + return 0; + +err_unregister_v4l2_dev: + v4l2_device_unregister(&s->v4l2_dev); +err_free_controls: + v4l2_ctrl_handler_free(&s->ctrl_handler); +err_free_mem: + kfree(s); + return ret; +} + +/* USB device ID list */ +static struct usb_device_id msi3101_id_table[] = { + { USB_DEVICE(0x1df7, 0x2500) }, /* Mirics MSi3101 SDR Dongle */ + { USB_DEVICE(0x2040, 0xd300) }, /* Hauppauge WinTV 133559 LF */ + { } +}; +MODULE_DEVICE_TABLE(usb, msi3101_id_table); + +/* USB subsystem interface */ +static struct usb_driver msi3101_driver = { + .name = KBUILD_MODNAME, + .probe = msi3101_probe, + .disconnect = msi3101_disconnect, + .id_table = msi3101_id_table, +}; + +module_usb_driver(msi3101_driver); + +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_DESCRIPTION("Mirics MSi3101 SDR Dongle"); +MODULE_LICENSE("GPL"); |