diff options
Diffstat (limited to 'drivers')
261 files changed, 29210 insertions, 5010 deletions
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 026ab0fc06f7..3bfac3accd22 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2118,6 +2118,7 @@ static const struct hid_device_id hid_ignore_list[] = { { HID_USB_DEVICE(USB_VENDOR_ID_BERKSHIRE, USB_DEVICE_ID_BERKSHIRE_PCWD) }, { HID_USB_DEVICE(USB_VENDOR_ID_CIDC, 0x0103) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_RADIO_SI470X) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_RADIO_SI4713) }, { HID_USB_DEVICE(USB_VENDOR_ID_CMEDIA, USB_DEVICE_ID_CM109) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_HIDCOM) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_ULTRAMOUSE) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 92b40c09d917..5a5248f2cc07 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -241,6 +241,8 @@ #define USB_VENDOR_ID_CYGNAL 0x10c4 #define USB_DEVICE_ID_CYGNAL_RADIO_SI470X 0x818a +#define USB_DEVICE_ID_CYGNAL_RADIO_SI4713 0x8244 + #define USB_VENDOR_ID_CYPRESS 0x04b4 #define USB_DEVICE_ID_CYPRESS_MOUSE 0x0001 #define USB_DEVICE_ID_CYPRESS_HIDCOM 0x5500 diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig index 8270388e2a0d..1d0758aeb8e4 100644 --- a/drivers/media/Kconfig +++ b/drivers/media/Kconfig @@ -172,6 +172,9 @@ comment "Media ancillary drivers (tuners, sensors, i2c, frontends)" config MEDIA_SUBDRV_AUTOSELECT bool "Autoselect ancillary drivers (tuners, sensors, i2c, frontends)" depends on MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT || MEDIA_CAMERA_SUPPORT + depends on HAS_IOMEM + select I2C + select I2C_MUX default y help By default, a media driver auto-selects all possible ancillary diff --git a/drivers/media/dvb-core/dvb-usb-ids.h b/drivers/media/dvb-core/dvb-usb-ids.h index 419a2d6b4349..f19a2ccd1e4b 100644 --- a/drivers/media/dvb-core/dvb-usb-ids.h +++ b/drivers/media/dvb-core/dvb-usb-ids.h @@ -239,6 +239,7 @@ #define USB_PID_AVERMEDIA_A835B_4835 0x4835 #define USB_PID_AVERMEDIA_1867 0x1867 #define USB_PID_AVERMEDIA_A867 0xa867 +#define USB_PID_AVERMEDIA_H335 0x0335 #define USB_PID_AVERMEDIA_TWINSTAR 0x0825 #define USB_PID_TECHNOTREND_CONNECT_S2400 0x3006 #define USB_PID_TECHNOTREND_CONNECT_S2400_8KEEPROM 0x3009 @@ -317,6 +318,7 @@ #define USB_PID_WINFAST_DTV_DONGLE_H 0x60f6 #define USB_PID_WINFAST_DTV_DONGLE_STK7700P_2 0x6f01 #define USB_PID_WINFAST_DTV_DONGLE_GOLD 0x6029 +#define USB_PID_WINFAST_DTV_DONGLE_MINID 0x6f0f #define USB_PID_GENPIX_8PSK_REV_1_COLD 0x0200 #define USB_PID_GENPIX_8PSK_REV_1_WARM 0x0201 #define USB_PID_GENPIX_8PSK_REV_2 0x0202 @@ -365,6 +367,7 @@ #define USB_PID_TERRATEC_DVBS2CI_V2 0x10ac #define USB_PID_TECHNISAT_USB2_HDCI_V1 0x0001 #define USB_PID_TECHNISAT_USB2_HDCI_V2 0x0002 +#define USB_PID_TECHNISAT_USB2_CABLESTAR_HDCI 0x0003 #define USB_PID_TECHNISAT_AIRSTAR_TELESTICK_2 0x0004 #define USB_PID_TECHNISAT_USB2_DVB_S2 0x0500 #define USB_PID_CPYTO_REDI_PC50A 0xa803 diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig index bddbab43a2df..dd12a1ebda82 100644 --- a/drivers/media/dvb-frontends/Kconfig +++ b/drivers/media/dvb-frontends/Kconfig @@ -35,6 +35,13 @@ config DVB_STV6110x help A Silicon tuner that supports DVB-S and DVB-S2 modes +config DVB_M88DS3103 + tristate "Montage M88DS3103" + depends on DVB_CORE && I2C && I2C_MUX + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Say Y when you want to support this frontend. + comment "Multistandard (cable + terrestrial) frontends" depends on DVB_CORE diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile index f9cb43d9aed9..0c75a6aafb9d 100644 --- a/drivers/media/dvb-frontends/Makefile +++ b/drivers/media/dvb-frontends/Makefile @@ -85,6 +85,7 @@ obj-$(CONFIG_DVB_STV6110) += stv6110.o obj-$(CONFIG_DVB_STV0900) += stv0900.o obj-$(CONFIG_DVB_STV090x) += stv090x.o obj-$(CONFIG_DVB_STV6110x) += stv6110x.o +obj-$(CONFIG_DVB_M88DS3103) += m88ds3103.o obj-$(CONFIG_DVB_ISL6423) += isl6423.o obj-$(CONFIG_DVB_EC100) += ec100.o obj-$(CONFIG_DVB_HD29L2) += hd29l2.o diff --git a/drivers/media/dvb-frontends/a8293.c b/drivers/media/dvb-frontends/a8293.c index 74fbb5d58bed..780da58132f1 100644 --- a/drivers/media/dvb-frontends/a8293.c +++ b/drivers/media/dvb-frontends/a8293.c @@ -96,6 +96,8 @@ static int a8293_set_voltage(struct dvb_frontend *fe, if (ret) goto err; + usleep_range(1500, 50000); + return ret; err: dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); diff --git a/drivers/media/dvb-frontends/cx24117.c b/drivers/media/dvb-frontends/cx24117.c index 476b422ccf19..68f768a5422d 100644 --- a/drivers/media/dvb-frontends/cx24117.c +++ b/drivers/media/dvb-frontends/cx24117.c @@ -135,15 +135,33 @@ enum cmds { - CMD_SET_VCO = 0x10, - CMD_TUNEREQUEST = 0x11, - CMD_MPEGCONFIG = 0x13, - CMD_TUNERINIT = 0x14, - CMD_LNBSEND = 0x21, /* Formerly CMD_SEND_DISEQC */ - CMD_LNBDCLEVEL = 0x22, - CMD_SET_TONE = 0x23, - CMD_UPDFWVERS = 0x35, - CMD_TUNERSLEEP = 0x36, + CMD_SET_VCOFREQ = 0x10, + CMD_TUNEREQUEST = 0x11, + CMD_GLOBAL_MPEGCFG = 0x13, + CMD_MPEGCFG = 0x14, + CMD_TUNERINIT = 0x15, + CMD_GET_SRATE = 0x18, + CMD_SET_GOLDCODE = 0x19, + CMD_GET_AGCACC = 0x1a, + CMD_DEMODINIT = 0x1b, + CMD_GETCTLACC = 0x1c, + + CMD_LNBCONFIG = 0x20, + CMD_LNBSEND = 0x21, + CMD_LNBDCLEVEL = 0x22, + CMD_LNBPCBCONFIG = 0x23, + CMD_LNBSENDTONEBST = 0x24, + CMD_LNBUPDREPLY = 0x25, + + CMD_SET_GPIOMODE = 0x30, + CMD_SET_GPIOEN = 0x31, + CMD_SET_GPIODIR = 0x32, + CMD_SET_GPIOOUT = 0x33, + CMD_ENABLERSCORR = 0x34, + CMD_FWVERSION = 0x35, + CMD_SET_SLEEPMODE = 0x36, + CMD_BERCTRL = 0x3c, + CMD_EVENTCTRL = 0x3d, }; static LIST_HEAD(hybrid_tuner_instance_list); @@ -619,8 +637,8 @@ static int cx24117_load_firmware(struct dvb_frontend *fe, cx24117_writereg(state, 0xf7, 0x0c); cx24117_writereg(state, 0xe0, 0x00); - /* CMD 1B */ - cmd.args[0] = 0x1b; + /* Init demodulator */ + cmd.args[0] = CMD_DEMODINIT; cmd.args[1] = 0x00; cmd.args[2] = 0x01; cmd.args[3] = 0x00; @@ -629,8 +647,8 @@ static int cx24117_load_firmware(struct dvb_frontend *fe, if (ret != 0) goto error; - /* CMD 10 */ - cmd.args[0] = CMD_SET_VCO; + /* Set VCO frequency */ + cmd.args[0] = CMD_SET_VCOFREQ; cmd.args[1] = 0x06; cmd.args[2] = 0x2b; cmd.args[3] = 0xd8; @@ -648,8 +666,8 @@ static int cx24117_load_firmware(struct dvb_frontend *fe, if (ret != 0) goto error; - /* CMD 15 */ - cmd.args[0] = 0x15; + /* Tuner init */ + cmd.args[0] = CMD_TUNERINIT; cmd.args[1] = 0x00; cmd.args[2] = 0x01; cmd.args[3] = 0x00; @@ -667,8 +685,8 @@ static int cx24117_load_firmware(struct dvb_frontend *fe, if (ret != 0) goto error; - /* CMD 13 */ - cmd.args[0] = CMD_MPEGCONFIG; + /* Global MPEG config */ + cmd.args[0] = CMD_GLOBAL_MPEGCFG; cmd.args[1] = 0x00; cmd.args[2] = 0x00; cmd.args[3] = 0x00; @@ -679,9 +697,9 @@ static int cx24117_load_firmware(struct dvb_frontend *fe, if (ret != 0) goto error; - /* CMD 14 */ + /* MPEG config for each demod */ for (i = 0; i < 2; i++) { - cmd.args[0] = CMD_TUNERINIT; + cmd.args[0] = CMD_MPEGCFG; cmd.args[1] = (u8) i; cmd.args[2] = 0x00; cmd.args[3] = 0x05; @@ -699,8 +717,8 @@ static int cx24117_load_firmware(struct dvb_frontend *fe, cx24117_writereg(state, 0xcf, 0x00); cx24117_writereg(state, 0xe5, 0x04); - /* Firmware CMD 35: Get firmware version */ - cmd.args[0] = CMD_UPDFWVERS; + /* Get firmware version */ + cmd.args[0] = CMD_FWVERSION; cmd.len = 2; for (i = 0; i < 4; i++) { cmd.args[1] = i; @@ -779,8 +797,8 @@ static int cx24117_read_signal_strength(struct dvb_frontend *fe, u8 reg = (state->demod == 0) ? CX24117_REG_SSTATUS0 : CX24117_REG_SSTATUS1; - /* Firmware CMD 1A */ - cmd.args[0] = 0x1a; + /* Read AGC accumulator register */ + cmd.args[0] = CMD_GET_AGCACC; cmd.args[1] = (u8) state->demod; cmd.len = 2; ret = cx24117_cmd_execute(fe, &cmd); @@ -899,22 +917,15 @@ static int cx24117_set_voltage(struct dvb_frontend *fe, voltage == SEC_VOLTAGE_18 ? "SEC_VOLTAGE_18" : "SEC_VOLTAGE_OFF"); - /* CMD 32 */ - cmd.args[0] = 0x32; - cmd.args[1] = reg; - cmd.args[2] = reg; + /* Prepare a set GPIO logic level CMD */ + cmd.args[0] = CMD_SET_GPIOOUT; + cmd.args[2] = reg; /* mask */ cmd.len = 3; - ret = cx24117_cmd_execute(fe, &cmd); - if (ret) - return ret; if ((voltage == SEC_VOLTAGE_13) || (voltage == SEC_VOLTAGE_18)) { - /* CMD 33 */ - cmd.args[0] = 0x33; + /* power on LNB */ cmd.args[1] = reg; - cmd.args[2] = reg; - cmd.len = 3; ret = cx24117_cmd_execute(fe, &cmd); if (ret != 0) return ret; @@ -926,22 +937,22 @@ static int cx24117_set_voltage(struct dvb_frontend *fe, /* Wait for voltage/min repeat delay */ msleep(100); - /* CMD 22 - CMD_LNBDCLEVEL */ + /* Set 13V/18V select pin */ cmd.args[0] = CMD_LNBDCLEVEL; cmd.args[1] = state->demod ? 0 : 1; cmd.args[2] = (voltage == SEC_VOLTAGE_18 ? 0x01 : 0x00); cmd.len = 3; + ret = cx24117_cmd_execute(fe, &cmd); /* Min delay time before DiSEqC send */ msleep(20); } else { - cmd.args[0] = 0x33; + /* power off LNB */ cmd.args[1] = 0x00; - cmd.args[2] = reg; - cmd.len = 3; + ret = cx24117_cmd_execute(fe, &cmd); } - return cx24117_cmd_execute(fe, &cmd); + return ret; } static int cx24117_set_tone(struct dvb_frontend *fe, @@ -968,8 +979,7 @@ static int cx24117_set_tone(struct dvb_frontend *fe, msleep(20); /* Set the tone */ - /* CMD 23 - CMD_SET_TONE */ - cmd.args[0] = CMD_SET_TONE; + cmd.args[0] = CMD_LNBPCBCONFIG; cmd.args[1] = (state->demod ? 0 : 1); cmd.args[2] = 0x00; cmd.args[3] = 0x00; @@ -1231,8 +1241,8 @@ static int cx24117_initfe(struct dvb_frontend *fe) mutex_lock(&state->priv->fe_lock); - /* Firmware CMD 36: Power config */ - cmd.args[0] = CMD_TUNERSLEEP; + /* Set sleep mode off */ + cmd.args[0] = CMD_SET_SLEEPMODE; cmd.args[1] = (state->demod ? 1 : 0); cmd.args[2] = 0; cmd.len = 3; @@ -1244,8 +1254,8 @@ static int cx24117_initfe(struct dvb_frontend *fe) if (ret != 0) goto exit; - /* CMD 3C */ - cmd.args[0] = 0x3c; + /* Set BER control */ + cmd.args[0] = CMD_BERCTRL; cmd.args[1] = (state->demod ? 1 : 0); cmd.args[2] = 0x10; cmd.args[3] = 0x10; @@ -1254,12 +1264,22 @@ static int cx24117_initfe(struct dvb_frontend *fe) if (ret != 0) goto exit; - /* CMD 34 */ - cmd.args[0] = 0x34; + /* Set RS correction (enable/disable) */ + cmd.args[0] = CMD_ENABLERSCORR; cmd.args[1] = (state->demod ? 1 : 0); cmd.args[2] = CX24117_OCC; cmd.len = 3; ret = cx24117_cmd_execute_nolock(fe, &cmd); + if (ret != 0) + goto exit; + + /* Set GPIO direction */ + /* Set as output - controls LNB power on/off */ + cmd.args[0] = CMD_SET_GPIODIR; + cmd.args[1] = 0x30; + cmd.args[2] = 0x30; + cmd.len = 3; + ret = cx24117_cmd_execute_nolock(fe, &cmd); exit: mutex_unlock(&state->priv->fe_lock); @@ -1278,8 +1298,8 @@ static int cx24117_sleep(struct dvb_frontend *fe) dev_dbg(&state->priv->i2c->dev, "%s() demod%d\n", __func__, state->demod); - /* Firmware CMD 36: Power config */ - cmd.args[0] = CMD_TUNERSLEEP; + /* Set sleep mode on */ + cmd.args[0] = CMD_SET_SLEEPMODE; cmd.args[1] = (state->demod ? 1 : 0); cmd.args[2] = 1; cmd.len = 3; @@ -1558,7 +1578,8 @@ static int cx24117_get_frontend(struct dvb_frontend *fe) u8 buf[0x1f-4]; - cmd.args[0] = 0x1c; + /* Read current tune parameters */ + cmd.args[0] = CMD_GETCTLACC; cmd.args[1] = (u8) state->demod; cmd.len = 2; ret = cx24117_cmd_execute(fe, &cmd); diff --git a/drivers/media/dvb-frontends/dib8000.c b/drivers/media/dvb-frontends/dib8000.c index 6dbbee453ee1..1632d78a5479 100644 --- a/drivers/media/dvb-frontends/dib8000.c +++ b/drivers/media/dvb-frontends/dib8000.c @@ -11,6 +11,7 @@ #include <linux/slab.h> #include <linux/i2c.h> #include <linux/mutex.h> +#include <asm/div64.h> #include "dvb_math.h" @@ -118,6 +119,12 @@ struct dib8000_state { u8 longest_intlv_layer; u16 output_mode; + /* for DVBv5 stats */ + s64 init_ucb; + unsigned long per_jiffies_stats; + unsigned long ber_jiffies_stats; + unsigned long ber_jiffies_stats_layer[3]; + #ifdef DIB8000_AGC_FREEZE u16 agc1_max; u16 agc1_min; @@ -157,15 +164,10 @@ static u16 dib8000_i2c_read16(struct i2c_device *i2c, u16 reg) return ret; } -static u16 dib8000_read_word(struct dib8000_state *state, u16 reg) +static u16 __dib8000_read_word(struct dib8000_state *state, u16 reg) { u16 ret; - if (mutex_lock_interruptible(&state->i2c_buffer_lock) < 0) { - dprintk("could not acquire lock"); - return 0; - } - state->i2c_write_buffer[0] = reg >> 8; state->i2c_write_buffer[1] = reg & 0xff; @@ -183,6 +185,21 @@ static u16 dib8000_read_word(struct dib8000_state *state, u16 reg) dprintk("i2c read error on %d", reg); ret = (state->i2c_read_buffer[0] << 8) | state->i2c_read_buffer[1]; + + return ret; +} + +static u16 dib8000_read_word(struct dib8000_state *state, u16 reg) +{ + u16 ret; + + if (mutex_lock_interruptible(&state->i2c_buffer_lock) < 0) { + dprintk("could not acquire lock"); + return 0; + } + + ret = __dib8000_read_word(state, reg); + mutex_unlock(&state->i2c_buffer_lock); return ret; @@ -192,8 +209,15 @@ static u32 dib8000_read32(struct dib8000_state *state, u16 reg) { u16 rw[2]; - rw[0] = dib8000_read_word(state, reg + 0); - rw[1] = dib8000_read_word(state, reg + 1); + if (mutex_lock_interruptible(&state->i2c_buffer_lock) < 0) { + dprintk("could not acquire lock"); + return 0; + } + + rw[0] = __dib8000_read_word(state, reg + 0); + rw[1] = __dib8000_read_word(state, reg + 1); + + mutex_unlock(&state->i2c_buffer_lock); return ((rw[0] << 16) | (rw[1])); } @@ -787,7 +811,7 @@ int dib8000_update_pll(struct dvb_frontend *fe, dprintk("PLL: Update ratio (prediv: %d, ratio: %d)", state->cfg.pll->pll_prediv, ratio); dib8000_write_word(state, 901, (state->cfg.pll->pll_prediv << 8) | (ratio << 0)); /* only the PLL ratio is updated. */ } -} + } return 0; } @@ -966,6 +990,45 @@ static u16 dib8000_identify(struct i2c_device *client) return value; } +static int dib8000_read_unc_blocks(struct dvb_frontend *fe, u32 *unc); + +static void dib8000_reset_stats(struct dvb_frontend *fe) +{ + struct dib8000_state *state = fe->demodulator_priv; + struct dtv_frontend_properties *c = &state->fe[0]->dtv_property_cache; + u32 ucb; + + memset(&c->strength, 0, sizeof(c->strength)); + memset(&c->cnr, 0, sizeof(c->cnr)); + memset(&c->post_bit_error, 0, sizeof(c->post_bit_error)); + memset(&c->post_bit_count, 0, sizeof(c->post_bit_count)); + memset(&c->block_error, 0, sizeof(c->block_error)); + + c->strength.len = 1; + c->cnr.len = 1; + c->block_error.len = 1; + c->block_count.len = 1; + c->post_bit_error.len = 1; + c->post_bit_count.len = 1; + + c->strength.stat[0].scale = FE_SCALE_DECIBEL; + c->strength.stat[0].uvalue = 0; + + c->cnr.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->block_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->block_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->post_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->post_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + + dib8000_read_unc_blocks(fe, &ucb); + + state->init_ucb = -ucb; + state->ber_jiffies_stats = 0; + state->per_jiffies_stats = 0; + memset(&state->ber_jiffies_stats_layer, 0, + sizeof(state->ber_jiffies_stats_layer)); +} + static int dib8000_reset(struct dvb_frontend *fe) { struct dib8000_state *state = fe->demodulator_priv; @@ -1071,6 +1134,8 @@ static int dib8000_reset(struct dvb_frontend *fe) dib8000_set_power_mode(state, DIB8000_POWER_INTERFACE_ONLY); + dib8000_reset_stats(fe); + return 0; } @@ -2445,7 +2510,8 @@ static int dib8000_autosearch_start(struct dvb_frontend *fe) if (state->revision == 0x8090) internal = dib8000_read32(state, 23) / 1000; - if (state->autosearch_state == AS_SEARCHING_FFT) { + if ((state->revision >= 0x8002) && + (state->autosearch_state == AS_SEARCHING_FFT)) { dib8000_write_word(state, 37, 0x0065); /* P_ctrl_pha_off_max default values */ dib8000_write_word(state, 116, 0x0000); /* P_ana_gain to 0 */ @@ -2481,7 +2547,8 @@ static int dib8000_autosearch_start(struct dvb_frontend *fe) dib8000_write_word(state, 770, (dib8000_read_word(state, 770) & 0xdfff) | (1 << 13)); /* P_restart_ccg = 1 */ dib8000_write_word(state, 770, (dib8000_read_word(state, 770) & 0xdfff) | (0 << 13)); /* P_restart_ccg = 0 */ dib8000_write_word(state, 0, (dib8000_read_word(state, 0) & 0x7ff) | (0 << 15) | (1 << 13)); /* P_restart_search = 0; */ - } else if (state->autosearch_state == AS_SEARCHING_GUARD) { + } else if ((state->revision >= 0x8002) && + (state->autosearch_state == AS_SEARCHING_GUARD)) { c->transmission_mode = TRANSMISSION_MODE_8K; c->guard_interval = GUARD_INTERVAL_1_8; c->inversion = 0; @@ -2583,7 +2650,8 @@ static int dib8000_autosearch_irq(struct dvb_frontend *fe) struct dib8000_state *state = fe->demodulator_priv; u16 irq_pending = dib8000_read_word(state, 1284); - if (state->autosearch_state == AS_SEARCHING_FFT) { + if ((state->revision >= 0x8002) && + (state->autosearch_state == AS_SEARCHING_FFT)) { if (irq_pending & 0x1) { dprintk("dib8000_autosearch_irq: max correlation result available"); return 3; @@ -2853,6 +2921,91 @@ static int dib8090p_init_sdram(struct dib8000_state *state) return 0; } +/** + * is_manual_mode - Check if TMCC should be used for parameters settings + * @c: struct dvb_frontend_properties + * + * By default, TMCC table should be used for parameter settings on most + * usercases. However, sometimes it is desirable to lock the demod to + * use the manual parameters. + * + * On manual mode, the current dib8000_tune state machine is very restrict: + * It requires that both per-layer and per-transponder parameters to be + * properly specified, otherwise the device won't lock. + * + * Check if all those conditions are properly satisfied before allowing + * the device to use the manual frequency lock mode. + */ +static int is_manual_mode(struct dtv_frontend_properties *c) +{ + int i, n_segs = 0; + + /* Use auto mode on DVB-T compat mode */ + if (c->delivery_system != SYS_ISDBT) + return 0; + + /* + * Transmission mode is only detected on auto mode, currently + */ + if (c->transmission_mode == TRANSMISSION_MODE_AUTO) { + dprintk("transmission mode auto"); + return 0; + } + + /* + * Guard interval is only detected on auto mode, currently + */ + if (c->guard_interval == GUARD_INTERVAL_AUTO) { + dprintk("guard interval auto"); + return 0; + } + + /* + * If no layer is enabled, assume auto mode, as at least one + * layer should be enabled + */ + if (!c->isdbt_layer_enabled) { + dprintk("no layer modulation specified"); + return 0; + } + + /* + * Check if the per-layer parameters aren't auto and + * disable a layer if segment count is 0 or invalid. + */ + for (i = 0; i < 3; i++) { + if (!(c->isdbt_layer_enabled & 1 << i)) + continue; + + if ((c->layer[i].segment_count > 13) || + (c->layer[i].segment_count == 0)) { + c->isdbt_layer_enabled &= ~(1 << i); + continue; + } + + n_segs += c->layer[i].segment_count; + + if ((c->layer[i].modulation == QAM_AUTO) || + (c->layer[i].fec == FEC_AUTO)) { + dprintk("layer %c has either modulation or FEC auto", + 'A' + i); + return 0; + } + } + + /* + * Userspace specified a wrong number of segments. + * fallback to auto mode. + */ + if (n_segs == 0 || n_segs > 13) { + dprintk("number of segments is invalid"); + return 0; + } + + /* Everything looks ok for manual mode */ + return 1; +} + static int dib8000_tune(struct dvb_frontend *fe) { struct dib8000_state *state = fe->demodulator_priv; @@ -2878,40 +3031,19 @@ static int dib8000_tune(struct dvb_frontend *fe) switch (*tune_state) { case CT_DEMOD_START: /* 30 */ + dib8000_reset_stats(fe); + if (state->revision == 0x8090) dib8090p_init_sdram(state); state->status = FE_STATUS_TUNE_PENDING; - if ((c->delivery_system != SYS_ISDBT) || - (c->inversion == INVERSION_AUTO) || - (c->transmission_mode == TRANSMISSION_MODE_AUTO) || - (c->guard_interval == GUARD_INTERVAL_AUTO) || - (((c->isdbt_layer_enabled & (1 << 0)) != 0) && - (c->layer[0].segment_count != 0xff) && - (c->layer[0].segment_count != 0) && - ((c->layer[0].modulation == QAM_AUTO) || - (c->layer[0].fec == FEC_AUTO))) || - (((c->isdbt_layer_enabled & (1 << 1)) != 0) && - (c->layer[1].segment_count != 0xff) && - (c->layer[1].segment_count != 0) && - ((c->layer[1].modulation == QAM_AUTO) || - (c->layer[1].fec == FEC_AUTO))) || - (((c->isdbt_layer_enabled & (1 << 2)) != 0) && - (c->layer[2].segment_count != 0xff) && - (c->layer[2].segment_count != 0) && - ((c->layer[2].modulation == QAM_AUTO) || - (c->layer[2].fec == FEC_AUTO))) || - (((c->layer[0].segment_count == 0) || - ((c->isdbt_layer_enabled & (1 << 0)) == 0)) && - ((c->layer[1].segment_count == 0) || - ((c->isdbt_layer_enabled & (2 << 0)) == 0)) && - ((c->layer[2].segment_count == 0) || ((c->isdbt_layer_enabled & (3 << 0)) == 0)))) - state->channel_parameters_set = 0; /* auto search */ - else - state->channel_parameters_set = 1; /* channel parameters are known */ + state->channel_parameters_set = is_manual_mode(c); + + dprintk("Tuning channel on %s search mode", + state->channel_parameters_set ? "manual" : "auto"); dib8000_viterbi_state(state, 0); /* force chan dec in restart */ - /* Layer monit */ + /* Layer monitor */ dib8000_write_word(state, 285, dib8000_read_word(state, 285) & 0x60); dib8000_set_frequency_offset(state); @@ -3256,15 +3388,27 @@ static int dib8000_sleep(struct dvb_frontend *fe) return dib8000_set_adc_state(state, DIBX000_SLOW_ADC_OFF) | dib8000_set_adc_state(state, DIBX000_ADC_OFF); } +static int dib8000_read_status(struct dvb_frontend *fe, fe_status_t * stat); + static int dib8000_get_frontend(struct dvb_frontend *fe) { struct dib8000_state *state = fe->demodulator_priv; u16 i, val = 0; - fe_status_t stat; + fe_status_t stat = 0; u8 index_frontend, sub_index_frontend; fe->dtv_property_cache.bandwidth_hz = 6000000; + /* + * If called to early, get_frontend makes dib8000_tune to either + * not lock or not sync. This causes dvbv5-scan/dvbv5-zap to fail. + * So, let's just return if frontend 0 has not locked. + */ + dib8000_read_status(fe, &stat); + if (!(stat & FE_HAS_SYNC)) + return 0; + + dprintk("TMCC lock"); for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { state->fe[index_frontend]->ops.read_status(state->fe[index_frontend], &stat); if (stat&FE_HAS_SYNC) { @@ -3335,9 +3479,13 @@ static int dib8000_get_frontend(struct dvb_frontend *fe) fe->dtv_property_cache.layer[i].segment_count = val & 0x0F; dprintk("dib8000_get_frontend : Layer %d segments = %d ", i, fe->dtv_property_cache.layer[i].segment_count); - val = dib8000_read_word(state, 499 + i); - fe->dtv_property_cache.layer[i].interleaving = val & 0x3; - dprintk("dib8000_get_frontend : Layer %d time_intlv = %d ", i, fe->dtv_property_cache.layer[i].interleaving); + val = dib8000_read_word(state, 499 + i) & 0x3; + /* Interleaving can be 0, 1, 2 or 4 */ + if (val == 3) + val = 4; + fe->dtv_property_cache.layer[i].interleaving = val; + dprintk("dib8000_get_frontend : Layer %d time_intlv = %d ", + i, fe->dtv_property_cache.layer[i].interleaving); val = dib8000_read_word(state, 481 + i); switch (val & 0x7) { @@ -3556,6 +3704,8 @@ static int dib8000_set_frontend(struct dvb_frontend *fe) return 0; } +static int dib8000_get_stats(struct dvb_frontend *fe, fe_status_t stat); + static int dib8000_read_status(struct dvb_frontend *fe, fe_status_t * stat) { struct dib8000_state *state = fe->demodulator_priv; @@ -3593,6 +3743,7 @@ static int dib8000_read_status(struct dvb_frontend *fe, fe_status_t * stat) if (lock & 0x01) *stat |= FE_HAS_VITERBI; } + dib8000_get_stats(fe, *stat); return 0; } @@ -3699,6 +3850,357 @@ static int dib8000_read_snr(struct dvb_frontend *fe, u16 * snr) return 0; } +struct per_layer_regs { + u16 lock, ber, per; +}; + +static const struct per_layer_regs per_layer_regs[] = { + { 554, 560, 562 }, + { 555, 576, 578 }, + { 556, 581, 583 }, +}; + +struct linear_segments { + unsigned x; + signed y; +}; + +/* + * Table to estimate signal strength in dBm. + * This table was empirically determinated by measuring the signal + * strength generated by a DTA-2111 RF generator directly connected into + * a dib8076 device (a PixelView PV-D231U stick), using a good quality + * 3 meters RC6 cable and good RC6 connectors. + * The real value can actually be different on other devices, depending + * on several factors, like if LNA is enabled or not, if diversity is + * enabled, type of connectors, etc. + * Yet, it is better to use this measure in dB than a random non-linear + * percentage value, especially for antenna adjustments. + * On my tests, the precision of the measure using this table is about + * 0.5 dB, with sounds reasonable enough. + */ +static struct linear_segments strength_to_db_table[] = { + { 55953, 108500 }, /* -22.5 dBm */ + { 55394, 108000 }, + { 53834, 107000 }, + { 52863, 106000 }, + { 52239, 105000 }, + { 52012, 104000 }, + { 51803, 103000 }, + { 51566, 102000 }, + { 51356, 101000 }, + { 51112, 100000 }, + { 50869, 99000 }, + { 50600, 98000 }, + { 50363, 97000 }, + { 50117, 96000 }, /* -35 dBm */ + { 49889, 95000 }, + { 49680, 94000 }, + { 49493, 93000 }, + { 49302, 92000 }, + { 48929, 91000 }, + { 48416, 90000 }, + { 48035, 89000 }, + { 47593, 88000 }, + { 47282, 87000 }, + { 46953, 86000 }, + { 46698, 85000 }, + { 45617, 84000 }, + { 44773, 83000 }, + { 43845, 82000 }, + { 43020, 81000 }, + { 42010, 80000 }, /* -51 dBm */ + { 0, 0 }, +}; + +static u32 interpolate_value(u32 value, struct linear_segments *segments, + unsigned len) +{ + u64 tmp64; + u32 dx; + s32 dy; + int i, ret; + + if (value >= segments[0].x) + return segments[0].y; + if (value < segments[len-1].x) + return segments[len-1].y; + + for (i = 1; i < len - 1; i++) { + /* If value is identical, no need to interpolate */ + if (value == segments[i].x) + return segments[i].y; + if (value > segments[i].x) + break; + } + + /* Linear interpolation between the two (x,y) points */ + dy = segments[i - 1].y - segments[i].y; + dx = segments[i - 1].x - segments[i].x; + + tmp64 = value - segments[i].x; + tmp64 *= dy; + do_div(tmp64, dx); + ret = segments[i].y + tmp64; + + return ret; +} + +static u32 dib8000_get_time_us(struct dvb_frontend *fe, int layer) +{ + struct dib8000_state *state = fe->demodulator_priv; + struct dtv_frontend_properties *c = &state->fe[0]->dtv_property_cache; + int ini_layer, end_layer, i; + u64 time_us, tmp64; + u32 tmp, denom; + int guard, rate_num, rate_denum = 1, bits_per_symbol, nsegs; + int interleaving = 0, fft_div; + + if (layer >= 0) { + ini_layer = layer; + end_layer = layer + 1; + } else { + ini_layer = 0; + end_layer = 3; + } + + switch (c->guard_interval) { + case GUARD_INTERVAL_1_4: + guard = 4; + break; + case GUARD_INTERVAL_1_8: + guard = 8; + break; + case GUARD_INTERVAL_1_16: + guard = 16; + break; + default: + case GUARD_INTERVAL_1_32: + guard = 32; + break; + } + + switch (c->transmission_mode) { + case TRANSMISSION_MODE_2K: + fft_div = 4; + break; + case TRANSMISSION_MODE_4K: + fft_div = 2; + break; + default: + case TRANSMISSION_MODE_8K: + fft_div = 1; + break; + } + + denom = 0; + for (i = ini_layer; i < end_layer; i++) { + nsegs = c->layer[i].segment_count; + if (nsegs == 0 || nsegs > 13) + continue; + + switch (c->layer[i].modulation) { + case DQPSK: + case QPSK: + bits_per_symbol = 2; + break; + case QAM_16: + bits_per_symbol = 4; + break; + default: + case QAM_64: + bits_per_symbol = 6; + break; + } + + switch (c->layer[i].fec) { + case FEC_1_2: + rate_num = 1; + rate_denum = 2; + break; + case FEC_2_3: + rate_num = 2; + rate_denum = 3; + break; + case FEC_3_4: + rate_num = 3; + rate_denum = 4; + break; + case FEC_5_6: + rate_num = 5; + rate_denum = 6; + break; + default: + case FEC_7_8: + rate_num = 7; + rate_denum = 8; + break; + } + + interleaving = c->layer[i].interleaving; + + denom += bits_per_symbol * rate_num * fft_div * nsegs * 384; + } + + /* If all goes wrong, wait for 1s for the next stats */ + if (!denom) + return 0; + + /* Estimate the period for the total bit rate */ + time_us = rate_denum * (1008 * 1562500L); + tmp64 = time_us; + do_div(tmp64, guard); + time_us = time_us + tmp64; + time_us += denom / 2; + do_div(time_us, denom); + + tmp = 1008 * 96 * interleaving; + time_us += tmp + tmp / guard; + + return time_us; +} + +static int dib8000_get_stats(struct dvb_frontend *fe, fe_status_t stat) +{ + struct dib8000_state *state = fe->demodulator_priv; + struct dtv_frontend_properties *c = &state->fe[0]->dtv_property_cache; + int i; + int show_per_stats = 0; + u32 time_us = 0, snr, val; + u64 blocks; + s32 db; + u16 strength; + + /* Get Signal strength */ + dib8000_read_signal_strength(fe, &strength); + val = strength; + db = interpolate_value(val, + strength_to_db_table, + ARRAY_SIZE(strength_to_db_table)) - 131000; + c->strength.stat[0].svalue = db; + + /* UCB/BER/CNR measures require lock */ + if (!(stat & FE_HAS_LOCK)) { + c->cnr.len = 1; + c->block_count.len = 1; + c->block_error.len = 1; + c->post_bit_error.len = 1; + c->post_bit_count.len = 1; + c->cnr.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->post_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->post_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->block_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->block_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + return 0; + } + + /* Check if time for stats was elapsed */ + if (time_after(jiffies, state->per_jiffies_stats)) { + state->per_jiffies_stats = jiffies + msecs_to_jiffies(1000); + + /* Get SNR */ + snr = dib8000_get_snr(fe); + for (i = 1; i < MAX_NUMBER_OF_FRONTENDS; i++) { + if (state->fe[i]) + snr += dib8000_get_snr(state->fe[i]); + } + snr = snr >> 16; + + if (snr) { + snr = 10 * intlog10(snr); + snr = (1000L * snr) >> 24; + } else { + snr = 0; + } + c->cnr.stat[0].svalue = snr; + c->cnr.stat[0].scale = FE_SCALE_DECIBEL; + + /* Get UCB measures */ + dib8000_read_unc_blocks(fe, &val); + if (val < state->init_ucb) + state->init_ucb += 0x100000000LL; + + c->block_error.stat[0].scale = FE_SCALE_COUNTER; + c->block_error.stat[0].uvalue = val + state->init_ucb; + + /* Estimate the number of packets based on bitrate */ + if (!time_us) + time_us = dib8000_get_time_us(fe, -1); + + if (time_us) { + blocks = 1250000ULL * 1000000ULL; + do_div(blocks, time_us * 8 * 204); + c->block_count.stat[0].scale = FE_SCALE_COUNTER; + c->block_count.stat[0].uvalue += blocks; + } + + show_per_stats = 1; + } + + /* Get post-BER measures */ + if (time_after(jiffies, state->ber_jiffies_stats)) { + time_us = dib8000_get_time_us(fe, -1); + state->ber_jiffies_stats = jiffies + msecs_to_jiffies((time_us + 500) / 1000); + + dprintk("Next all layers stats available in %u us.", time_us); + + dib8000_read_ber(fe, &val); + c->post_bit_error.stat[0].scale = FE_SCALE_COUNTER; + c->post_bit_error.stat[0].uvalue += val; + + c->post_bit_count.stat[0].scale = FE_SCALE_COUNTER; + c->post_bit_count.stat[0].uvalue += 100000000; + } + + if (state->revision < 0x8002) + return 0; + + c->block_error.len = 4; + c->post_bit_error.len = 4; + c->post_bit_count.len = 4; + + for (i = 0; i < 3; i++) { + unsigned nsegs = c->layer[i].segment_count; + + if (nsegs == 0 || nsegs > 13) + continue; + + time_us = 0; + + if (time_after(jiffies, state->ber_jiffies_stats_layer[i])) { + time_us = dib8000_get_time_us(fe, i); + + state->ber_jiffies_stats_layer[i] = jiffies + msecs_to_jiffies((time_us + 500) / 1000); + dprintk("Next layer %c stats will be available in %u us\n", + 'A' + i, time_us); + + val = dib8000_read_word(state, per_layer_regs[i].ber); + c->post_bit_error.stat[1 + i].scale = FE_SCALE_COUNTER; + c->post_bit_error.stat[1 + i].uvalue += val; + + c->post_bit_count.stat[1 + i].scale = FE_SCALE_COUNTER; + c->post_bit_count.stat[1 + i].uvalue += 100000000; + } + + if (show_per_stats) { + val = dib8000_read_word(state, per_layer_regs[i].per); + + c->block_error.stat[1 + i].scale = FE_SCALE_COUNTER; + c->block_error.stat[1 + i].uvalue += val; + + if (!time_us) + time_us = dib8000_get_time_us(fe, i); + if (time_us) { + blocks = 1250000ULL * 1000000ULL; + do_div(blocks, time_us * 8 * 204); + c->block_count.stat[0].scale = FE_SCALE_COUNTER; + c->block_count.stat[0].uvalue += blocks; + } + } + } + return 0; +} + int dib8000_set_slave_frontend(struct dvb_frontend *fe, struct dvb_frontend *fe_slave) { struct dib8000_state *state = fe->demodulator_priv; diff --git a/drivers/media/dvb-frontends/drxk.h b/drivers/media/dvb-frontends/drxk.h index f22eb9f13ad5..f6cb34660327 100644 --- a/drivers/media/dvb-frontends/drxk.h +++ b/drivers/media/dvb-frontends/drxk.h @@ -29,7 +29,6 @@ * A value of 0 (default) or lower indicates that * the correct number of parameters will be * automatically detected. - * @load_firmware_sync: Force the firmware load to be synchronous. * * On the *_gpio vars, bit 0 is UIO-1, bit 1 is UIO-2 and bit 2 is * UIO-3. @@ -41,7 +40,6 @@ struct drxk_config { bool parallel_ts; bool dynamic_clk; bool enable_merr_cfg; - bool load_firmware_sync; bool antenna_dvbt; u16 antenna_gpio; diff --git a/drivers/media/dvb-frontends/drxk_hard.c b/drivers/media/dvb-frontends/drxk_hard.c index bf29a3f0e6f0..cce94a75b2e1 100644 --- a/drivers/media/dvb-frontends/drxk_hard.c +++ b/drivers/media/dvb-frontends/drxk_hard.c @@ -6830,25 +6830,13 @@ struct dvb_frontend *drxk_attach(const struct drxk_config *config, /* Load firmware and initialize DRX-K */ if (state->microcode_name) { - if (config->load_firmware_sync) { - const struct firmware *fw = NULL; + const struct firmware *fw = NULL; - status = request_firmware(&fw, state->microcode_name, - state->i2c->dev.parent); - if (status < 0) - fw = NULL; - load_firmware_cb(fw, state); - } else { - status = request_firmware_nowait(THIS_MODULE, 1, - state->microcode_name, - state->i2c->dev.parent, - GFP_KERNEL, - state, load_firmware_cb); - if (status < 0) { - pr_err("failed to request a firmware\n"); - return NULL; - } - } + status = request_firmware(&fw, state->microcode_name, + state->i2c->dev.parent); + if (status < 0) + fw = NULL; + load_firmware_cb(fw, state); } else if (init_drxk(state) < 0) goto error; diff --git a/drivers/media/dvb-frontends/m88ds3103.c b/drivers/media/dvb-frontends/m88ds3103.c new file mode 100644 index 000000000000..b8a7897e7bd8 --- /dev/null +++ b/drivers/media/dvb-frontends/m88ds3103.c @@ -0,0 +1,1311 @@ +/* + * Montage M88DS3103 demodulator 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. + */ + +#include "m88ds3103_priv.h" + +static struct dvb_frontend_ops m88ds3103_ops; + +/* write multiple registers */ +static int m88ds3103_wr_regs(struct m88ds3103_priv *priv, + u8 reg, const u8 *val, int len) +{ +#define MAX_WR_LEN 32 +#define MAX_WR_XFER_LEN (MAX_WR_LEN + 1) + int ret; + u8 buf[MAX_WR_XFER_LEN]; + struct i2c_msg msg[1] = { + { + .addr = priv->cfg->i2c_addr, + .flags = 0, + .len = 1 + len, + .buf = buf, + } + }; + + if (WARN_ON(len > MAX_WR_LEN)) + return -EINVAL; + + buf[0] = reg; + memcpy(&buf[1], val, len); + + mutex_lock(&priv->i2c_mutex); + ret = i2c_transfer(priv->i2c, msg, 1); + mutex_unlock(&priv->i2c_mutex); + 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); + ret = -EREMOTEIO; + } + + return ret; +} + +/* read multiple registers */ +static int m88ds3103_rd_regs(struct m88ds3103_priv *priv, + u8 reg, u8 *val, int len) +{ +#define MAX_RD_LEN 3 +#define MAX_RD_XFER_LEN (MAX_RD_LEN) + int ret; + u8 buf[MAX_RD_XFER_LEN]; + struct i2c_msg msg[2] = { + { + .addr = priv->cfg->i2c_addr, + .flags = 0, + .len = 1, + .buf = ®, + }, { + .addr = priv->cfg->i2c_addr, + .flags = I2C_M_RD, + .len = len, + .buf = buf, + } + }; + + if (WARN_ON(len > MAX_RD_LEN)) + return -EINVAL; + + mutex_lock(&priv->i2c_mutex); + ret = i2c_transfer(priv->i2c, msg, 2); + mutex_unlock(&priv->i2c_mutex); + if (ret == 2) { + 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); + ret = -EREMOTEIO; + } + + return ret; +} + +/* write single register */ +static int m88ds3103_wr_reg(struct m88ds3103_priv *priv, u8 reg, u8 val) +{ + return m88ds3103_wr_regs(priv, reg, &val, 1); +} + +/* read single register */ +static int m88ds3103_rd_reg(struct m88ds3103_priv *priv, u8 reg, u8 *val) +{ + return m88ds3103_rd_regs(priv, reg, val, 1); +} + +/* write single register with mask */ +static int m88ds3103_wr_reg_mask(struct m88ds3103_priv *priv, + u8 reg, u8 val, u8 mask) +{ + int ret; + u8 u8tmp; + + /* no need for read if whole reg is written */ + if (mask != 0xff) { + ret = m88ds3103_rd_regs(priv, reg, &u8tmp, 1); + if (ret) + return ret; + + val &= mask; + u8tmp &= ~mask; + val |= u8tmp; + } + + return m88ds3103_wr_regs(priv, reg, &val, 1); +} + +/* read single register with mask */ +static int m88ds3103_rd_reg_mask(struct m88ds3103_priv *priv, + u8 reg, u8 *val, u8 mask) +{ + int ret, i; + u8 u8tmp; + + ret = m88ds3103_rd_regs(priv, reg, &u8tmp, 1); + if (ret) + return ret; + + u8tmp &= mask; + + /* find position of the first bit */ + for (i = 0; i < 8; i++) { + if ((mask >> i) & 0x01) + break; + } + *val = u8tmp >> i; + + return 0; +} + +/* write reg val table using reg addr auto increment */ +static int m88ds3103_wr_reg_val_tab(struct m88ds3103_priv *priv, + const struct m88ds3103_reg_val *tab, int tab_len) +{ + int ret, i, j; + u8 buf[83]; + dev_dbg(&priv->i2c->dev, "%s: tab_len=%d\n", __func__, tab_len); + + if (tab_len > 83) { + ret = -EINVAL; + goto err; + } + + for (i = 0, j = 0; i < tab_len; i++, j++) { + buf[j] = tab[i].val; + + if (i == tab_len - 1 || tab[i].reg != tab[i + 1].reg - 1 || + !((j + 1) % (priv->cfg->i2c_wr_max - 1))) { + ret = m88ds3103_wr_regs(priv, tab[i].reg - j, buf, j + 1); + if (ret) + goto err; + + j = -1; + } + } + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + u8 u8tmp; + + *status = 0; + + if (!priv->warm) { + ret = -EAGAIN; + goto err; + } + + switch (c->delivery_system) { + case SYS_DVBS: + ret = m88ds3103_rd_reg_mask(priv, 0xd1, &u8tmp, 0x07); + if (ret) + goto err; + + if (u8tmp == 0x07) + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC | + FE_HAS_LOCK; + break; + case SYS_DVBS2: + ret = m88ds3103_rd_reg_mask(priv, 0x0d, &u8tmp, 0x8f); + if (ret) + goto err; + + if (u8tmp == 0x8f) + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC | + FE_HAS_LOCK; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n", + __func__); + ret = -EINVAL; + goto err; + } + + priv->fe_status = *status; + + dev_dbg(&priv->i2c->dev, "%s: lock=%02x status=%02x\n", + __func__, u8tmp, *status); + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_set_frontend(struct dvb_frontend *fe) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret, len; + const struct m88ds3103_reg_val *init; + u8 u8tmp, u8tmp1, u8tmp2; + u8 buf[2]; + u16 u16tmp, divide_ratio; + u32 tuner_frequency, target_mclk, ts_clk; + s32 s32tmp; + dev_dbg(&priv->i2c->dev, + "%s: delivery_system=%d modulation=%d frequency=%d symbol_rate=%d inversion=%d pilot=%d rolloff=%d\n", + __func__, c->delivery_system, + c->modulation, c->frequency, c->symbol_rate, + c->inversion, c->pilot, c->rolloff); + + if (!priv->warm) { + ret = -EAGAIN; + goto err; + } + + /* program tuner */ + if (fe->ops.tuner_ops.set_params) { + ret = fe->ops.tuner_ops.set_params(fe); + if (ret) + goto err; + } + + if (fe->ops.tuner_ops.get_frequency) { + ret = fe->ops.tuner_ops.get_frequency(fe, &tuner_frequency); + if (ret) + goto err; + } + + /* reset */ + ret = m88ds3103_wr_reg(priv, 0x07, 0x80); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0x07, 0x00); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0xb2, 0x01); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0x00, 0x01); + if (ret) + goto err; + + switch (c->delivery_system) { + case SYS_DVBS: + len = ARRAY_SIZE(m88ds3103_dvbs_init_reg_vals); + init = m88ds3103_dvbs_init_reg_vals; + target_mclk = 96000; + break; + case SYS_DVBS2: + len = ARRAY_SIZE(m88ds3103_dvbs2_init_reg_vals); + init = m88ds3103_dvbs2_init_reg_vals; + + switch (priv->cfg->ts_mode) { + case M88DS3103_TS_SERIAL: + case M88DS3103_TS_SERIAL_D7: + if (c->symbol_rate < 18000000) + target_mclk = 96000; + else + target_mclk = 144000; + break; + case M88DS3103_TS_PARALLEL: + case M88DS3103_TS_PARALLEL_12: + case M88DS3103_TS_PARALLEL_16: + case M88DS3103_TS_PARALLEL_19_2: + case M88DS3103_TS_CI: + if (c->symbol_rate < 18000000) + target_mclk = 96000; + else if (c->symbol_rate < 28000000) + target_mclk = 144000; + else + target_mclk = 192000; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid ts_mode\n", + __func__); + ret = -EINVAL; + goto err; + } + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n", + __func__); + ret = -EINVAL; + goto err; + } + + /* program init table */ + if (c->delivery_system != priv->delivery_system) { + ret = m88ds3103_wr_reg_val_tab(priv, init, len); + if (ret) + goto err; + } + + u8tmp1 = 0; /* silence compiler warning */ + switch (priv->cfg->ts_mode) { + case M88DS3103_TS_SERIAL: + u8tmp1 = 0x00; + ts_clk = 0; + u8tmp = 0x46; + break; + case M88DS3103_TS_SERIAL_D7: + u8tmp1 = 0x20; + ts_clk = 0; + u8tmp = 0x46; + break; + case M88DS3103_TS_PARALLEL: + ts_clk = 24000; + u8tmp = 0x42; + break; + case M88DS3103_TS_PARALLEL_12: + ts_clk = 12000; + u8tmp = 0x42; + break; + case M88DS3103_TS_PARALLEL_16: + ts_clk = 16000; + u8tmp = 0x42; + break; + case M88DS3103_TS_PARALLEL_19_2: + ts_clk = 19200; + u8tmp = 0x42; + break; + case M88DS3103_TS_CI: + ts_clk = 6000; + u8tmp = 0x43; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid ts_mode\n", __func__); + ret = -EINVAL; + goto err; + } + + /* TS mode */ + ret = m88ds3103_wr_reg(priv, 0xfd, u8tmp); + if (ret) + goto err; + + switch (priv->cfg->ts_mode) { + case M88DS3103_TS_SERIAL: + case M88DS3103_TS_SERIAL_D7: + ret = m88ds3103_wr_reg_mask(priv, 0x29, u8tmp1, 0x20); + if (ret) + goto err; + } + + if (ts_clk) { + divide_ratio = DIV_ROUND_UP(target_mclk, ts_clk); + u8tmp1 = divide_ratio / 2; + u8tmp2 = DIV_ROUND_UP(divide_ratio, 2); + } else { + divide_ratio = 0; + u8tmp1 = 0; + u8tmp2 = 0; + } + + dev_dbg(&priv->i2c->dev, + "%s: target_mclk=%d ts_clk=%d divide_ratio=%d\n", + __func__, target_mclk, ts_clk, divide_ratio); + + u8tmp1--; + u8tmp2--; + /* u8tmp1[5:2] => fe[3:0], u8tmp1[1:0] => ea[7:6] */ + u8tmp1 &= 0x3f; + /* u8tmp2[5:0] => ea[5:0] */ + u8tmp2 &= 0x3f; + + ret = m88ds3103_rd_reg(priv, 0xfe, &u8tmp); + if (ret) + goto err; + + u8tmp = ((u8tmp & 0xf0) << 0) | u8tmp1 >> 2; + ret = m88ds3103_wr_reg(priv, 0xfe, u8tmp); + if (ret) + goto err; + + u8tmp = ((u8tmp1 & 0x03) << 6) | u8tmp2 >> 0; + ret = m88ds3103_wr_reg(priv, 0xea, u8tmp); + if (ret) + goto err; + + switch (target_mclk) { + case 72000: + u8tmp1 = 0x00; /* 0b00 */ + u8tmp2 = 0x03; /* 0b11 */ + break; + case 96000: + u8tmp1 = 0x02; /* 0b10 */ + u8tmp2 = 0x01; /* 0b01 */ + break; + case 115200: + u8tmp1 = 0x01; /* 0b01 */ + u8tmp2 = 0x01; /* 0b01 */ + break; + case 144000: + u8tmp1 = 0x00; /* 0b00 */ + u8tmp2 = 0x01; /* 0b01 */ + break; + case 192000: + u8tmp1 = 0x03; /* 0b11 */ + u8tmp2 = 0x00; /* 0b00 */ + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid target_mclk\n", __func__); + ret = -EINVAL; + goto err; + } + + ret = m88ds3103_wr_reg_mask(priv, 0x22, u8tmp1 << 6, 0xc0); + if (ret) + goto err; + + ret = m88ds3103_wr_reg_mask(priv, 0x24, u8tmp2 << 6, 0xc0); + if (ret) + goto err; + + if (c->symbol_rate <= 3000000) + u8tmp = 0x20; + else if (c->symbol_rate <= 10000000) + u8tmp = 0x10; + else + u8tmp = 0x06; + + ret = m88ds3103_wr_reg(priv, 0xc3, 0x08); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0xc8, u8tmp); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0xc4, 0x08); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0xc7, 0x00); + if (ret) + goto err; + + u16tmp = DIV_ROUND_CLOSEST((c->symbol_rate / 1000) << 15, M88DS3103_MCLK_KHZ / 2); + buf[0] = (u16tmp >> 0) & 0xff; + buf[1] = (u16tmp >> 8) & 0xff; + ret = m88ds3103_wr_regs(priv, 0x61, buf, 2); + if (ret) + goto err; + + ret = m88ds3103_wr_reg_mask(priv, 0x4d, priv->cfg->spec_inv << 1, 0x02); + if (ret) + goto err; + + ret = m88ds3103_wr_reg_mask(priv, 0x30, priv->cfg->agc_inv << 4, 0x10); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0x33, priv->cfg->agc); + if (ret) + goto err; + + dev_dbg(&priv->i2c->dev, "%s: carrier offset=%d\n", __func__, + (tuner_frequency - c->frequency)); + + s32tmp = 0x10000 * (tuner_frequency - c->frequency); + s32tmp = DIV_ROUND_CLOSEST(s32tmp, M88DS3103_MCLK_KHZ); + if (s32tmp < 0) + s32tmp += 0x10000; + + buf[0] = (s32tmp >> 0) & 0xff; + buf[1] = (s32tmp >> 8) & 0xff; + ret = m88ds3103_wr_regs(priv, 0x5e, buf, 2); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0x00, 0x00); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0xb2, 0x00); + if (ret) + goto err; + + priv->delivery_system = c->delivery_system; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_init(struct dvb_frontend *fe) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + int ret, len, remaining; + const struct firmware *fw = NULL; + u8 *fw_file = M88DS3103_FIRMWARE; + u8 u8tmp; + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); + + /* set cold state by default */ + priv->warm = false; + + /* wake up device from sleep */ + ret = m88ds3103_wr_reg_mask(priv, 0x08, 0x01, 0x01); + if (ret) + goto err; + + ret = m88ds3103_wr_reg_mask(priv, 0x04, 0x00, 0x01); + if (ret) + goto err; + + ret = m88ds3103_wr_reg_mask(priv, 0x23, 0x00, 0x10); + if (ret) + goto err; + + /* reset */ + ret = m88ds3103_wr_reg(priv, 0x07, 0x60); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0x07, 0x00); + if (ret) + goto err; + + /* firmware status */ + ret = m88ds3103_rd_reg(priv, 0xb9, &u8tmp); + if (ret) + goto err; + + dev_dbg(&priv->i2c->dev, "%s: firmware=%02x\n", __func__, u8tmp); + + if (u8tmp) + goto skip_fw_download; + + /* cold state - try to download firmware */ + dev_info(&priv->i2c->dev, "%s: found a '%s' in cold state\n", + KBUILD_MODNAME, m88ds3103_ops.info.name); + + /* request the firmware, this will block and timeout */ + ret = request_firmware(&fw, fw_file, priv->i2c->dev.parent); + if (ret) { + dev_err(&priv->i2c->dev, "%s: firmare file '%s' not found\n", + KBUILD_MODNAME, fw_file); + goto err; + } + + dev_info(&priv->i2c->dev, "%s: downloading firmware from file '%s'\n", + KBUILD_MODNAME, fw_file); + + ret = m88ds3103_wr_reg(priv, 0xb2, 0x01); + if (ret) + goto err; + + for (remaining = fw->size; remaining > 0; + remaining -= (priv->cfg->i2c_wr_max - 1)) { + len = remaining; + if (len > (priv->cfg->i2c_wr_max - 1)) + len = (priv->cfg->i2c_wr_max - 1); + + ret = m88ds3103_wr_regs(priv, 0xb0, + &fw->data[fw->size - remaining], len); + if (ret) { + dev_err(&priv->i2c->dev, + "%s: firmware download failed=%d\n", + KBUILD_MODNAME, ret); + goto err; + } + } + + ret = m88ds3103_wr_reg(priv, 0xb2, 0x00); + if (ret) + goto err; + + release_firmware(fw); + fw = NULL; + + ret = m88ds3103_rd_reg(priv, 0xb9, &u8tmp); + if (ret) + goto err; + + if (!u8tmp) { + dev_info(&priv->i2c->dev, "%s: firmware did not run\n", + KBUILD_MODNAME); + ret = -EFAULT; + goto err; + } + + dev_info(&priv->i2c->dev, "%s: found a '%s' in warm state\n", + KBUILD_MODNAME, m88ds3103_ops.info.name); + dev_info(&priv->i2c->dev, "%s: firmware version %X.%X\n", + KBUILD_MODNAME, (u8tmp >> 4) & 0xf, (u8tmp >> 0 & 0xf)); + +skip_fw_download: + /* warm state */ + priv->warm = true; + + return 0; +err: + if (fw) + release_firmware(fw); + + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_sleep(struct dvb_frontend *fe) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + int ret; + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); + + priv->delivery_system = SYS_UNDEFINED; + + /* TS Hi-Z */ + ret = m88ds3103_wr_reg_mask(priv, 0x27, 0x00, 0x01); + if (ret) + goto err; + + /* sleep */ + ret = m88ds3103_wr_reg_mask(priv, 0x08, 0x00, 0x01); + if (ret) + goto err; + + ret = m88ds3103_wr_reg_mask(priv, 0x04, 0x01, 0x01); + if (ret) + goto err; + + ret = m88ds3103_wr_reg_mask(priv, 0x23, 0x10, 0x10); + if (ret) + goto err; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_get_frontend(struct dvb_frontend *fe) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + u8 buf[3]; + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); + + if (!priv->warm || !(priv->fe_status & FE_HAS_LOCK)) { + ret = -EAGAIN; + goto err; + } + + switch (c->delivery_system) { + case SYS_DVBS: + ret = m88ds3103_rd_reg(priv, 0xe0, &buf[0]); + if (ret) + goto err; + + ret = m88ds3103_rd_reg(priv, 0xe6, &buf[1]); + if (ret) + goto err; + + switch ((buf[0] >> 2) & 0x01) { + case 0: + c->inversion = INVERSION_OFF; + break; + case 1: + c->inversion = INVERSION_ON; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid inversion\n", + __func__); + } + + switch ((buf[1] >> 5) & 0x07) { + case 0: + c->fec_inner = FEC_7_8; + break; + case 1: + c->fec_inner = FEC_5_6; + break; + case 2: + c->fec_inner = FEC_3_4; + break; + case 3: + c->fec_inner = FEC_2_3; + break; + case 4: + c->fec_inner = FEC_1_2; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid fec_inner\n", + __func__); + } + + c->modulation = QPSK; + + break; + case SYS_DVBS2: + ret = m88ds3103_rd_reg(priv, 0x7e, &buf[0]); + if (ret) + goto err; + + ret = m88ds3103_rd_reg(priv, 0x89, &buf[1]); + if (ret) + goto err; + + ret = m88ds3103_rd_reg(priv, 0xf2, &buf[2]); + if (ret) + goto err; + + switch ((buf[0] >> 0) & 0x0f) { + case 2: + c->fec_inner = FEC_2_5; + break; + case 3: + c->fec_inner = FEC_1_2; + break; + case 4: + c->fec_inner = FEC_3_5; + break; + case 5: + c->fec_inner = FEC_2_3; + break; + case 6: + c->fec_inner = FEC_3_4; + break; + case 7: + c->fec_inner = FEC_4_5; + break; + case 8: + c->fec_inner = FEC_5_6; + break; + case 9: + c->fec_inner = FEC_8_9; + break; + case 10: + c->fec_inner = FEC_9_10; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid fec_inner\n", + __func__); + } + + switch ((buf[0] >> 5) & 0x01) { + case 0: + c->pilot = PILOT_OFF; + break; + case 1: + c->pilot = PILOT_ON; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid pilot\n", + __func__); + } + + switch ((buf[0] >> 6) & 0x07) { + case 0: + c->modulation = QPSK; + break; + case 1: + c->modulation = PSK_8; + break; + case 2: + c->modulation = APSK_16; + break; + case 3: + c->modulation = APSK_32; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid modulation\n", + __func__); + } + + switch ((buf[1] >> 7) & 0x01) { + case 0: + c->inversion = INVERSION_OFF; + break; + case 1: + c->inversion = INVERSION_ON; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid inversion\n", + __func__); + } + + switch ((buf[2] >> 0) & 0x03) { + case 0: + c->rolloff = ROLLOFF_35; + break; + case 1: + c->rolloff = ROLLOFF_25; + break; + case 2: + c->rolloff = ROLLOFF_20; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid rolloff\n", + __func__); + } + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n", + __func__); + ret = -EINVAL; + goto err; + } + + ret = m88ds3103_rd_regs(priv, 0x6d, buf, 2); + if (ret) + goto err; + + c->symbol_rate = 1ull * ((buf[1] << 8) | (buf[0] << 0)) * + M88DS3103_MCLK_KHZ * 1000 / 0x10000; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret, i, tmp; + u8 buf[3]; + u16 noise, signal; + u32 noise_tot, signal_tot; + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); + /* reports SNR in resolution of 0.1 dB */ + + /* more iterations for more accurate estimation */ + #define M88DS3103_SNR_ITERATIONS 3 + + switch (c->delivery_system) { + case SYS_DVBS: + tmp = 0; + + for (i = 0; i < M88DS3103_SNR_ITERATIONS; i++) { + ret = m88ds3103_rd_reg(priv, 0xff, &buf[0]); + if (ret) + goto err; + + tmp += buf[0]; + } + + /* use of one register limits max value to 15 dB */ + /* SNR(X) dB = 10 * ln(X) / ln(10) dB */ + tmp = DIV_ROUND_CLOSEST(tmp, 8 * M88DS3103_SNR_ITERATIONS); + if (tmp) + *snr = 100ul * intlog2(tmp) / intlog2(10); + else + *snr = 0; + break; + case SYS_DVBS2: + noise_tot = 0; + signal_tot = 0; + + for (i = 0; i < M88DS3103_SNR_ITERATIONS; i++) { + ret = m88ds3103_rd_regs(priv, 0x8c, buf, 3); + if (ret) + goto err; + + noise = buf[1] << 6; /* [13:6] */ + noise |= buf[0] & 0x3f; /* [5:0] */ + noise >>= 2; + signal = buf[2] * buf[2]; + signal >>= 1; + + noise_tot += noise; + signal_tot += signal; + } + + noise = noise_tot / M88DS3103_SNR_ITERATIONS; + signal = signal_tot / M88DS3103_SNR_ITERATIONS; + + /* SNR(X) dB = 10 * log10(X) dB */ + if (signal > noise) { + tmp = signal / noise; + *snr = 100ul * intlog10(tmp) / (1 << 24); + } else { + *snr = 0; + } + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n", + __func__); + ret = -EINVAL; + goto err; + } + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + + +static int m88ds3103_set_tone(struct dvb_frontend *fe, + fe_sec_tone_mode_t fe_sec_tone_mode) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + int ret; + u8 u8tmp, tone, reg_a1_mask; + dev_dbg(&priv->i2c->dev, "%s: fe_sec_tone_mode=%d\n", __func__, + fe_sec_tone_mode); + + if (!priv->warm) { + ret = -EAGAIN; + goto err; + } + + switch (fe_sec_tone_mode) { + case SEC_TONE_ON: + tone = 0; + reg_a1_mask = 0x87; + break; + case SEC_TONE_OFF: + tone = 1; + reg_a1_mask = 0x00; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid fe_sec_tone_mode\n", + __func__); + ret = -EINVAL; + goto err; + } + + u8tmp = tone << 7 | priv->cfg->envelope_mode << 5; + ret = m88ds3103_wr_reg_mask(priv, 0xa2, u8tmp, 0xe0); + if (ret) + goto err; + + u8tmp = 1 << 2; + ret = m88ds3103_wr_reg_mask(priv, 0xa1, u8tmp, reg_a1_mask); + if (ret) + goto err; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_diseqc_send_master_cmd(struct dvb_frontend *fe, + struct dvb_diseqc_master_cmd *diseqc_cmd) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + int ret, i; + u8 u8tmp; + dev_dbg(&priv->i2c->dev, "%s: msg=%*ph\n", __func__, + diseqc_cmd->msg_len, diseqc_cmd->msg); + + if (!priv->warm) { + ret = -EAGAIN; + goto err; + } + + if (diseqc_cmd->msg_len < 3 || diseqc_cmd->msg_len > 6) { + ret = -EINVAL; + goto err; + } + + u8tmp = priv->cfg->envelope_mode << 5; + ret = m88ds3103_wr_reg_mask(priv, 0xa2, u8tmp, 0xe0); + if (ret) + goto err; + + ret = m88ds3103_wr_regs(priv, 0xa3, diseqc_cmd->msg, + diseqc_cmd->msg_len); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0xa1, + (diseqc_cmd->msg_len - 1) << 3 | 0x07); + if (ret) + goto err; + + /* DiSEqC message typical period is 54 ms */ + usleep_range(40000, 60000); + + /* wait DiSEqC TX ready */ + for (i = 20, u8tmp = 1; i && u8tmp; i--) { + usleep_range(5000, 10000); + + ret = m88ds3103_rd_reg_mask(priv, 0xa1, &u8tmp, 0x40); + if (ret) + goto err; + } + + dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i); + + if (i == 0) { + dev_dbg(&priv->i2c->dev, "%s: diseqc tx timeout\n", __func__); + + ret = m88ds3103_wr_reg_mask(priv, 0xa1, 0x40, 0xc0); + if (ret) + goto err; + } + + ret = m88ds3103_wr_reg_mask(priv, 0xa2, 0x80, 0xc0); + if (ret) + goto err; + + if (i == 0) { + ret = -ETIMEDOUT; + goto err; + } + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_diseqc_send_burst(struct dvb_frontend *fe, + fe_sec_mini_cmd_t fe_sec_mini_cmd) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + int ret, i; + u8 u8tmp, burst; + dev_dbg(&priv->i2c->dev, "%s: fe_sec_mini_cmd=%d\n", __func__, + fe_sec_mini_cmd); + + if (!priv->warm) { + ret = -EAGAIN; + goto err; + } + + u8tmp = priv->cfg->envelope_mode << 5; + ret = m88ds3103_wr_reg_mask(priv, 0xa2, u8tmp, 0xe0); + if (ret) + goto err; + + switch (fe_sec_mini_cmd) { + case SEC_MINI_A: + burst = 0x02; + break; + case SEC_MINI_B: + burst = 0x01; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid fe_sec_mini_cmd\n", + __func__); + ret = -EINVAL; + goto err; + } + + ret = m88ds3103_wr_reg(priv, 0xa1, burst); + if (ret) + goto err; + + /* DiSEqC ToneBurst period is 12.5 ms */ + usleep_range(11000, 20000); + + /* wait DiSEqC TX ready */ + for (i = 5, u8tmp = 1; i && u8tmp; i--) { + usleep_range(800, 2000); + + ret = m88ds3103_rd_reg_mask(priv, 0xa1, &u8tmp, 0x40); + if (ret) + goto err; + } + + dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i); + + ret = m88ds3103_wr_reg_mask(priv, 0xa2, 0x80, 0xc0); + if (ret) + goto err; + + if (i == 0) { + dev_dbg(&priv->i2c->dev, "%s: diseqc tx timeout\n", __func__); + ret = -ETIMEDOUT; + goto err; + } + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ds3103_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *s) +{ + s->min_delay_ms = 3000; + + return 0; +} + +static void m88ds3103_release(struct dvb_frontend *fe) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + i2c_del_mux_adapter(priv->i2c_adapter); + kfree(priv); +} + +static int m88ds3103_select(struct i2c_adapter *adap, void *mux_priv, u32 chan) +{ + struct m88ds3103_priv *priv = mux_priv; + int ret; + struct i2c_msg gate_open_msg[1] = { + { + .addr = priv->cfg->i2c_addr, + .flags = 0, + .len = 2, + .buf = "\x03\x11", + } + }; + + mutex_lock(&priv->i2c_mutex); + + /* open tuner I2C repeater for 1 xfer, closes automatically */ + ret = __i2c_transfer(priv->i2c, gate_open_msg, 1); + if (ret != 1) { + dev_warn(&priv->i2c->dev, "%s: i2c wr failed=%d\n", + KBUILD_MODNAME, ret); + if (ret >= 0) + ret = -EREMOTEIO; + + return ret; + } + + return 0; +} + +static int m88ds3103_deselect(struct i2c_adapter *adap, void *mux_priv, + u32 chan) +{ + struct m88ds3103_priv *priv = mux_priv; + + mutex_unlock(&priv->i2c_mutex); + + return 0; +} + +struct dvb_frontend *m88ds3103_attach(const struct m88ds3103_config *cfg, + struct i2c_adapter *i2c, struct i2c_adapter **tuner_i2c_adapter) +{ + int ret; + struct m88ds3103_priv *priv; + u8 chip_id, u8tmp; + + /* allocate memory for the internal priv */ + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + ret = -ENOMEM; + dev_err(&i2c->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME); + goto err; + } + + priv->cfg = cfg; + priv->i2c = i2c; + mutex_init(&priv->i2c_mutex); + + ret = m88ds3103_rd_reg(priv, 0x01, &chip_id); + if (ret) + goto err; + + dev_dbg(&priv->i2c->dev, "%s: chip_id=%02x\n", __func__, chip_id); + + switch (chip_id) { + case 0xd0: + break; + default: + goto err; + } + + switch (priv->cfg->clock_out) { + case M88DS3103_CLOCK_OUT_DISABLED: + u8tmp = 0x80; + break; + case M88DS3103_CLOCK_OUT_ENABLED: + u8tmp = 0x00; + break; + case M88DS3103_CLOCK_OUT_ENABLED_DIV2: + u8tmp = 0x10; + break; + default: + goto err; + } + + ret = m88ds3103_wr_reg(priv, 0x29, u8tmp); + if (ret) + goto err; + + /* sleep */ + ret = m88ds3103_wr_reg_mask(priv, 0x08, 0x00, 0x01); + if (ret) + goto err; + + ret = m88ds3103_wr_reg_mask(priv, 0x04, 0x01, 0x01); + if (ret) + goto err; + + ret = m88ds3103_wr_reg_mask(priv, 0x23, 0x10, 0x10); + if (ret) + goto err; + + /* create mux i2c adapter for tuner */ + priv->i2c_adapter = i2c_add_mux_adapter(i2c, &i2c->dev, priv, 0, 0, 0, + m88ds3103_select, m88ds3103_deselect); + if (priv->i2c_adapter == NULL) + goto err; + + *tuner_i2c_adapter = priv->i2c_adapter; + + /* create dvb_frontend */ + memcpy(&priv->fe.ops, &m88ds3103_ops, sizeof(struct dvb_frontend_ops)); + priv->fe.demodulator_priv = priv; + + return &priv->fe; +err: + dev_dbg(&i2c->dev, "%s: failed=%d\n", __func__, ret); + kfree(priv); + return NULL; +} +EXPORT_SYMBOL(m88ds3103_attach); + +static struct dvb_frontend_ops m88ds3103_ops = { + .delsys = { SYS_DVBS, SYS_DVBS2 }, + .info = { + .name = "Montage M88DS3103", + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_tolerance = 5000, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + .caps = FE_CAN_INVERSION_AUTO | + FE_CAN_FEC_1_2 | + FE_CAN_FEC_2_3 | + FE_CAN_FEC_3_4 | + FE_CAN_FEC_4_5 | + FE_CAN_FEC_5_6 | + FE_CAN_FEC_6_7 | + FE_CAN_FEC_7_8 | + FE_CAN_FEC_8_9 | + FE_CAN_FEC_AUTO | + FE_CAN_QPSK | + FE_CAN_RECOVER | + FE_CAN_2G_MODULATION + }, + + .release = m88ds3103_release, + + .get_tune_settings = m88ds3103_get_tune_settings, + + .init = m88ds3103_init, + .sleep = m88ds3103_sleep, + + .set_frontend = m88ds3103_set_frontend, + .get_frontend = m88ds3103_get_frontend, + + .read_status = m88ds3103_read_status, + .read_snr = m88ds3103_read_snr, + + .diseqc_send_master_cmd = m88ds3103_diseqc_send_master_cmd, + .diseqc_send_burst = m88ds3103_diseqc_send_burst, + + .set_tone = m88ds3103_set_tone, +}; + +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_DESCRIPTION("Montage M88DS3103 DVB-S/S2 demodulator driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(M88DS3103_FIRMWARE); diff --git a/drivers/media/dvb-frontends/m88ds3103.h b/drivers/media/dvb-frontends/m88ds3103.h new file mode 100644 index 000000000000..bbb7e3aa5675 --- /dev/null +++ b/drivers/media/dvb-frontends/m88ds3103.h @@ -0,0 +1,114 @@ +/* + * Montage M88DS3103 demodulator 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. + */ + +#ifndef M88DS3103_H +#define M88DS3103_H + +#include <linux/dvb/frontend.h> + +struct m88ds3103_config { + /* + * I2C address + * Default: none, must set + * 0x68, ... + */ + u8 i2c_addr; + + /* + * clock + * Default: none, must set + * 27000000 + */ + u32 clock; + + /* + * max bytes I2C provider is asked to write at once + * Default: none, must set + * 33, 65, ... + */ + u16 i2c_wr_max; + + /* + * TS output mode + * Default: M88DS3103_TS_SERIAL + */ +#define M88DS3103_TS_SERIAL 0 /* TS output pin D0, normal */ +#define M88DS3103_TS_SERIAL_D7 1 /* TS output pin D7 */ +#define M88DS3103_TS_PARALLEL 2 /* 24 MHz, normal */ +#define M88DS3103_TS_PARALLEL_12 3 /* 12 MHz */ +#define M88DS3103_TS_PARALLEL_16 4 /* 16 MHz */ +#define M88DS3103_TS_PARALLEL_19_2 5 /* 19.2 MHz */ +#define M88DS3103_TS_CI 6 /* 6 MHz */ + u8 ts_mode; + + /* + * spectrum inversion + * Default: 0 + */ + u8 spec_inv:1; + + /* + * AGC polarity + * Default: 0 + */ + u8 agc_inv:1; + + /* + * clock output + * Default: M88DS3103_CLOCK_OUT_DISABLED + */ +#define M88DS3103_CLOCK_OUT_DISABLED 0 +#define M88DS3103_CLOCK_OUT_ENABLED 1 +#define M88DS3103_CLOCK_OUT_ENABLED_DIV2 2 + u8 clock_out; + + /* + * DiSEqC envelope mode + * Default: 0 + */ + u8 envelope_mode:1; + + /* + * AGC configuration + * Default: none, must set + */ + u8 agc; +}; + +/* + * Driver implements own I2C-adapter for tuner I2C access. That's since chip + * has I2C-gate control which closes gate automatically after I2C transfer. + * Using own I2C adapter we can workaround that. + */ + +#if defined(CONFIG_DVB_M88DS3103) || \ + (defined(CONFIG_DVB_M88DS3103_MODULE) && defined(MODULE)) +extern struct dvb_frontend *m88ds3103_attach( + const struct m88ds3103_config *config, + struct i2c_adapter *i2c, + struct i2c_adapter **tuner_i2c); +#else +static inline struct dvb_frontend *m88ds3103_attach( + const struct m88ds3103_config *config, + struct i2c_adapter *i2c, + struct i2c_adapter **tuner_i2c) +{ + pr_warn("%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif + +#endif diff --git a/drivers/media/dvb-frontends/m88ds3103_priv.h b/drivers/media/dvb-frontends/m88ds3103_priv.h new file mode 100644 index 000000000000..84c3c06df622 --- /dev/null +++ b/drivers/media/dvb-frontends/m88ds3103_priv.h @@ -0,0 +1,215 @@ +/* + * Montage M88DS3103 demodulator 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. + */ + +#ifndef M88DS3103_PRIV_H +#define M88DS3103_PRIV_H + +#include "dvb_frontend.h" +#include "m88ds3103.h" +#include "dvb_math.h" +#include <linux/firmware.h> +#include <linux/i2c-mux.h> + +#define M88DS3103_FIRMWARE "dvb-demod-m88ds3103.fw" +#define M88DS3103_MCLK_KHZ 96000 + +struct m88ds3103_priv { + struct i2c_adapter *i2c; + /* mutex needed due to own tuner I2C adapter */ + struct mutex i2c_mutex; + const struct m88ds3103_config *cfg; + struct dvb_frontend fe; + fe_delivery_system_t delivery_system; + fe_status_t fe_status; + bool warm; /* FW running */ + struct i2c_adapter *i2c_adapter; +}; + +struct m88ds3103_reg_val { + u8 reg; + u8 val; +}; + +static const struct m88ds3103_reg_val m88ds3103_dvbs_init_reg_vals[] = { + {0x23, 0x07}, + {0x08, 0x03}, + {0x0c, 0x02}, + {0x21, 0x54}, + {0x25, 0x8a}, + {0x27, 0x31}, + {0x30, 0x08}, + {0x31, 0x40}, + {0x32, 0x32}, + {0x35, 0xff}, + {0x3a, 0x00}, + {0x37, 0x10}, + {0x38, 0x10}, + {0x39, 0x02}, + {0x42, 0x60}, + {0x4a, 0x80}, + {0x4b, 0x04}, + {0x4d, 0x91}, + {0x5d, 0xc8}, + {0x50, 0x36}, + {0x51, 0x36}, + {0x52, 0x36}, + {0x53, 0x36}, + {0x56, 0x01}, + {0x63, 0x0f}, + {0x64, 0x30}, + {0x65, 0x40}, + {0x68, 0x26}, + {0x69, 0x4c}, + {0x70, 0x20}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x40}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x60}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x80}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0xa0}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x1f}, + {0x76, 0x38}, + {0x77, 0xa6}, + {0x78, 0x0c}, + {0x79, 0x80}, + {0x7f, 0x14}, + {0x7c, 0x00}, + {0xae, 0x82}, + {0x80, 0x64}, + {0x81, 0x66}, + {0x82, 0x44}, + {0x85, 0x04}, + {0xcd, 0xf4}, + {0x90, 0x33}, + {0xa0, 0x44}, + {0xc0, 0x08}, + {0xc3, 0x10}, + {0xc4, 0x08}, + {0xc5, 0xf0}, + {0xc6, 0xff}, + {0xc7, 0x00}, + {0xc8, 0x1a}, + {0xc9, 0x80}, + {0xe0, 0xf8}, + {0xe6, 0x8b}, + {0xd0, 0x40}, + {0xf8, 0x20}, + {0xfa, 0x0f}, + {0x00, 0x00}, + {0xbd, 0x01}, + {0xb8, 0x00}, +}; + +static const struct m88ds3103_reg_val m88ds3103_dvbs2_init_reg_vals[] = { + {0x23, 0x07}, + {0x08, 0x07}, + {0x0c, 0x02}, + {0x21, 0x54}, + {0x25, 0x8a}, + {0x27, 0x31}, + {0x30, 0x08}, + {0x32, 0x32}, + {0x35, 0xff}, + {0x3a, 0x00}, + {0x37, 0x10}, + {0x38, 0x10}, + {0x39, 0x02}, + {0x42, 0x60}, + {0x4a, 0x80}, + {0x4b, 0x04}, + {0x4d, 0x91}, + {0x5d, 0xc8}, + {0x50, 0x36}, + {0x51, 0x36}, + {0x52, 0x36}, + {0x53, 0x36}, + {0x56, 0x01}, + {0x63, 0x0f}, + {0x64, 0x10}, + {0x65, 0x20}, + {0x68, 0x46}, + {0x69, 0xcd}, + {0x70, 0x20}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x40}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x60}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x80}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0xa0}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x1f}, + {0x76, 0x38}, + {0x77, 0xa6}, + {0x78, 0x0c}, + {0x79, 0x80}, + {0x7f, 0x14}, + {0x85, 0x08}, + {0xcd, 0xf4}, + {0x90, 0x33}, + {0x86, 0x00}, + {0x87, 0x0f}, + {0x89, 0x00}, + {0x8b, 0x44}, + {0x8c, 0x66}, + {0x9d, 0xc1}, + {0x8a, 0x10}, + {0xad, 0x40}, + {0xa0, 0x44}, + {0xc0, 0x08}, + {0xc1, 0x10}, + {0xc2, 0x08}, + {0xc3, 0x10}, + {0xc4, 0x08}, + {0xc5, 0xf0}, + {0xc6, 0xff}, + {0xc7, 0x00}, + {0xc8, 0x1a}, + {0xc9, 0x80}, + {0xca, 0x23}, + {0xcb, 0x24}, + {0xcc, 0xf4}, + {0xce, 0x74}, + {0x00, 0x00}, + {0xbd, 0x01}, + {0xb8, 0x00}, +}; + +#endif diff --git a/drivers/media/dvb-frontends/m88rs2000.c b/drivers/media/dvb-frontends/m88rs2000.c index 4da5272075cb..b2351466b0da 100644 --- a/drivers/media/dvb-frontends/m88rs2000.c +++ b/drivers/media/dvb-frontends/m88rs2000.c @@ -110,28 +110,94 @@ static u8 m88rs2000_readreg(struct m88rs2000_state *state, u8 reg) return b1[0]; } +static u32 m88rs2000_get_mclk(struct dvb_frontend *fe) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + u32 mclk; + u8 reg; + /* Must not be 0x00 or 0xff */ + reg = m88rs2000_readreg(state, 0x86); + if (!reg || reg == 0xff) + return 0; + + reg /= 2; + reg += 1; + + mclk = (u32)(reg * RS2000_FE_CRYSTAL_KHZ + 28 / 2) / 28; + + return mclk; +} + +static int m88rs2000_set_carrieroffset(struct dvb_frontend *fe, s16 offset) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + u32 mclk; + s32 tmp; + u8 reg; + int ret; + + mclk = m88rs2000_get_mclk(fe); + if (!mclk) + return -EINVAL; + + tmp = (offset * 4096 + (s32)mclk / 2) / (s32)mclk; + if (tmp < 0) + tmp += 4096; + + /* Carrier Offset */ + ret = m88rs2000_writereg(state, 0x9c, (u8)(tmp >> 4)); + + reg = m88rs2000_readreg(state, 0x9d); + reg &= 0xf; + reg |= (u8)(tmp & 0xf) << 4; + + ret |= m88rs2000_writereg(state, 0x9d, reg); + + return ret; +} + static int m88rs2000_set_symbolrate(struct dvb_frontend *fe, u32 srate) { struct m88rs2000_state *state = fe->demodulator_priv; int ret; - u32 temp; + u64 temp; + u32 mclk; u8 b[3]; if ((srate < 1000000) || (srate > 45000000)) return -EINVAL; + mclk = m88rs2000_get_mclk(fe); + if (!mclk) + return -EINVAL; + temp = srate / 1000; - temp *= 11831; - temp /= 68; - temp -= 3; + temp *= 1 << 24; + + do_div(temp, mclk); b[0] = (u8) (temp >> 16) & 0xff; b[1] = (u8) (temp >> 8) & 0xff; b[2] = (u8) temp & 0xff; + ret = m88rs2000_writereg(state, 0x93, b[2]); ret |= m88rs2000_writereg(state, 0x94, b[1]); ret |= m88rs2000_writereg(state, 0x95, b[0]); + if (srate > 10000000) + ret |= m88rs2000_writereg(state, 0xa0, 0x20); + else + ret |= m88rs2000_writereg(state, 0xa0, 0x60); + + ret |= m88rs2000_writereg(state, 0xa1, 0xe0); + + if (srate > 12000000) + ret |= m88rs2000_writereg(state, 0xa3, 0x20); + else if (srate > 2800000) + ret |= m88rs2000_writereg(state, 0xa3, 0x98); + else + ret |= m88rs2000_writereg(state, 0xa3, 0x90); + deb_info("m88rs2000: m88rs2000_set_symbolrate\n"); return ret; } @@ -261,8 +327,6 @@ struct inittab m88rs2000_shutdown[] = { struct inittab fe_reset[] = { {DEMOD_WRITE, 0x00, 0x01}, - {DEMOD_WRITE, 0xf1, 0xbf}, - {DEMOD_WRITE, 0x00, 0x01}, {DEMOD_WRITE, 0x20, 0x81}, {DEMOD_WRITE, 0x21, 0x80}, {DEMOD_WRITE, 0x10, 0x33}, @@ -305,9 +369,6 @@ struct inittab fe_trigger[] = { {DEMOD_WRITE, 0x9b, 0x64}, {DEMOD_WRITE, 0x9e, 0x00}, {DEMOD_WRITE, 0x9f, 0xf8}, - {DEMOD_WRITE, 0xa0, 0x20}, - {DEMOD_WRITE, 0xa1, 0xe0}, - {DEMOD_WRITE, 0xa3, 0x38}, {DEMOD_WRITE, 0x98, 0xff}, {DEMOD_WRITE, 0xc0, 0x0f}, {DEMOD_WRITE, 0x89, 0x01}, @@ -408,7 +469,7 @@ static int m88rs2000_read_status(struct dvb_frontend *fe, fe_status_t *status) *status = 0; - if ((reg & 0x7) == 0x7) { + if ((reg & 0xee) == 0xee) { *status = FE_HAS_CARRIER | FE_HAS_SIGNAL | FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; if (state->config->set_ts_params) @@ -480,33 +541,38 @@ static int m88rs2000_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) static int m88rs2000_set_fec(struct m88rs2000_state *state, fe_code_rate_t fec) { - u16 fec_set; + u8 fec_set, reg; + int ret; + switch (fec) { - /* This is not confirmed kept for reference */ -/* case FEC_1_2: - fec_set = 0x88; + case FEC_1_2: + fec_set = 0x8; break; case FEC_2_3: - fec_set = 0x68; + fec_set = 0x10; break; case FEC_3_4: - fec_set = 0x48; + fec_set = 0x20; break; case FEC_5_6: - fec_set = 0x28; + fec_set = 0x40; break; case FEC_7_8: - fec_set = 0x18; - break; */ + fec_set = 0x80; + break; case FEC_AUTO: default: - fec_set = 0x08; + fec_set = 0x0; } - m88rs2000_writereg(state, 0x76, fec_set); - return 0; -} + reg = m88rs2000_readreg(state, 0x70); + reg &= 0x7; + ret = m88rs2000_writereg(state, 0x70, reg | fec_set); + ret |= m88rs2000_writereg(state, 0x76, 0x8); + + return ret; +} static fe_code_rate_t m88rs2000_get_fec(struct m88rs2000_state *state) { @@ -515,18 +581,20 @@ static fe_code_rate_t m88rs2000_get_fec(struct m88rs2000_state *state) reg = m88rs2000_readreg(state, 0x76); m88rs2000_writereg(state, 0x9a, 0xb0); + reg &= 0xf0; + reg >>= 5; + switch (reg) { - case 0x88: + case 0x4: return FEC_1_2; - case 0x68: + case 0x3: return FEC_2_3; - case 0x48: + case 0x2: return FEC_3_4; - case 0x28: + case 0x1: return FEC_5_6; - case 0x18: + case 0x0: return FEC_7_8; - case 0x08: default: break; } @@ -540,9 +608,8 @@ static int m88rs2000_set_frontend(struct dvb_frontend *fe) struct dtv_frontend_properties *c = &fe->dtv_property_cache; fe_status_t status; int i, ret = 0; - s32 tmp; u32 tuner_freq; - u16 offset = 0; + s16 offset = 0; u8 reg; state->no_lock_count = 0; @@ -567,38 +634,31 @@ static int m88rs2000_set_frontend(struct dvb_frontend *fe) if (ret < 0) return -ENODEV; - offset = tuner_freq - c->frequency; - - /* calculate offset assuming 96000kHz*/ - tmp = offset; - tmp *= 65536; - - tmp = (2 * tmp + 96000) / (2 * 96000); - if (tmp < 0) - tmp += 65536; + offset = (s16)((s32)tuner_freq - c->frequency); - offset = tmp & 0xffff; + /* default mclk value 96.4285 * 2 * 1000 = 192857 */ + if (((c->frequency % 192857) >= (192857 - 3000)) || + (c->frequency % 192857) <= 3000) + ret = m88rs2000_writereg(state, 0x86, 0xc2); + else + ret = m88rs2000_writereg(state, 0x86, 0xc6); - ret = m88rs2000_writereg(state, 0x9a, 0x30); - /* Unknown usually 0xc6 sometimes 0xc1 */ - reg = m88rs2000_readreg(state, 0x86); - ret |= m88rs2000_writereg(state, 0x86, reg); - /* Offset lower nibble always 0 */ - ret |= m88rs2000_writereg(state, 0x9c, (offset >> 8)); - ret |= m88rs2000_writereg(state, 0x9d, offset & 0xf0); + ret |= m88rs2000_set_carrieroffset(fe, offset); + if (ret < 0) + return -ENODEV; + /* Reset demod by symbol rate */ + if (c->symbol_rate > 27500000) + ret = m88rs2000_writereg(state, 0xf1, 0xa4); + else + ret = m88rs2000_writereg(state, 0xf1, 0xbf); - /* Reset Demod */ - ret = m88rs2000_tab_set(state, fe_reset); + ret |= m88rs2000_tab_set(state, fe_reset); if (ret < 0) return -ENODEV; - /* Unknown */ - reg = m88rs2000_readreg(state, 0x70); - ret = m88rs2000_writereg(state, 0x70, reg); - /* Set FEC */ - ret |= m88rs2000_set_fec(state, c->fec_inner); + ret = m88rs2000_set_fec(state, c->fec_inner); ret |= m88rs2000_writereg(state, 0x85, 0x1); ret |= m88rs2000_writereg(state, 0x8a, 0xbf); ret |= m88rs2000_writereg(state, 0x8d, 0x1e); @@ -620,7 +680,7 @@ static int m88rs2000_set_frontend(struct dvb_frontend *fe) for (i = 0; i < 25; i++) { reg = m88rs2000_readreg(state, 0x8c); - if ((reg & 0x7) == 0x7) { + if ((reg & 0xee) == 0xee) { status = FE_HAS_LOCK; break; } diff --git a/drivers/media/dvb-frontends/m88rs2000.h b/drivers/media/dvb-frontends/m88rs2000.h index 14ce31e76ae6..0a50ea90736b 100644 --- a/drivers/media/dvb-frontends/m88rs2000.h +++ b/drivers/media/dvb-frontends/m88rs2000.h @@ -53,6 +53,8 @@ static inline struct dvb_frontend *m88rs2000_attach( } #endif /* CONFIG_DVB_M88RS2000 */ +#define RS2000_FE_CRYSTAL_KHZ 27000 + enum { DEMOD_WRITE = 0x1, WRITE_DELAY = 0x10, diff --git a/drivers/media/dvb-frontends/nxt200x.c b/drivers/media/dvb-frontends/nxt200x.c index fbca9856313a..4bf057544607 100644 --- a/drivers/media/dvb-frontends/nxt200x.c +++ b/drivers/media/dvb-frontends/nxt200x.c @@ -40,7 +40,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt /* Max transfer size done by I2C transfer functions */ -#define MAX_XFER_SIZE 64 +#define MAX_XFER_SIZE 256 #define NXT2002_DEFAULT_FIRMWARE "dvb-fe-nxt2002.fw" #define NXT2004_DEFAULT_FIRMWARE "dvb-fe-nxt2004.fw" diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 842654d33317..4aa9c5311cc5 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -555,14 +555,6 @@ config VIDEO_MT9V032 This is a Video4Linux2 sensor-level driver for the Micron MT9V032 752x480 CMOS sensor. -config VIDEO_TCM825X - tristate "TCM825x camera sensor support" - depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_INT_DEVICE - depends on MEDIA_CAMERA_SUPPORT - ---help--- - This is a driver for the Toshiba TCM825x VGA camera sensor. - It is used for example in Nokia N800. - config VIDEO_SR030PC30 tristate "Siliconfile SR030PC30 sensor support" depends on I2C && VIDEO_V4L2 @@ -594,6 +586,13 @@ config VIDEO_S5K4ECGX This is a V4L2 sensor-level driver for Samsung S5K4ECGX 5M camera sensor with an embedded SoC image signal processor. +config VIDEO_S5K5BAF + tristate "Samsung S5K5BAF sensor support" + depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + ---help--- + This is a V4L2 sensor-level driver for Samsung S5K5BAF 2M + camera sensor with an embedded SoC image signal processor. + source "drivers/media/i2c/smiapp/Kconfig" config VIDEO_S5C73M3 @@ -655,6 +654,18 @@ config VIDEO_UPD64083 To compile this driver as a module, choose M here: the module will be called upd64083. +comment "Audio/Video compression chips" + +config VIDEO_SAA6752HS + tristate "Philips SAA6752HS MPEG-2 Audio/Video Encoder" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the Philips SAA6752HS MPEG-2 video and MPEG-audio/AC-3 + audio encoder with multiplexer. + + To compile this driver as a module, choose M here: the + module will be called saa6752hs. + comment "Miscellaneous helper chips" config VIDEO_THS7303 diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index e03f1776f4f4..48888ae876fb 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_VIDEO_SAA717X) += saa717x.o obj-$(CONFIG_VIDEO_SAA7127) += saa7127.o obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o obj-$(CONFIG_VIDEO_SAA7191) += saa7191.o +obj-$(CONFIG_VIDEO_SAA6752HS) += saa6752hs.o obj-$(CONFIG_VIDEO_ADV7170) += adv7170.o obj-$(CONFIG_VIDEO_ADV7175) += adv7175.o obj-$(CONFIG_VIDEO_ADV7180) += adv7180.o @@ -57,7 +58,6 @@ obj-$(CONFIG_VIDEO_UPD64083) += upd64083.o obj-$(CONFIG_VIDEO_OV7640) += ov7640.o obj-$(CONFIG_VIDEO_OV7670) += ov7670.o obj-$(CONFIG_VIDEO_OV9650) += ov9650.o -obj-$(CONFIG_VIDEO_TCM825X) += tcm825x.o obj-$(CONFIG_VIDEO_MT9M032) += mt9m032.o obj-$(CONFIG_VIDEO_MT9P031) += mt9p031.o obj-$(CONFIG_VIDEO_MT9T001) += mt9t001.o @@ -67,6 +67,7 @@ obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o obj-$(CONFIG_VIDEO_NOON010PC30) += noon010pc30.o obj-$(CONFIG_VIDEO_S5K6AA) += s5k6aa.o obj-$(CONFIG_VIDEO_S5K4ECGX) += s5k4ecgx.o +obj-$(CONFIG_VIDEO_S5K5BAF) += s5k5baf.o obj-$(CONFIG_VIDEO_S5C73M3) += s5c73m3/ obj-$(CONFIG_VIDEO_ADP1653) += adp1653.o obj-$(CONFIG_VIDEO_AS3645A) += as3645a.o diff --git a/drivers/media/i2c/ad9389b.c b/drivers/media/i2c/ad9389b.c index b06a7e54ee0d..83225d6a0dd9 100644 --- a/drivers/media/i2c/ad9389b.c +++ b/drivers/media/i2c/ad9389b.c @@ -66,11 +66,6 @@ MODULE_LICENSE("GPL"); ********************************************************************** */ -struct i2c_reg_value { - u8 reg; - u8 value; -}; - struct ad9389b_state_edid { /* total number of blocks */ u32 blocks; @@ -143,14 +138,14 @@ static int ad9389b_wr(struct v4l2_subdev *sd, u8 reg, u8 val) if (ret == 0) return 0; } - v4l2_err(sd, "I2C Write Problem\n"); + v4l2_err(sd, "%s: failed reg 0x%x, val 0x%x\n", __func__, reg, val); 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 ad9389b_wr_and_or(struct v4l2_subdev *sd, u8 reg, - u8 clr_mask, u8 val_mask) + u8 clr_mask, u8 val_mask) { ad9389b_wr(sd, reg, (ad9389b_rd(sd, reg) & clr_mask) | val_mask); } @@ -321,12 +316,12 @@ static int ad9389b_s_ctrl(struct v4l2_ctrl *ctrl) struct ad9389b_state *state = get_ad9389b_state(sd); v4l2_dbg(1, debug, sd, - "%s: ctrl id: %d, ctrl->val %d\n", __func__, ctrl->id, ctrl->val); + "%s: ctrl id: %d, ctrl->val %d\n", __func__, ctrl->id, ctrl->val); if (state->hdmi_mode_ctrl == ctrl) { /* Set HDMI or DVI-D */ ad9389b_wr_and_or(sd, 0xaf, 0xfd, - ctrl->val == V4L2_DV_TX_MODE_HDMI ? 0x02 : 0x00); + ctrl->val == V4L2_DV_TX_MODE_HDMI ? 0x02 : 0x00); return 0; } if (state->rgb_quantization_range_ctrl == ctrl) @@ -387,61 +382,57 @@ static int ad9389b_log_status(struct v4l2_subdev *sd) v4l2_info(sd, "chip revision %d\n", state->chip_revision); 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", - (ad9389b_rd(sd, 0x42) & MASK_AD9389B_HPD_DETECT) ? - "detected" : "no", - (ad9389b_rd(sd, 0x42) & MASK_AD9389B_MSEN_DETECT) ? - "detected" : "no", - edid->segments ? "found" : "no", edid->blocks); - if (state->have_monitor) { - v4l2_info(sd, "%s output %s\n", - (ad9389b_rd(sd, 0xaf) & 0x02) ? - "HDMI" : "DVI-D", - (ad9389b_rd(sd, 0xa1) & 0x3c) ? - "disabled" : "enabled"); - } + (ad9389b_rd(sd, 0x42) & MASK_AD9389B_HPD_DETECT) ? + "detected" : "no", + (ad9389b_rd(sd, 0x42) & MASK_AD9389B_MSEN_DETECT) ? + "detected" : "no", + edid->segments ? "found" : "no", edid->blocks); + v4l2_info(sd, "%s output %s\n", + (ad9389b_rd(sd, 0xaf) & 0x02) ? + "HDMI" : "DVI-D", + (ad9389b_rd(sd, 0xa1) & 0x3c) ? + "disabled" : "enabled"); v4l2_info(sd, "ad9389b: %s\n", (ad9389b_rd(sd, 0xb8) & 0x40) ? - "encrypted" : "no encryption"); + "encrypted" : "no encryption"); v4l2_info(sd, "state: %s, error: %s, detect count: %u, msk/irq: %02x/%02x\n", - states[ad9389b_rd(sd, 0xc8) & 0xf], - errors[ad9389b_rd(sd, 0xc8) >> 4], - state->edid_detect_counter, - ad9389b_rd(sd, 0x94), ad9389b_rd(sd, 0x96)); + states[ad9389b_rd(sd, 0xc8) & 0xf], + errors[ad9389b_rd(sd, 0xc8) >> 4], + state->edid_detect_counter, + ad9389b_rd(sd, 0x94), ad9389b_rd(sd, 0x96)); manual_gear = ad9389b_rd(sd, 0x98) & 0x80; v4l2_info(sd, "ad9389b: RGB quantization: %s range\n", - ad9389b_rd(sd, 0x3b) & 0x01 ? "limited" : "full"); + ad9389b_rd(sd, 0x3b) & 0x01 ? "limited" : "full"); v4l2_info(sd, "ad9389b: %s gear %d\n", manual_gear ? "manual" : "automatic", manual_gear ? ((ad9389b_rd(sd, 0x98) & 0x70) >> 4) : - ((ad9389b_rd(sd, 0x9e) & 0x0e) >> 1)); - if (state->have_monitor) { - if (ad9389b_rd(sd, 0xaf) & 0x02) { - /* HDMI only */ - u8 manual_cts = ad9389b_rd(sd, 0x0a) & 0x80; - u32 N = (ad9389b_rd(sd, 0x01) & 0xf) << 16 | - ad9389b_rd(sd, 0x02) << 8 | - ad9389b_rd(sd, 0x03); - u8 vic_detect = ad9389b_rd(sd, 0x3e) >> 2; - u8 vic_sent = ad9389b_rd(sd, 0x3d) & 0x3f; - u32 CTS; - - if (manual_cts) - CTS = (ad9389b_rd(sd, 0x07) & 0xf) << 16 | - ad9389b_rd(sd, 0x08) << 8 | - ad9389b_rd(sd, 0x09); - else - CTS = (ad9389b_rd(sd, 0x04) & 0xf) << 16 | - ad9389b_rd(sd, 0x05) << 8 | - ad9389b_rd(sd, 0x06); - N = (ad9389b_rd(sd, 0x01) & 0xf) << 16 | - ad9389b_rd(sd, 0x02) << 8 | - ad9389b_rd(sd, 0x03); - - v4l2_info(sd, "ad9389b: CTS %s mode: N %d, CTS %d\n", - manual_cts ? "manual" : "automatic", N, CTS); - - v4l2_info(sd, "ad9389b: VIC: detected %d, sent %d\n", - vic_detect, vic_sent); - } + ((ad9389b_rd(sd, 0x9e) & 0x0e) >> 1)); + if (ad9389b_rd(sd, 0xaf) & 0x02) { + /* HDMI only */ + u8 manual_cts = ad9389b_rd(sd, 0x0a) & 0x80; + u32 N = (ad9389b_rd(sd, 0x01) & 0xf) << 16 | + ad9389b_rd(sd, 0x02) << 8 | + ad9389b_rd(sd, 0x03); + u8 vic_detect = ad9389b_rd(sd, 0x3e) >> 2; + u8 vic_sent = ad9389b_rd(sd, 0x3d) & 0x3f; + u32 CTS; + + if (manual_cts) + CTS = (ad9389b_rd(sd, 0x07) & 0xf) << 16 | + ad9389b_rd(sd, 0x08) << 8 | + ad9389b_rd(sd, 0x09); + else + CTS = (ad9389b_rd(sd, 0x04) & 0xf) << 16 | + ad9389b_rd(sd, 0x05) << 8 | + ad9389b_rd(sd, 0x06); + N = (ad9389b_rd(sd, 0x01) & 0xf) << 16 | + ad9389b_rd(sd, 0x02) << 8 | + ad9389b_rd(sd, 0x03); + + v4l2_info(sd, "ad9389b: CTS %s mode: N %d, CTS %d\n", + manual_cts ? "manual" : "automatic", N, CTS); + + v4l2_info(sd, "ad9389b: VIC: detected %d, sent %d\n", + vic_detect, vic_sent); } if (state->dv_timings.type == V4L2_DV_BT_656_1120) v4l2_print_dv_timings(sd->name, "timings: ", @@ -486,7 +477,7 @@ static int ad9389b_s_power(struct v4l2_subdev *sd, int on) } if (i > 1) v4l2_dbg(1, debug, sd, - "needed %d retries to powerup the ad9389b\n", i); + "needed %d retries to powerup the ad9389b\n", i); /* Select chip: AD9389B */ ad9389b_wr_and_or(sd, 0xba, 0xef, 0x10); @@ -556,14 +547,16 @@ static int ad9389b_isr(struct v4l2_subdev *sd, u32 status, bool *handled) irq_status = ad9389b_rd(sd, 0x96); /* clear detected interrupts */ ad9389b_wr(sd, 0x96, irq_status); + /* enable interrupts */ + ad9389b_set_isr(sd, true); + + v4l2_dbg(1, debug, sd, "%s: irq_status 0x%x\n", __func__, irq_status); - if (irq_status & (MASK_AD9389B_HPD_INT | MASK_AD9389B_MSEN_INT)) + if (irq_status & (MASK_AD9389B_HPD_INT)) ad9389b_check_monitor_present_status(sd); if (irq_status & MASK_AD9389B_EDID_RDY_INT) ad9389b_check_edid_status(sd); - /* enable interrupts */ - ad9389b_set_isr(sd, true); *handled = true; return 0; } @@ -599,7 +592,7 @@ static int ad9389b_get_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edi 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); + 128 * edid->blocks); return 0; } @@ -612,8 +605,6 @@ static const struct v4l2_subdev_pad_ops ad9389b_pad_ops = { /* Enable/disable ad9389b output */ static int ad9389b_s_stream(struct v4l2_subdev *sd, int enable) { - struct ad9389b_state *state = get_ad9389b_state(sd); - v4l2_dbg(1, debug, sd, "%s: %sable\n", __func__, (enable ? "en" : "dis")); ad9389b_wr_and_or(sd, 0xa1, ~0x3c, (enable ? 0 : 0x3c)); @@ -621,7 +612,6 @@ static int ad9389b_s_stream(struct v4l2_subdev *sd, int enable) ad9389b_check_monitor_present_status(sd); } else { ad9389b_s_power(sd, 0); - state->have_monitor = false; } return 0; } @@ -686,14 +676,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) + struct v4l2_enum_dv_timings *timings) { 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) + struct v4l2_dv_timings_cap *cap) { *cap = ad9389b_timings_cap; return 0; @@ -724,15 +714,15 @@ static int ad9389b_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 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; + return -EINVAL; } /* Set N (used with CTS to regenerate the audio clock) */ @@ -748,15 +738,15 @@ static int ad9389b_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 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; + return -EINVAL; } /* Set sampling frequency for I2S audio to 48 kHz */ @@ -800,7 +790,7 @@ static const struct v4l2_subdev_ops ad9389b_ops = { /* ----------------------------------------------------------------------- */ static void ad9389b_dbg_dump_edid(int lvl, int debug, struct v4l2_subdev *sd, - int segment, u8 *buf) + int segment, u8 *buf) { int i, j; @@ -826,8 +816,8 @@ static void ad9389b_dbg_dump_edid(int lvl, int debug, struct v4l2_subdev *sd, static void ad9389b_edid_handler(struct work_struct *work) { struct delayed_work *dwork = to_delayed_work(work); - struct ad9389b_state *state = container_of(dwork, - struct ad9389b_state, edid_handler); + struct ad9389b_state *state = + container_of(dwork, struct ad9389b_state, edid_handler); struct v4l2_subdev *sd = &state->sd; struct ad9389b_edid_detect ed; @@ -845,11 +835,10 @@ static void ad9389b_edid_handler(struct work_struct *work) if (state->edid.read_retries) { state->edid.read_retries--; 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); + &state->edid_handler, EDID_DELAY); return; } } @@ -915,49 +904,35 @@ static void ad9389b_notify_monitor_detect(struct v4l2_subdev *sd) v4l2_subdev_notify(sd, AD9389B_MONITOR_DETECT, (void *)&mdt); } -static void ad9389b_check_monitor_present_status(struct v4l2_subdev *sd) +static void ad9389b_update_monitor_present_status(struct v4l2_subdev *sd) { struct ad9389b_state *state = get_ad9389b_state(sd); /* read hotplug and rx-sense state */ u8 status = ad9389b_rd(sd, 0x42); v4l2_dbg(1, debug, sd, "%s: status: 0x%x%s%s\n", - __func__, - status, - status & MASK_AD9389B_HPD_DETECT ? ", hotplug" : "", - status & MASK_AD9389B_MSEN_DETECT ? ", rx-sense" : ""); + __func__, + status, + status & MASK_AD9389B_HPD_DETECT ? ", hotplug" : "", + status & MASK_AD9389B_MSEN_DETECT ? ", rx-sense" : ""); - if ((status & MASK_AD9389B_HPD_DETECT) && - ((status & MASK_AD9389B_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; - ad9389b_set_isr(sd, true); - if (!ad9389b_s_power(sd, true)) { - v4l2_dbg(1, debug, sd, - "%s: monitor detected, powerup failed\n", __func__); - return; - } - ad9389b_setup(sd); - ad9389b_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_AD9389B_HPD_DETECT) { + if (status & MASK_AD9389B_HPD_DETECT) { v4l2_dbg(1, debug, sd, "%s: hotplug detected\n", __func__); + state->have_monitor = true; + if (!ad9389b_s_power(sd, true)) { + v4l2_dbg(1, debug, sd, + "%s: monitor detected, powerup failed\n", __func__); + return; + } + ad9389b_setup(sd); + ad9389b_notify_monitor_detect(sd); state->edid.read_retries = EDID_MAX_RETRIES; queue_delayed_work(state->work_queue, - &state->edid_handler, EDID_DELAY); + &state->edid_handler, EDID_DELAY); } else if (!(status & MASK_AD9389B_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; - ad9389b_notify_monitor_detect(sd); - } + state->have_monitor = false; + ad9389b_notify_monitor_detect(sd); ad9389b_s_power(sd, false); memset(&state->edid, 0, sizeof(struct ad9389b_state_edid)); } @@ -966,6 +941,35 @@ static void ad9389b_check_monitor_present_status(struct v4l2_subdev *sd) v4l2_ctrl_s_ctrl(state->hotplug_ctrl, ad9389b_have_hotplug(sd) ? 0x1 : 0x0); v4l2_ctrl_s_ctrl(state->rx_sense_ctrl, ad9389b_have_rx_sense(sd) ? 0x1 : 0x0); v4l2_ctrl_s_ctrl(state->have_edid0_ctrl, state->edid.segments ? 0x1 : 0x0); + + /* update with setting from ctrls */ + ad9389b_s_ctrl(state->rgb_quantization_range_ctrl); + ad9389b_s_ctrl(state->hdmi_mode_ctrl); +} + +static void ad9389b_check_monitor_present_status(struct v4l2_subdev *sd) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + int retry = 0; + + ad9389b_update_monitor_present_status(sd); + + /* + * Rapid toggling of the hotplug may leave the chip powered off, + * even if we think it is on. In that case reset and power up again. + */ + while (state->power_on && (ad9389b_rd(sd, 0x41) & 0x40)) { + if (++retry > 5) { + v4l2_err(sd, "retried %d times, give up\n", retry); + return; + } + v4l2_dbg(1, debug, sd, "%s: reset and re-check status (%d)\n", __func__, retry); + ad9389b_notify_monitor_detect(sd); + cancel_delayed_work_sync(&state->edid_handler); + memset(&state->edid, 0, sizeof(struct ad9389b_state_edid)); + ad9389b_s_power(sd, false); + ad9389b_update_monitor_present_status(sd); + } } static bool edid_block_verify_crc(u8 *edid_block) @@ -978,7 +982,7 @@ static bool edid_block_verify_crc(u8 *edid_block) return sum == 0; } -static bool edid_segment_verify_crc(struct v4l2_subdev *sd, u32 segment) +static bool edid_verify_crc(struct v4l2_subdev *sd, u32 segment) { struct ad9389b_state *state = get_ad9389b_state(sd); u32 blocks = state->edid.blocks; @@ -992,6 +996,25 @@ static bool edid_segment_verify_crc(struct v4l2_subdev *sd, u32 segment) return false; } +static bool edid_verify_header(struct v4l2_subdev *sd, u32 segment) +{ + static const u8 hdmi_header[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 + }; + struct ad9389b_state *state = get_ad9389b_state(sd); + u8 *data = state->edid.data; + int i; + + if (segment) + return true; + + for (i = 0; i < ARRAY_SIZE(hdmi_header); i++) + if (data[i] != hdmi_header[i]) + return false; + + return true; +} + static bool ad9389b_check_edid_status(struct v4l2_subdev *sd) { struct ad9389b_state *state = get_ad9389b_state(sd); @@ -1000,7 +1023,7 @@ static bool ad9389b_check_edid_status(struct v4l2_subdev *sd) u8 edidRdy = ad9389b_rd(sd, 0xc5); v4l2_dbg(1, debug, sd, "%s: edid ready (retries: %d)\n", - __func__, EDID_MAX_RETRIES - state->edid.read_retries); + __func__, EDID_MAX_RETRIES - state->edid.read_retries); if (!(edidRdy & MASK_AD9389B_EDID_RDY)) return false; @@ -1013,16 +1036,16 @@ static bool ad9389b_check_edid_status(struct v4l2_subdev *sd) v4l2_dbg(1, debug, sd, "%s: got segment %d\n", __func__, segment); ad9389b_edid_rd(sd, 256, &state->edid.data[segment * 256]); ad9389b_dbg_dump_edid(2, debug, sd, segment, - &state->edid.data[segment * 256]); + &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); + __func__, state->edid.blocks); } - if (!edid_segment_verify_crc(sd, segment)) { + if (!edid_verify_crc(sd, segment) || + !edid_verify_header(sd, segment)) { /* edid crc error, force reread of edid segment */ - v4l2_err(sd, "%s: edid crc error\n", __func__); - state->have_monitor = false; + v4l2_err(sd, "%s: edid crc or header error\n", __func__); ad9389b_s_power(sd, false); ad9389b_s_power(sd, true); return false; @@ -1032,12 +1055,12 @@ static bool ad9389b_check_edid_status(struct v4l2_subdev *sd) 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); + __func__, state->edid.segments); ad9389b_wr(sd, 0xc9, 0xf); ad9389b_wr(sd, 0xc4, state->edid.segments); state->edid.read_retries = EDID_MAX_RETRIES; queue_delayed_work(state->work_queue, - &state->edid_handler, EDID_DELAY); + &state->edid_handler, EDID_DELAY); return false; } @@ -1081,7 +1104,7 @@ static int ad9389b_probe(struct i2c_client *client, const struct i2c_device_id * return -EIO; v4l_dbg(1, debug, client, "detecting ad9389b client on address 0x%x\n", - client->addr << 1); + client->addr << 1); state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); if (!state) @@ -1140,7 +1163,7 @@ static int ad9389b_probe(struct i2c_client *client, const struct i2c_device_id * goto err_entity; } v4l2_dbg(1, debug, sd, "reg 0x41 0x%x, chip version (reg 0x00) 0x%x\n", - ad9389b_rd(sd, 0x41), state->chip_revision); + ad9389b_rd(sd, 0x41), state->chip_revision); state->edid_i2c_client = i2c_new_dummy(client->adapter, (0x7e>>1)); if (state->edid_i2c_client == NULL) { @@ -1163,7 +1186,7 @@ static int ad9389b_probe(struct i2c_client *client, const struct i2c_device_id * ad9389b_set_isr(sd, true); v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name, - client->addr << 1, client->adapter->name); + client->addr << 1, client->adapter->name); return 0; err_unreg: diff --git a/drivers/media/i2c/adv7511.c b/drivers/media/i2c/adv7511.c index 7c8d971f1f61..ee618942cb8e 100644 --- a/drivers/media/i2c/adv7511.c +++ b/drivers/media/i2c/adv7511.c @@ -452,6 +452,29 @@ static int adv7511_log_status(struct v4l2_subdev *sd) 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 (adv7511_rd(sd, 0xaf) & 0x02) { + /* HDMI only */ + u8 manual_cts = adv7511_rd(sd, 0x0a) & 0x80; + u32 N = (adv7511_rd(sd, 0x01) & 0xf) << 16 | + adv7511_rd(sd, 0x02) << 8 | + adv7511_rd(sd, 0x03); + u8 vic_detect = adv7511_rd(sd, 0x3e) >> 2; + u8 vic_sent = adv7511_rd(sd, 0x3d) & 0x3f; + u32 CTS; + + if (manual_cts) + CTS = (adv7511_rd(sd, 0x07) & 0xf) << 16 | + adv7511_rd(sd, 0x08) << 8 | + adv7511_rd(sd, 0x09); + else + CTS = (adv7511_rd(sd, 0x04) & 0xf) << 16 | + adv7511_rd(sd, 0x05) << 8 | + adv7511_rd(sd, 0x06); + v4l2_info(sd, "CTS %s mode: N %d, CTS %d\n", + manual_cts ? "manual" : "automatic", N, CTS); + v4l2_info(sd, "VIC: detected %d, sent %d\n", + vic_detect, vic_sent); + } if (state->dv_timings.type == V4L2_DV_BT_656_1120) v4l2_print_dv_timings(sd->name, "timings: ", &state->dv_timings, false); @@ -942,26 +965,38 @@ static void adv7511_check_monitor_present_status(struct v4l2_subdev *sd) static bool edid_block_verify_crc(uint8_t *edid_block) { - int i; uint8_t sum = 0; + int i; for (i = 0; i < 128; i++) - sum += *(edid_block + i); - return (sum == 0); + sum += edid_block[i]; + return sum == 0; } -static bool edid_segment_verify_crc(struct v4l2_subdev *sd, u32 segment) +static bool edid_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]); + if (!edid_block_verify_crc(&data[segment * 256])) + return false; + if ((segment + 1) * 2 <= blocks) + return edid_block_verify_crc(&data[segment * 256 + 128]); + return true; +} + +static bool edid_verify_header(struct v4l2_subdev *sd, u32 segment) +{ + static const u8 hdmi_header[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 + }; + struct adv7511_state *state = get_adv7511_state(sd); + u8 *data = state->edid.data; + + if (segment != 0) return true; - } - return false; + return !memcmp(data, hdmi_header, sizeof(hdmi_header)); } static bool adv7511_check_edid_status(struct v4l2_subdev *sd) @@ -990,9 +1025,10 @@ static bool adv7511_check_edid_status(struct v4l2_subdev *sd) 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)) { + if (!edid_verify_crc(sd, segment) || + !edid_verify_header(sd, segment)) { /* edid crc error, force reread of edid segment */ - v4l2_dbg(1, debug, sd, "%s: edid crc error\n", __func__); + v4l2_err(sd, "%s: edid crc or header error\n", __func__); state->have_monitor = false; adv7511_s_power(sd, false); adv7511_s_power(sd, true); @@ -1038,6 +1074,12 @@ static void adv7511_init_setup(struct v4l2_subdev *sd) /* clear all interrupts */ adv7511_wr(sd, 0x96, 0xff); + /* + * Stop HPD from resetting a lot of registers. + * It might leave the chip in a partly un-initialized state, + * in particular with regards to hotplug bounces. + */ + adv7511_wr_and_or(sd, 0xd6, 0x3f, 0xc0); memset(edid, 0, sizeof(struct adv7511_state_edid)); state->have_monitor = false; adv7511_set_isr(sd, false); diff --git a/drivers/media/i2c/adv7604.c b/drivers/media/i2c/adv7604.c index a324106b9f11..71c8570bd9ea 100644 --- a/drivers/media/i2c/adv7604.c +++ b/drivers/media/i2c/adv7604.c @@ -53,8 +53,6 @@ MODULE_LICENSE("GPL"); /* ADV7604 system clock frequency */ #define ADV7604_fsc (28636360) -#define DIGITAL_INPUT (state->mode == ADV7604_MODE_HDMI) - /* ********************************************************************** * @@ -67,17 +65,19 @@ struct adv7604_state { struct v4l2_subdev sd; struct media_pad pad; struct v4l2_ctrl_handler hdl; - enum adv7604_mode mode; + enum adv7604_input_port selected_input; struct v4l2_dv_timings timings; - u8 edid[256]; - unsigned edid_blocks; + struct { + u8 edid[256]; + u32 present; + unsigned blocks; + } edid; + u16 spa_port_a[2]; struct v4l2_fract aspect_ratio; u32 rgb_quantization_range; struct workqueue_struct *work_queues; 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; @@ -160,6 +160,7 @@ static const struct v4l2_dv_timings adv7604_timings[] = { V4L2_DV_BT_DMT_1792X1344P60, V4L2_DV_BT_DMT_1856X1392P60, V4L2_DV_BT_DMT_1920X1200P60_RB, + V4L2_DV_BT_DMT_1366X768P60_RB, V4L2_DV_BT_DMT_1366X768P60, V4L2_DV_BT_DMT_1920X1080P60, { }, @@ -507,57 +508,31 @@ static inline int edid_read_block(struct v4l2_subdev *sd, unsigned len, u8 *val) return 0; } -static void adv7604_delayed_work_enable_hotplug(struct work_struct *work) -{ - struct delayed_work *dwork = to_delayed_work(work); - struct adv7604_state *state = container_of(dwork, struct adv7604_state, - delayed_work_enable_hotplug); - struct v4l2_subdev *sd = &state->sd; - - v4l2_dbg(2, debug, sd, "%s: enable hotplug\n", __func__); - - v4l2_subdev_notify(sd, ADV7604_HOTPLUG, (void *)1); -} - static inline int edid_write_block(struct v4l2_subdev *sd, unsigned len, const u8 *val) { - struct i2c_client *client = v4l2_get_subdevdata(sd); struct adv7604_state *state = to_state(sd); int err = 0; int i; v4l2_dbg(2, debug, sd, "%s: write EDID block (%d byte)\n", __func__, len); - v4l2_subdev_notify(sd, ADV7604_HOTPLUG, (void *)0); - - /* Disables I2C access to internal EDID ram from DDC port */ - rep_write_and_or(sd, 0x77, 0xf0, 0x0); - for (i = 0; !err && i < len; 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; + return err; +} - /* adv7604 calculates the checksums and enables I2C access to internal - EDID ram from DDC port. */ - rep_write_and_or(sd, 0x77, 0xf0, 0x1); +static void adv7604_delayed_work_enable_hotplug(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct adv7604_state *state = container_of(dwork, struct adv7604_state, + delayed_work_enable_hotplug); + struct v4l2_subdev *sd = &state->sd; - for (i = 0; i < 1000; i++) { - if (rep_read(sd, 0x7d) & 1) - break; - mdelay(1); - } - if (i == 1000) { - v4l_err(client, "error enabling edid\n"); - return -EIO; - } + v4l2_dbg(2, debug, sd, "%s: enable hotplug\n", __func__); - /* enable hotplug after 100 ms */ - queue_delayed_work(state->work_queues, - &state->delayed_work_enable_hotplug, HZ / 10); - return 0; + v4l2_subdev_notify(sd, ADV7604_HOTPLUG, (void *)&state->edid.present); } static inline int hdmi_read(struct v4l2_subdev *sd, u8 reg) @@ -574,6 +549,11 @@ static inline int hdmi_write(struct v4l2_subdev *sd, u8 reg, u8 val) return adv_smbus_write_byte_data(state->i2c_hdmi, reg, val); } +static inline int hdmi_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return hdmi_write(sd, reg, (hdmi_read(sd, reg) & mask) | val); +} + static inline int test_read(struct v4l2_subdev *sd, u8 reg) { struct adv7604_state *state = to_state(sd); @@ -623,6 +603,26 @@ static inline int vdp_write(struct v4l2_subdev *sd, u8 reg, u8 val) /* ----------------------------------------------------------------------- */ +static inline bool is_analog_input(struct v4l2_subdev *sd) +{ + struct adv7604_state *state = to_state(sd); + + return state->selected_input == ADV7604_INPUT_VGA_RGB || + state->selected_input == ADV7604_INPUT_VGA_COMP; +} + +static inline bool is_digital_input(struct v4l2_subdev *sd) +{ + struct adv7604_state *state = to_state(sd); + + return state->selected_input == ADV7604_INPUT_HDMI_PORT_A || + state->selected_input == ADV7604_INPUT_HDMI_PORT_B || + state->selected_input == ADV7604_INPUT_HDMI_PORT_C || + state->selected_input == ADV7604_INPUT_HDMI_PORT_D; +} + +/* ----------------------------------------------------------------------- */ + #ifdef CONFIG_VIDEO_ADV_DEBUG static void adv7604_inv_register(struct v4l2_subdev *sd) { @@ -696,45 +696,47 @@ static int adv7604_g_register(struct v4l2_subdev *sd, static int adv7604_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, reg->val & 0xff); + io_write(sd, reg->reg & 0xff, val); break; case 1: - avlink_write(sd, reg->reg & 0xff, reg->val & 0xff); + avlink_write(sd, reg->reg & 0xff, val); break; case 2: - cec_write(sd, reg->reg & 0xff, reg->val & 0xff); + cec_write(sd, reg->reg & 0xff, val); break; case 3: - infoframe_write(sd, reg->reg & 0xff, reg->val & 0xff); + infoframe_write(sd, reg->reg & 0xff, val); break; case 4: - esdp_write(sd, reg->reg & 0xff, reg->val & 0xff); + esdp_write(sd, reg->reg & 0xff, val); break; case 5: - dpp_write(sd, reg->reg & 0xff, reg->val & 0xff); + dpp_write(sd, reg->reg & 0xff, val); break; case 6: - afe_write(sd, reg->reg & 0xff, reg->val & 0xff); + afe_write(sd, reg->reg & 0xff, val); break; case 7: - rep_write(sd, reg->reg & 0xff, reg->val & 0xff); + rep_write(sd, reg->reg & 0xff, val); break; case 8: - edid_write(sd, reg->reg & 0xff, reg->val & 0xff); + edid_write(sd, reg->reg & 0xff, val); break; case 9: - hdmi_write(sd, reg->reg & 0xff, reg->val & 0xff); + hdmi_write(sd, reg->reg & 0xff, val); break; case 0xa: - test_write(sd, reg->reg & 0xff, reg->val & 0xff); + test_write(sd, reg->reg & 0xff, val); break; case 0xb: - cp_write(sd, reg->reg & 0xff, reg->val & 0xff); + cp_write(sd, reg->reg & 0xff, val); break; case 0xc: - vdp_write(sd, reg->reg & 0xff, reg->val & 0xff); + vdp_write(sd, reg->reg & 0xff, val); break; default: v4l2_info(sd, "Register %03llx not supported\n", reg->reg); @@ -748,10 +750,13 @@ static int adv7604_s_register(struct v4l2_subdev *sd, static int adv7604_s_detect_tx_5v_ctrl(struct v4l2_subdev *sd) { struct adv7604_state *state = to_state(sd); + u8 reg_io_6f = io_read(sd, 0x6f); - /* port A only */ return v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl, - ((io_read(sd, 0x6f) & 0x10) >> 4)); + ((reg_io_6f & 0x10) >> 4) | + ((reg_io_6f & 0x08) >> 2) | + (reg_io_6f & 0x04) | + ((reg_io_6f & 0x02) << 2)); } static int find_and_set_predefined_video_timings(struct v4l2_subdev *sd, @@ -759,12 +764,11 @@ static int find_and_set_predefined_video_timings(struct v4l2_subdev *sd, const struct adv7604_video_standards *predef_vid_timings, const struct v4l2_dv_timings *timings) { - struct adv7604_state *state = to_state(sd); int i; for (i = 0; predef_vid_timings[i].timings.bt.width; i++) { if (!v4l2_match_dv_timings(timings, &predef_vid_timings[i].timings, - DIGITAL_INPUT ? 250000 : 1000000)) + is_digital_input(sd) ? 250000 : 1000000)) continue; io_write(sd, 0x00, predef_vid_timings[i].vid_std); /* video std */ io_write(sd, 0x01, (predef_vid_timings[i].v_freq << 4) + @@ -799,27 +803,22 @@ static int configure_predefined_video_timings(struct v4l2_subdev *sd, cp_write(sd, 0xab, 0x00); cp_write(sd, 0xac, 0x00); - switch (state->mode) { - case ADV7604_MODE_COMP: - case ADV7604_MODE_GR: + if (is_analog_input(sd)) { err = find_and_set_predefined_video_timings(sd, 0x01, adv7604_prim_mode_comp, timings); if (err) err = find_and_set_predefined_video_timings(sd, 0x02, adv7604_prim_mode_gr, timings); - break; - case ADV7604_MODE_HDMI: + } else if (is_digital_input(sd)) { err = find_and_set_predefined_video_timings(sd, 0x05, adv7604_prim_mode_hdmi_comp, timings); if (err) err = find_and_set_predefined_video_timings(sd, 0x06, adv7604_prim_mode_hdmi_gr, timings); - break; - default: - v4l2_dbg(2, debug, sd, "%s: Unknown mode %d\n", - __func__, state->mode); + } else { + v4l2_dbg(2, debug, sd, "%s: Unknown port %d selected\n", + __func__, state->selected_input); err = -1; - break; } @@ -846,9 +845,7 @@ static void configure_custom_video_timings(struct v4l2_subdev *sd, v4l2_dbg(2, debug, sd, "%s\n", __func__); - switch (state->mode) { - case ADV7604_MODE_COMP: - case ADV7604_MODE_GR: + if (is_analog_input(sd)) { /* auto graphics */ io_write(sd, 0x00, 0x07); /* video std */ io_write(sd, 0x01, 0x02); /* prim mode */ @@ -858,33 +855,28 @@ static void configure_custom_video_timings(struct v4l2_subdev *sd, /* 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)) { + 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, 0xa2, (cp_start_sav >> 4) & 0xff); cp_write(sd, 0xa3, ((cp_start_sav & 0x0f) << 4) | - ((cp_start_eav >> 8) & 0x0f)); + ((cp_start_eav >> 8) & 0x0f)); cp_write(sd, 0xa4, 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_end_vbi >> 8) & 0xf)); cp_write(sd, 0xa7, cp_end_vbi & 0xff); - break; - case ADV7604_MODE_HDMI: + } else if (is_digital_input(sd)) { /* set default prim_mode/vid_std for HDMI according 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; + } else { + v4l2_dbg(2, debug, sd, "%s: Unknown port %d selected\n", + __func__, state->selected_input); } cp_write(sd, 0x8f, (ch1_fr_ll >> 8) & 0x7); @@ -893,43 +885,149 @@ static void configure_custom_video_timings(struct v4l2_subdev *sd, cp_write(sd, 0xac, (height & 0x0f) << 4); } +static void adv7604_set_offset(struct v4l2_subdev *sd, bool auto_offset, u16 offset_a, u16 offset_b, u16 offset_c) +{ + struct adv7604_state *state = to_state(sd); + u8 offset_buf[4]; + + if (auto_offset) { + offset_a = 0x3ff; + offset_b = 0x3ff; + offset_c = 0x3ff; + } + + v4l2_dbg(2, debug, sd, "%s: %s offset: a = 0x%x, b = 0x%x, c = 0x%x\n", + __func__, auto_offset ? "Auto" : "Manual", + offset_a, offset_b, offset_c); + + offset_buf[0] = (cp_read(sd, 0x77) & 0xc0) | ((offset_a & 0x3f0) >> 4); + offset_buf[1] = ((offset_a & 0x00f) << 4) | ((offset_b & 0x3c0) >> 6); + offset_buf[2] = ((offset_b & 0x03f) << 2) | ((offset_c & 0x300) >> 8); + offset_buf[3] = offset_c & 0x0ff; + + /* Registers must be written in this order with no i2c access in between */ + if (adv_smbus_write_i2c_block_data(state->i2c_cp, 0x77, 4, offset_buf)) + v4l2_err(sd, "%s: i2c error writing to CP reg 0x77, 0x78, 0x79, 0x7a\n", __func__); +} + +static void adv7604_set_gain(struct v4l2_subdev *sd, bool auto_gain, u16 gain_a, u16 gain_b, u16 gain_c) +{ + struct adv7604_state *state = to_state(sd); + u8 gain_buf[4]; + u8 gain_man = 1; + u8 agc_mode_man = 1; + + if (auto_gain) { + gain_man = 0; + agc_mode_man = 0; + gain_a = 0x100; + gain_b = 0x100; + gain_c = 0x100; + } + + v4l2_dbg(2, debug, sd, "%s: %s gain: a = 0x%x, b = 0x%x, c = 0x%x\n", + __func__, auto_gain ? "Auto" : "Manual", + gain_a, gain_b, gain_c); + + gain_buf[0] = ((gain_man << 7) | (agc_mode_man << 6) | ((gain_a & 0x3f0) >> 4)); + gain_buf[1] = (((gain_a & 0x00f) << 4) | ((gain_b & 0x3c0) >> 6)); + gain_buf[2] = (((gain_b & 0x03f) << 2) | ((gain_c & 0x300) >> 8)); + gain_buf[3] = ((gain_c & 0x0ff)); + + /* Registers must be written in this order with no i2c access in between */ + if (adv_smbus_write_i2c_block_data(state->i2c_cp, 0x73, 4, gain_buf)) + v4l2_err(sd, "%s: i2c error writing to CP reg 0x73, 0x74, 0x75, 0x76\n", __func__); +} + static void set_rgb_quantization_range(struct v4l2_subdev *sd) { struct adv7604_state *state = to_state(sd); + bool rgb_output = io_read(sd, 0x02) & 0x02; + bool hdmi_signal = hdmi_read(sd, 0x05) & 0x80; + + v4l2_dbg(2, debug, sd, "%s: RGB quantization range: %d, RGB out: %d, HDMI: %d\n", + __func__, state->rgb_quantization_range, + rgb_output, hdmi_signal); + + adv7604_set_gain(sd, true, 0x0, 0x0, 0x0); + adv7604_set_offset(sd, true, 0x0, 0x0, 0x0); switch (state->rgb_quantization_range) { case V4L2_DV_RGB_RANGE_AUTO: - /* automatic */ - if (DIGITAL_INPUT && !(hdmi_read(sd, 0x05) & 0x80)) { - /* receiving DVI-D signal */ + if (state->selected_input == ADV7604_INPUT_VGA_RGB) { + /* Receiving analog RGB signal + * Set RGB full range (0-255) */ + io_write_and_or(sd, 0x02, 0x0f, 0x10); + break; + } + + if (state->selected_input == ADV7604_INPUT_VGA_COMP) { + /* Receiving analog YPbPr signal + * Set automode */ + io_write_and_or(sd, 0x02, 0x0f, 0xf0); + break; + } + + if (hdmi_signal) { + /* Receiving HDMI signal + * Set automode */ + io_write_and_or(sd, 0x02, 0x0f, 0xf0); + break; + } - /* ADV7604 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); + /* Receiving DVI-D signal + * ADV7604 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); + if (is_digital_input(sd) && rgb_output) { + adv7604_set_offset(sd, false, 0x40, 0x40, 0x40); } else { - /* RGB full range (0-255) */ - io_write_and_or(sd, 0x02, 0x0f, 0x10); + adv7604_set_gain(sd, false, 0xe0, 0xe0, 0xe0); + adv7604_set_offset(sd, false, 0x70, 0x70, 0x70); } - } else { - /* receiving HDMI or analog signal, set automode */ - io_write_and_or(sd, 0x02, 0x0f, 0xf0); } break; case V4L2_DV_RGB_RANGE_LIMITED: + if (state->selected_input == ADV7604_INPUT_VGA_COMP) { + /* YCrCb limited range (16-235) */ + io_write_and_or(sd, 0x02, 0x0f, 0x20); + break; + } + /* RGB limited range (16-235) */ io_write_and_or(sd, 0x02, 0x0f, 0x00); + break; case V4L2_DV_RGB_RANGE_FULL: + if (state->selected_input == ADV7604_INPUT_VGA_COMP) { + /* YCrCb full range (0-255) */ + io_write_and_or(sd, 0x02, 0x0f, 0x60); + break; + } + /* RGB full range (0-255) */ io_write_and_or(sd, 0x02, 0x0f, 0x10); + + if (is_analog_input(sd) || hdmi_signal) + break; + + /* Adjust gain/offset for DVI-D signals only */ + if (rgb_output) { + adv7604_set_offset(sd, false, 0x40, 0x40, 0x40); + } else { + adv7604_set_gain(sd, false, 0xe0, 0xe0, 0xe0); + adv7604_set_offset(sd, false, 0x70, 0x70, 0x70); + } break; } } - static int adv7604_s_ctrl(struct v4l2_ctrl *ctrl) { struct v4l2_subdev *sd = to_sd(ctrl); @@ -983,8 +1081,9 @@ static inline bool no_power(struct v4l2_subdev *sd) static inline bool no_signal_tmds(struct v4l2_subdev *sd) { - /* TODO port B, C and D */ - return !(io_read(sd, 0x6a) & 0x10); + struct adv7604_state *state = to_state(sd); + + return !(io_read(sd, 0x6a) & (0x10 >> state->selected_input)); } static inline bool no_lock_tmds(struct v4l2_subdev *sd) @@ -1011,7 +1110,6 @@ static inline bool no_lock_stdi(struct v4l2_subdev *sd) static inline bool no_signal(struct v4l2_subdev *sd) { - struct adv7604_state *state = to_state(sd); bool ret; ret = no_power(sd); @@ -1019,7 +1117,7 @@ static inline bool no_signal(struct v4l2_subdev *sd) ret |= no_lock_stdi(sd); ret |= no_lock_sspd(sd); - if (DIGITAL_INPUT) { + if (is_digital_input(sd)) { ret |= no_lock_tmds(sd); ret |= no_signal_tmds(sd); } @@ -1036,13 +1134,11 @@ static inline bool no_lock_cp(struct v4l2_subdev *sd) static int adv7604_g_input_status(struct v4l2_subdev *sd, u32 *status) { - struct adv7604_state *state = to_state(sd); - *status = 0; *status |= no_power(sd) ? V4L2_IN_ST_NO_POWER : 0; *status |= no_signal(sd) ? V4L2_IN_ST_NO_SIGNAL : 0; if (no_lock_cp(sd)) - *status |= DIGITAL_INPUT ? V4L2_IN_ST_NO_SYNC : V4L2_IN_ST_NO_H_LOCK; + *status |= is_digital_input(sd) ? V4L2_IN_ST_NO_SYNC : V4L2_IN_ST_NO_H_LOCK; v4l2_dbg(1, debug, sd, "%s: status = 0x%x\n", __func__, *status); @@ -1157,13 +1253,11 @@ static int adv7604_enum_dv_timings(struct v4l2_subdev *sd, static int adv7604_dv_timings_cap(struct v4l2_subdev *sd, struct v4l2_dv_timings_cap *cap) { - struct adv7604_state *state = to_state(sd); - cap->type = V4L2_DV_BT_656_1120; cap->bt.max_width = 1920; cap->bt.max_height = 1200; cap->bt.min_pixelclock = 25000000; - if (DIGITAL_INPUT) + if (is_digital_input(sd)) cap->bt.max_pixelclock = 225000000; else cap->bt.max_pixelclock = 170000000; @@ -1179,12 +1273,11 @@ static int adv7604_dv_timings_cap(struct v4l2_subdev *sd, static void adv7604_fill_optional_dv_timings_fields(struct v4l2_subdev *sd, struct v4l2_dv_timings *timings) { - struct adv7604_state *state = to_state(sd); int i; for (i = 0; adv7604_timings[i].bt.width; i++) { if (v4l2_match_dv_timings(timings, &adv7604_timings[i], - DIGITAL_INPUT ? 250000 : 1000000)) { + is_digital_input(sd) ? 250000 : 1000000)) { *timings = adv7604_timings[i]; break; } @@ -1204,6 +1297,7 @@ static int adv7604_query_dv_timings(struct v4l2_subdev *sd, memset(timings, 0, sizeof(struct v4l2_dv_timings)); if (no_signal(sd)) { + state->restart_stdi_once = true; v4l2_dbg(1, debug, sd, "%s: no valid signal\n", __func__); return -ENOLINK; } @@ -1216,7 +1310,7 @@ static int adv7604_query_dv_timings(struct v4l2_subdev *sd, bt->interlaced = stdi.interlaced ? V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE; - if (DIGITAL_INPUT) { + if (is_digital_input(sd)) { uint32_t freq; timings->type = V4L2_DV_BT_656_1120; @@ -1305,8 +1399,8 @@ found: return -ENOLINK; } - if ((!DIGITAL_INPUT && bt->pixelclock > 170000000) || - (DIGITAL_INPUT && bt->pixelclock > 225000000)) { + if ((is_analog_input(sd) && bt->pixelclock > 170000000) || + (is_digital_input(sd) && bt->pixelclock > 225000000)) { v4l2_dbg(1, debug, sd, "%s: pixelclock out of range %d\n", __func__, (u32)bt->pixelclock); return -ERANGE; @@ -1329,10 +1423,15 @@ static int adv7604_s_dv_timings(struct v4l2_subdev *sd, if (!timings) return -EINVAL; + if (v4l2_match_dv_timings(&state->timings, timings, 0)) { + v4l2_dbg(1, debug, sd, "%s: no change\n", __func__); + return 0; + } + bt = &timings->bt; - if ((!DIGITAL_INPUT && bt->pixelclock > 170000000) || - (DIGITAL_INPUT && bt->pixelclock > 225000000)) { + if ((is_analog_input(sd) && bt->pixelclock > 170000000) || + (is_digital_input(sd) && bt->pixelclock > 225000000)) { v4l2_dbg(1, debug, sd, "%s: pixelclock out of range %d\n", __func__, (u32)bt->pixelclock); return -ERANGE; @@ -1354,7 +1453,6 @@ static int adv7604_s_dv_timings(struct v4l2_subdev *sd, set_rgb_quantization_range(sd); - if (debug > 1) v4l2_print_dv_timings(sd->name, "adv7604_s_dv_timings: ", timings, true); @@ -1374,30 +1472,24 @@ static void enable_input(struct v4l2_subdev *sd) { struct adv7604_state *state = to_state(sd); - switch (state->mode) { - case ADV7604_MODE_COMP: - case ADV7604_MODE_GR: - /* enable */ + if (is_analog_input(sd)) { io_write(sd, 0x15, 0xb0); /* Disable Tristate of Pins (no audio) */ - break; - case ADV7604_MODE_HDMI: - /* enable */ - hdmi_write(sd, 0x1a, 0x0a); /* Unmute audio */ + } else if (is_digital_input(sd)) { + hdmi_write_and_or(sd, 0x00, 0xfc, state->selected_input); 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; + hdmi_write_and_or(sd, 0x1a, 0xef, 0x00); /* Unmute audio */ + } else { + v4l2_dbg(2, debug, sd, "%s: Unknown port %d selected\n", + __func__, state->selected_input); } } static void disable_input(struct v4l2_subdev *sd) { - /* disable */ + hdmi_write_and_or(sd, 0x1a, 0xef, 0x10); /* Mute audio */ + msleep(16); /* 512 samples with >= 32 kHz sample rate [REF_03, c. 7.16.10] */ 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 */ } @@ -1405,9 +1497,7 @@ static void select_input(struct v4l2_subdev *sd) { struct adv7604_state *state = to_state(sd); - switch (state->mode) { - case ADV7604_MODE_COMP: - case ADV7604_MODE_GR: + if (is_analog_input(sd)) { /* reset ADI recommended settings for HDMI: */ /* "ADV7604 Register Settings Recommendations (rev. 2.5, June 2010)" p. 4. */ hdmi_write(sd, 0x0d, 0x04); /* HDMI filter optimization */ @@ -1433,9 +1523,9 @@ static void select_input(struct v4l2_subdev *sd) 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; + } else if (is_digital_input(sd)) { + hdmi_write(sd, 0x00, state->selected_input & 0x03); - case ADV7604_MODE_HDMI: /* set ADI recommended settings for HDMI: */ /* "ADV7604 Register Settings Recommendations (rev. 2.5, June 2010)" p. 4. */ hdmi_write(sd, 0x0d, 0x84); /* HDMI filter optimization */ @@ -1461,12 +1551,9 @@ static void select_input(struct v4l2_subdev *sd) cp_write(sd, 0x3e, 0x00); /* CP core pre-gain control */ cp_write(sd, 0xc3, 0x39); /* CP coast control. Graphics mode */ cp_write(sd, 0x40, 0x80); /* CP core pre-gain control. Graphics mode */ - - break; - default: - v4l2_dbg(2, debug, sd, "%s: Unknown mode %d\n", - __func__, state->mode); - break; + } else { + v4l2_dbg(2, debug, sd, "%s: Unknown port %d selected\n", + __func__, state->selected_input); } } @@ -1475,9 +1562,13 @@ static int adv7604_s_routing(struct v4l2_subdev *sd, { struct adv7604_state *state = to_state(sd); - v4l2_dbg(2, debug, sd, "%s: input %d", __func__, input); + v4l2_dbg(2, debug, sd, "%s: input %d, selected input %d", + __func__, input, state->selected_input); + + if (input == state->selected_input) + return 0; - state->mode = input; + state->selected_input = input; disable_input(sd); @@ -1516,36 +1607,47 @@ static int adv7604_g_mbus_fmt(struct v4l2_subdev *sd, 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; + const u8 irq_reg_0x43 = io_read(sd, 0x43); + const u8 irq_reg_0x6b = io_read(sd, 0x6b); + const u8 irq_reg_0x70 = io_read(sd, 0x70); + u8 fmt_change_digital; + u8 fmt_change; + u8 tx_5v; + + if (irq_reg_0x43) + io_write(sd, 0x44, irq_reg_0x43); + if (irq_reg_0x70) + io_write(sd, 0x71, irq_reg_0x70); + if (irq_reg_0x6b) + io_write(sd, 0x6c, irq_reg_0x6b); + + v4l2_dbg(2, debug, sd, "%s: ", __func__); /* format change */ - fmt_change = io_read(sd, 0x43) & 0x98; - if (fmt_change) - io_write(sd, 0x44, fmt_change); - fmt_change_digital = DIGITAL_INPUT ? (io_read(sd, 0x6b) & 0xc0) : 0; - if (fmt_change_digital) - io_write(sd, 0x6c, fmt_change_digital); + fmt_change = irq_reg_0x43 & 0x98; + fmt_change_digital = is_digital_input(sd) ? (irq_reg_0x6b & 0xc0) : 0; + if (fmt_change || fmt_change_digital) { v4l2_dbg(1, debug, sd, "%s: fmt_change = 0x%x, fmt_change_digital = 0x%x\n", __func__, fmt_change, fmt_change_digital); - 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); - } + v4l2_subdev_notify(sd, ADV7604_FMT_CHANGE, NULL); if (handled) *handled = true; } + /* HDMI/DVI mode */ + if (irq_reg_0x6b & 0x01) { + v4l2_dbg(1, debug, sd, "%s: irq %s mode\n", __func__, + (io_read(sd, 0x6a) & 0x01) ? "HDMI" : "DVI"); + set_rgb_quantization_range(sd); + if (handled) + *handled = true; + } + /* tx 5v detect */ - tx_5v = io_read(sd, 0x70) & 0x10; + tx_5v = io_read(sd, 0x70) & 0x1e; if (tx_5v) { v4l2_dbg(1, debug, sd, "%s: tx_5v: 0x%x\n", __func__, tx_5v); io_write(sd, 0x71, tx_5v); @@ -1559,55 +1661,178 @@ static int adv7604_isr(struct v4l2_subdev *sd, u32 status, bool *handled) static int adv7604_get_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid) { struct adv7604_state *state = to_state(sd); + u8 *data = NULL; - if (edid->pad != 0) + if (edid->pad > ADV7604_EDID_PORT_D) return -EINVAL; if (edid->blocks == 0) return -EINVAL; - if (edid->start_block >= state->edid_blocks) + if (edid->blocks > 2) + return -EINVAL; + if (edid->start_block > 1) return -EINVAL; - if (edid->start_block + edid->blocks > state->edid_blocks) - edid->blocks = state->edid_blocks - edid->start_block; + if (edid->start_block == 1) + edid->blocks = 1; if (!edid->edid) return -EINVAL; - memcpy(edid->edid + edid->start_block * 128, - state->edid + edid->start_block * 128, + + if (edid->blocks > state->edid.blocks) + edid->blocks = state->edid.blocks; + + switch (edid->pad) { + case ADV7604_EDID_PORT_A: + case ADV7604_EDID_PORT_B: + case ADV7604_EDID_PORT_C: + case ADV7604_EDID_PORT_D: + if (state->edid.present & (1 << edid->pad)) + data = state->edid.edid; + break; + default: + return -EINVAL; + break; + } + if (!data) + return -ENODATA; + + memcpy(edid->edid, + data + edid->start_block * 128, edid->blocks * 128); return 0; } +static int get_edid_spa_location(const u8 *edid) +{ + u8 d; + + if ((edid[0x7e] != 1) || + (edid[0x80] != 0x02) || + (edid[0x81] != 0x03)) { + return -1; + } + + /* 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 -1; +} + static int adv7604_set_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid) { struct adv7604_state *state = to_state(sd); + int spa_loc; + int tmp = 0; int err; + int i; - if (edid->pad != 0) + if (edid->pad > ADV7604_EDID_PORT_D) return -EINVAL; if (edid->start_block != 0) return -EINVAL; if (edid->blocks == 0) { - /* Pull down the hotplug pin */ - v4l2_subdev_notify(sd, ADV7604_HOTPLUG, (void *)0); - /* Disables I2C access to internal EDID ram from DDC port */ - rep_write_and_or(sd, 0x77, 0xf0, 0x0); - state->edid_blocks = 0; + /* Disable hotplug and I2C access to EDID RAM from DDC port */ + state->edid.present &= ~(1 << edid->pad); + v4l2_subdev_notify(sd, ADV7604_HOTPLUG, (void *)&state->edid.present); + rep_write_and_or(sd, 0x77, 0xf0, state->edid.present); + /* Fall back to a 16:9 aspect ratio */ state->aspect_ratio.numerator = 16; state->aspect_ratio.denominator = 9; + + if (!state->edid.present) + state->edid.blocks = 0; + + v4l2_dbg(2, debug, sd, "%s: clear EDID pad %d, edid.present = 0x%x\n", + __func__, edid->pad, state->edid.present); return 0; } - if (edid->blocks > 2) + if (edid->blocks > 2) { + edid->blocks = 2; return -E2BIG; + } if (!edid->edid) return -EINVAL; - memcpy(state->edid, edid->edid, 128 * edid->blocks); - state->edid_blocks = edid->blocks; + + v4l2_dbg(2, debug, sd, "%s: write EDID pad %d, edid.present = 0x%x\n", + __func__, edid->pad, state->edid.present); + + /* Disable hotplug and I2C access to EDID RAM from DDC port */ + cancel_delayed_work_sync(&state->delayed_work_enable_hotplug); + v4l2_subdev_notify(sd, ADV7604_HOTPLUG, (void *)&tmp); + rep_write_and_or(sd, 0x77, 0xf0, 0x00); + + spa_loc = get_edid_spa_location(edid->edid); + if (spa_loc < 0) + spa_loc = 0xc0; /* Default value [REF_02, p. 116] */ + + switch (edid->pad) { + case ADV7604_EDID_PORT_A: + state->spa_port_a[0] = edid->edid[spa_loc]; + state->spa_port_a[1] = edid->edid[spa_loc + 1]; + break; + case ADV7604_EDID_PORT_B: + rep_write(sd, 0x70, edid->edid[spa_loc]); + rep_write(sd, 0x71, edid->edid[spa_loc + 1]); + break; + case ADV7604_EDID_PORT_C: + rep_write(sd, 0x72, edid->edid[spa_loc]); + rep_write(sd, 0x73, edid->edid[spa_loc + 1]); + break; + case ADV7604_EDID_PORT_D: + rep_write(sd, 0x74, edid->edid[spa_loc]); + rep_write(sd, 0x75, edid->edid[spa_loc + 1]); + break; + default: + return -EINVAL; + } + rep_write(sd, 0x76, spa_loc & 0xff); + rep_write_and_or(sd, 0x77, 0xbf, (spa_loc >> 2) & 0x40); + + edid->edid[spa_loc] = state->spa_port_a[0]; + edid->edid[spa_loc + 1] = state->spa_port_a[1]; + + memcpy(state->edid.edid, edid->edid, 128 * edid->blocks); + state->edid.blocks = edid->blocks; state->aspect_ratio = v4l2_calc_aspect_ratio(edid->edid[0x15], edid->edid[0x16]); - err = edid_write_block(sd, 128 * edid->blocks, state->edid); - if (err < 0) - v4l2_err(sd, "error %d writing edid\n", err); - return err; + state->edid.present |= 1 << edid->pad; + + err = edid_write_block(sd, 128 * edid->blocks, state->edid.edid); + if (err < 0) { + v4l2_err(sd, "error %d writing edid pad %d\n", err, edid->pad); + return err; + } + + /* adv7604 calculates the checksums and enables I2C access to internal + EDID RAM from DDC port. */ + rep_write_and_or(sd, 0x77, 0xf0, state->edid.present); + + for (i = 0; i < 1000; i++) { + if (rep_read(sd, 0x7d) & state->edid.present) + break; + mdelay(1); + } + if (i == 1000) { + v4l2_err(sd, "error enabling edid (0x%x)\n", state->edid.present); + return -EIO; + } + + + /* enable hotplug after 100 ms */ + queue_delayed_work(state->work_queues, + &state->delayed_work_enable_hotplug, HZ / 10); + return 0; } /*********** avi info frame CEA-861-E **************/ @@ -1670,7 +1895,7 @@ static int adv7604_log_status(struct v4l2_subdev *sd) char *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", + "xvYCC Bt.601", "xvYCC Bt.709", "YCbCr Bt.601 (0-255)", "YCbCr Bt.709 (0-255)", "invalid", "invalid", "invalid", "invalid", "invalid", "invalid", "invalid", "automatic" @@ -1689,16 +1914,20 @@ static int adv7604_log_status(struct v4l2_subdev *sd) 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" : (DIGITAL_INPUT ? "DVI-D" : "DVI-A")); - v4l2_info(sd, "EDID: %s\n", ((rep_read(sd, 0x7d) & 0x01) && - (rep_read(sd, 0x77) & 0x01)) ? "enabled" : "disabled "); + v4l2_info(sd, "EDID enabled port A: %s, B: %s, C: %s, D: %s\n", + ((rep_read(sd, 0x7d) & 0x01) ? "Yes" : "No"), + ((rep_read(sd, 0x7d) & 0x02) ? "Yes" : "No"), + ((rep_read(sd, 0x7d) & 0x04) ? "Yes" : "No"), + ((rep_read(sd, 0x7d) & 0x08) ? "Yes" : "No")); v4l2_info(sd, "CEC: %s\n", !!(cec_read(sd, 0x2a) & 0x01) ? "enabled" : "disabled"); v4l2_info(sd, "-----Signal status-----\n"); - v4l2_info(sd, "Cable detected (+5V power): %s\n", - (io_read(sd, 0x6f) & 0x10) ? "true" : "false"); + v4l2_info(sd, "Cable detected (+5V power) port A: %s, B: %s, C: %s, D: %s\n", + ((io_read(sd, 0x6f) & 0x10) ? "Yes" : "No"), + ((io_read(sd, 0x6f) & 0x08) ? "Yes" : "No"), + ((io_read(sd, 0x6f) & 0x04) ? "Yes" : "No"), + ((io_read(sd, 0x6f) & 0x02) ? "Yes" : "No")); v4l2_info(sd, "TMDS signal detected: %s\n", no_signal_tmds(sd) ? "false" : "true"); v4l2_info(sd, "TMDS signal locked: %s\n", @@ -1744,11 +1973,14 @@ static int adv7604_log_status(struct v4l2_subdev *sd) v4l2_info(sd, "Color space conversion: %s\n", csc_coeff_sel_rb[cp_read(sd, 0xfc) >> 4]); - if (!DIGITAL_INPUT) + 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, "Digital video port selected: %c\n", + (hdmi_read(sd, 0x00) & 0x03) + 'A'); + 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" : ""); @@ -1894,10 +2126,16 @@ static int adv7604_core_init(struct v4l2_subdev *sd) pdata->replicate_av_codes << 1 | pdata->invert_cbcr << 0); - /* TODO from platform data */ cp_write(sd, 0x69, 0x30); /* Enable CP CSC */ - io_write(sd, 0x06, 0xa6); /* positive VS and HS */ - io_write(sd, 0x14, 0x7f); /* Drive strength adjusted to max */ + + /* VS, HS polarities */ + io_write(sd, 0x06, 0xa0 | pdata->inv_vs_pol << 2 | pdata->inv_hs_pol << 1); + + /* Adjust drive strength */ + io_write(sd, 0x14, 0x40 | pdata->dr_str_data << 4 | + pdata->dr_str_clk << 2 | + pdata->dr_str_sync); + cp_write(sd, 0xba, (pdata->hdmi_free_run_mode << 1) | 0x01); /* HDMI free run */ cp_write(sd, 0xf3, 0xdc); /* Low threshold to enter/exit free run mode */ cp_write(sd, 0xf9, 0x23); /* STDI ch. 1 - LCVS change threshold - @@ -1907,6 +2145,11 @@ static int adv7604_core_init(struct v4l2_subdev *sd) cp_write(sd, 0xc9, 0x2d); /* use prim_mode and vid_std as free run resolution for digital formats */ + /* HDMI audio */ + hdmi_write_and_or(sd, 0x15, 0xfc, 0x03); /* Mute on FIFO over-/underflow [REF_01, c. 1.2.18] */ + hdmi_write_and_or(sd, 0x1a, 0xf1, 0x08); /* Wait 1 s before unmute */ + hdmi_write_and_or(sd, 0x68, 0xf9, 0x06); /* FIFO reset on over-/underflow [REF_01, c. 1.2.19] */ + /* TODO from platform data */ afe_write(sd, 0xb5, 0x01); /* Setting MCLK to 256Fs */ @@ -1917,8 +2160,8 @@ static int adv7604_core_init(struct v4l2_subdev *sd) io_write(sd, 0x40, 0xc2); /* Configure INT1 */ io_write(sd, 0x41, 0xd7); /* STDI irq for any change, disable INT2 */ io_write(sd, 0x46, 0x98); /* Enable SSPD, STDI and CP unlocked interrupts */ - io_write(sd, 0x6e, 0xc0); /* Enable V_LOCKED and DE_REGEN_LCK interrupts */ - io_write(sd, 0x73, 0x10); /* Enable CABLE_DET_A_ST (+5v) interrupt */ + io_write(sd, 0x6e, 0xc1); /* Enable V_LOCKED, DE_REGEN_LCK, HDMI_MODE interrupts */ + io_write(sd, 0x73, 0x1e); /* Enable CABLE_DET_A_ST (+5v) interrupts */ return v4l2_ctrl_handler_setup(sd->ctrl_handler); } @@ -1964,6 +2207,8 @@ static struct i2c_client *adv7604_dummy_client(struct v4l2_subdev *sd, static int adv7604_probe(struct i2c_client *client, const struct i2c_device_id *id) { + static const struct v4l2_dv_timings cea640x480 = + V4L2_DV_BT_CEA_640X480P59_94; struct adv7604_state *state; struct adv7604_platform_data *pdata = client->dev.platform_data; struct v4l2_ctrl_handler *hdl; @@ -1984,19 +2229,19 @@ static int adv7604_probe(struct i2c_client *client, /* initialize variables */ state->restart_stdi_once = true; - state->prev_input_status = ~0; + state->selected_input = ~0; /* platform data */ if (!pdata) { v4l_err(client, "No platform data!\n"); return -ENODEV; } - memcpy(&state->pdata, pdata, sizeof(state->pdata)); + state->pdata = *pdata; + state->timings = cea640x480; sd = &state->sd; v4l2_i2c_subdev_init(sd, client, &adv7604_ops); sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; - state->connector_hdmi = pdata->connector_hdmi; /* i2c access to adv7604? */ if (adv_smbus_read_byte_data_check(client, 0xfb, false) != 0x68) { @@ -2020,7 +2265,7 @@ 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); + V4L2_CID_DV_RX_POWER_PRESENT, 0, 0x0f, 0, 0); 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, diff --git a/drivers/media/i2c/adv7842.c b/drivers/media/i2c/adv7842.c index b154f36740b4..1effc21e1cdd 100644 --- a/drivers/media/i2c/adv7842.c +++ b/drivers/media/i2c/adv7842.c @@ -20,10 +20,13 @@ /* * 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 + * REF_01 - Analog devices, ADV7842, + * Register Settings Recommendations, Rev. 1.9, April 2011 + * REF_02 - Analog devices, Software User Guide, UG-206, + * ADV7842 I2C Register Maps, Rev. 0, November 2010 + * REF_03 - Analog devices, Hardware User Guide, UG-214, + * ADV7842 Fast Switching 2:1 HDMI 1.4 Receiver with 3D-Comb + * Decoder and Digitizer , Rev. 0, January 2011 */ @@ -61,6 +64,7 @@ MODULE_LICENSE("GPL"); */ struct adv7842_state { + struct adv7842_platform_data pdata; struct v4l2_subdev sd; struct media_pad pad; struct v4l2_ctrl_handler hdl; @@ -81,7 +85,7 @@ struct adv7842_state { bool is_cea_format; struct workqueue_struct *work_queues; struct delayed_work delayed_work_enable_hotplug; - bool connector_hdmi; + bool restart_stdi_once; bool hdmi_port_a; /* i2c clients */ @@ -491,6 +495,11 @@ static inline int hdmi_write(struct v4l2_subdev *sd, u8 reg, u8 val) return adv_smbus_write_byte_data(state->i2c_hdmi, reg, val); } +static inline int hdmi_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return hdmi_write(sd, reg, (hdmi_read(sd, reg) & mask) | val); +} + static inline int cp_read(struct v4l2_subdev *sd, u8 reg) { struct adv7842_state *state = to_state(sd); @@ -532,7 +541,7 @@ static void main_reset(struct v4l2_subdev *sd) adv_smbus_write_byte_no_check(client, 0xff, 0x80); - mdelay(2); + mdelay(5); } /* ----------------------------------------------------------------------- */ @@ -587,10 +596,10 @@ static void adv7842_delayed_work_enable_hotplug(struct work_struct *work) 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 */ + if (present & (0x04 << ADV7842_EDID_PORT_A)) + mask |= 0x20; + if (present & (0x04 << ADV7842_EDID_PORT_B)) + mask |= 0x10; io_write_and_or(sd, 0x20, 0xcf, mask); } @@ -679,14 +688,12 @@ 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); + v4l2_dbg(2, debug, sd, "%s: write EDID on port %c (spa at 0x%x)\n", + __func__, (port == ADV7842_EDID_PORT_A) ? 'A' : 'B', spa_loc); /* HPA disable on port A and B */ io_write_and_or(sd, 0x20, 0xcf, 0x00); @@ -694,6 +701,9 @@ static int edid_write_hdmi_segment(struct v4l2_subdev *sd, u8 port) /* Disable I2C access to internal EDID ram from HDMI DDC ports */ rep_write_and_or(sd, 0x77, 0xf3, 0x00); + if (!state->hdmi_edid.present) + return 0; + /* edid segment pointer '0' for HDMI ports */ rep_write_and_or(sd, 0x77, 0xef, 0x00); @@ -703,44 +713,32 @@ static int edid_write_hdmi_segment(struct v4l2_subdev *sd, u8 port) 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); + if (spa_loc < 0) + spa_loc = 0xc0; /* Default value [REF_02, p. 199] */ + + if (port == ADV7842_EDID_PORT_A) { + rep_write(sd, 0x72, val[spa_loc]); + rep_write(sd, 0x73, val[spa_loc + 1]); } 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(sd, 0x74, val[spa_loc]); + rep_write(sd, 0x75, val[spa_loc + 1]); } - rep_write_and_or(sd, 0x77, 0xbf, 0x00); + rep_write(sd, 0x76, spa_loc & 0xff); + rep_write_and_or(sd, 0x77, 0xbf, (spa_loc >> 2) & 0x40); /* 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); + rep_write_and_or(sd, 0x77, 0xf3, state->hdmi_edid.present); for (i = 0; i < 1000; i++) { - if (rep_read(sd, 0x7d) & mask) + if (rep_read(sd, 0x7d) & state->hdmi_edid.present) break; mdelay(1); } if (i == 1000) { - v4l_err(client, "error enabling edid on port %d\n", port); + v4l_err(client, "error enabling edid on port %c\n", + (port == ADV7842_EDID_PORT_A) ? 'A' : 'B'); return -EIO; } @@ -927,7 +925,7 @@ static int configure_predefined_video_timings(struct v4l2_subdev *sd, cp_write(sd, 0x27, 0x00); cp_write(sd, 0x28, 0x00); cp_write(sd, 0x29, 0x00); - cp_write(sd, 0x8f, 0x00); + cp_write(sd, 0x8f, 0x40); cp_write(sd, 0x90, 0x00); cp_write(sd, 0xa5, 0x00); cp_write(sd, 0xa6, 0x00); @@ -1033,34 +1031,60 @@ static void set_rgb_quantization_range(struct v4l2_subdev *sd) { struct adv7842_state *state = to_state(sd); + v4l2_dbg(2, debug, sd, "%s: rgb_quantization_range = %d\n", + __func__, state->rgb_quantization_range); + 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 */ + if (state->mode == ADV7842_MODE_RGB) { + /* Receiving analog RGB signal + * Set RGB full range (0-255) */ + io_write_and_or(sd, 0x02, 0x0f, 0x10); + break; + } + + if (state->mode == ADV7842_MODE_COMP) { + /* Receiving analog YPbPr signal + * Set automode */ io_write_and_or(sd, 0x02, 0x0f, 0xf0); + break; + } + + if (hdmi_read(sd, 0x05) & 0x80) { + /* Receiving HDMI signal + * Set automode */ + io_write_and_or(sd, 0x02, 0x0f, 0xf0); + break; + } + + /* 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); } break; case V4L2_DV_RGB_RANGE_LIMITED: - /* RGB limited range (16-235) */ - io_write_and_or(sd, 0x02, 0x0f, 0x00); + if (state->mode == ADV7842_MODE_COMP) { + /* YCrCb limited range (16-235) */ + io_write_and_or(sd, 0x02, 0x0f, 0x20); + } else { + /* 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); + if (state->mode == ADV7842_MODE_COMP) { + /* YCrCb full range (0-255) */ + io_write_and_or(sd, 0x02, 0x0f, 0x60); + } else { + /* RGB full range (0-255) */ + io_write_and_or(sd, 0x02, 0x0f, 0x10); + } break; } } @@ -1298,7 +1322,7 @@ static int adv7842_dv_timings_cap(struct v4l2_subdev *sd, } /* Fill the optional fields .standards and .flags in struct v4l2_dv_timings - if the format is listed in adv7604_timings[] */ + if the format is listed in adv7842_timings[] */ static void adv7842_fill_optional_dv_timings_fields(struct v4l2_subdev *sd, struct v4l2_dv_timings *timings) { @@ -1314,119 +1338,106 @@ static int adv7842_query_dv_timings(struct v4l2_subdev *sd, struct v4l2_bt_timings *bt = &timings->bt; struct stdi_readback stdi = { 0 }; + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + /* SDP block */ if (state->mode == ADV7842_MODE_SDP) return -ENODATA; /* read STDI */ if (read_stdi(sd, &stdi)) { + state->restart_stdi_once = true; 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; + uint32_t freq; - 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; + timings->type = V4L2_DV_BT_656_1120; - if (w != bt->width || h != bt->height) - continue; + bt->width = (hdmi_read(sd, 0x07) & 0x0f) * 256 + hdmi_read(sd, 0x08); + bt->height = (hdmi_read(sd, 0x09) & 0x0f) * 256 + hdmi_read(sd, 0x0a); + freq = (hdmi_read(sd, 0x06) * 1000000) + + ((hdmi_read(sd, 0x3b) & 0x30) >> 4) * 250000; - if (abs(freq - bt->pixelclock) > 1000000) - continue; - *timings = v4l2_dv_timings_presets[i]; - return 0; + if (is_hdmi(sd)) { + /* adjust for deep color mode */ + freq = freq * 8 / (((hdmi_read(sd, 0x0b) & 0xc0) >> 5) + 8); } - - 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 + + bt->pixelclock = freq; + bt->hfrontporch = (hdmi_read(sd, 0x20) & 0x03) * 256 + hdmi_read(sd, 0x21); - bt->hsync = (hdmi_read(sd, 0x22) & 0x1f) * 256 + + bt->hsync = (hdmi_read(sd, 0x22) & 0x03) * 256 + hdmi_read(sd, 0x23); - bt->hbackporch = (hdmi_read(sd, 0x24) & 0x1f) * 256 + + bt->hbackporch = (hdmi_read(sd, 0x24) & 0x03) * 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; + bt->vfrontporch = ((hdmi_read(sd, 0x2a) & 0x1f) * 256 + + hdmi_read(sd, 0x2b)) / 2; + bt->vsync = ((hdmi_read(sd, 0x2e) & 0x1f) * 256 + + hdmi_read(sd, 0x2f)) / 2; + bt->vbackporch = ((hdmi_read(sd, 0x32) & 0x1f) * 256 + + hdmi_read(sd, 0x33)) / 2; + bt->polarities = ((hdmi_read(sd, 0x05) & 0x10) ? V4L2_DV_VSYNC_POS_POL : 0) | + ((hdmi_read(sd, 0x05) & 0x20) ? V4L2_DV_HSYNC_POS_POL : 0); + if (bt->interlaced == V4L2_DV_INTERLACED) { + bt->height += (hdmi_read(sd, 0x0b) & 0x0f) * 256 + + hdmi_read(sd, 0x0c); + bt->il_vfrontporch = ((hdmi_read(sd, 0x2c) & 0x1f) * 256 + + hdmi_read(sd, 0x2d)) / 2; + bt->il_vsync = ((hdmi_read(sd, 0x30) & 0x1f) * 256 + + hdmi_read(sd, 0x31)) / 2; + bt->vbackporch = ((hdmi_read(sd, 0x34) & 0x1f) * 256 + + hdmi_read(sd, 0x35)) / 2; } - + adv7842_fill_optional_dv_timings_fields(sd, timings); + } else { + /* find format + * Since LCVS values are inaccurate [REF_03, p. 339-340], + * stdi2dv_timings() is called with lcvs +-1 if the first attempt fails. + */ + if (!stdi2dv_timings(sd, &stdi, timings)) + goto found; + stdi.lcvs += 1; + v4l2_dbg(1, debug, sd, "%s: lcvs + 1 = %d\n", __func__, stdi.lcvs); + if (!stdi2dv_timings(sd, &stdi, timings)) + goto found; + stdi.lcvs -= 2; + v4l2_dbg(1, debug, sd, "%s: lcvs - 1 = %d\n", __func__, stdi.lcvs); if (stdi2dv_timings(sd, &stdi, timings)) { + /* + * The STDI block may measure wrong values, especially + * for lcvs and lcf. If the driver can not find any + * valid timing, the STDI block is restarted to measure + * the video timings again. The function will return an + * error, but the restart of STDI will generate a new + * STDI interrupt and the format detection process will + * restart. + */ + if (state->restart_stdi_once) { + v4l2_dbg(1, debug, sd, "%s: restart STDI\n", __func__); + /* TODO restart STDI for Sync Channel 2 */ + /* enter one-shot mode */ + cp_write_and_or(sd, 0x86, 0xf9, 0x00); + /* trigger STDI restart */ + cp_write_and_or(sd, 0x86, 0xf9, 0x04); + /* reset to continuous mode */ + cp_write_and_or(sd, 0x86, 0xf9, 0x02); + state->restart_stdi_once = false; + return -ENOLINK; + } v4l2_dbg(1, debug, sd, "%s: format not supported\n", __func__); return -ERANGE; } + state->restart_stdi_once = true; } +found: if (debug > 1) - v4l2_print_dv_timings(sd->name, "adv7842_query_dv_timings: ", - timings, true); + v4l2_print_dv_timings(sd->name, "adv7842_query_dv_timings:", + timings, true); return 0; } @@ -1437,9 +1448,16 @@ static int adv7842_s_dv_timings(struct v4l2_subdev *sd, struct v4l2_bt_timings *bt; int err; + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + if (state->mode == ADV7842_MODE_SDP) return -ENODATA; + if (v4l2_match_dv_timings(&state->timings, timings, 0)) { + v4l2_dbg(1, debug, sd, "%s: no change\n", __func__); + return 0; + } + bt = &timings->bt; if (!v4l2_valid_dv_timings(timings, adv7842_get_dv_timings_cap(sd), @@ -1450,7 +1468,7 @@ static int adv7842_s_dv_timings(struct v4l2_subdev *sd, state->timings = *timings; - cp_write(sd, 0x91, bt->interlaced ? 0x50 : 0x10); + cp_write(sd, 0x91, bt->interlaced ? 0x40 : 0x00); /* Use prim_mode and vid_std when available */ err = configure_predefined_video_timings(sd, timings); @@ -1483,18 +1501,18 @@ static int adv7842_g_dv_timings(struct v4l2_subdev *sd, static void enable_input(struct v4l2_subdev *sd) { struct adv7842_state *state = to_state(sd); + + set_rgb_quantization_range(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 */ + hdmi_write_and_or(sd, 0x1a, 0xef, 0x00); /* Unmute audio */ break; default: v4l2_dbg(2, debug, sd, "%s: Unknown mode %d\n", @@ -1505,9 +1523,9 @@ static void enable_input(struct v4l2_subdev *sd) static void disable_input(struct v4l2_subdev *sd) { - /* disable */ + hdmi_write_and_or(sd, 0x1a, 0xef, 0x10); /* Mute audio [REF_01, c. 2.2.2] */ + msleep(16); /* 512 samples with >= 32 kHz sample rate [REF_03, c. 8.29] */ 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 */ } @@ -1575,9 +1593,6 @@ static void select_input(struct v4l2_subdev *sd, 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 */ @@ -1611,8 +1626,6 @@ static void select_input(struct v4l2_subdev *sd, /* 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: @@ -1627,6 +1640,13 @@ static void select_input(struct v4l2_subdev *sd, afe_write(sd, 0x00, 0x00); /* power up ADC */ afe_write(sd, 0xc8, 0x00); /* phase control */ + if (state->mode == ADV7842_MODE_COMP) { + /* force to YCrCb */ + io_write_and_or(sd, 0x02, 0x0f, 0x60); + } else { + /* force to RGB */ + io_write_and_or(sd, 0x02, 0x0f, 0x10); + } /* set ADI recommended settings for digitizer */ /* "ADV7842 Register Settings Recommendations @@ -1722,19 +1742,19 @@ static int adv7842_s_routing(struct v4l2_subdev *sd, 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__); + state->mode = ADV7842_MODE_COMP; + state->vid_std_select = ADV7842_RGB_VID_STD_AUTO_GRAPH_MODE; + break; case ADV7842_SELECT_VGA_RGB: state->mode = ADV7842_MODE_RGB; state->vid_std_select = ADV7842_RGB_VID_STD_AUTO_GRAPH_MODE; @@ -1814,12 +1834,15 @@ static void adv7842_irq_enable(struct v4l2_subdev *sd, bool enable) io_write(sd, 0x78, 0x03); /* Enable SDP Standard Detection Change and SDP Video Detected */ io_write(sd, 0xa0, 0x09); + /* Enable HDMI_MODE interrupt */ + io_write(sd, 0x69, 0x08); } 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); + io_write(sd, 0x69, 0x0); } } @@ -1827,11 +1850,9 @@ 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); + u8 irq_status[6]; - /* disable irq-pin output */ - io_write(sd, 0x40, irq_cfg | 0x3); + adv7842_irq_enable(sd, false); /* read status */ irq_status[0] = io_read(sd, 0x43); @@ -1839,6 +1860,7 @@ static int adv7842_isr(struct v4l2_subdev *sd, u32 status, bool *handled) irq_status[2] = io_read(sd, 0x70); irq_status[3] = io_read(sd, 0x75); irq_status[4] = io_read(sd, 0x9d); + irq_status[5] = io_read(sd, 0x66); /* and clear */ if (irq_status[0]) @@ -1851,10 +1873,14 @@ static int adv7842_isr(struct v4l2_subdev *sd, u32 status, bool *handled) io_write(sd, 0x76, irq_status[3]); if (irq_status[4]) io_write(sd, 0x9e, irq_status[4]); + if (irq_status[5]) + io_write(sd, 0x67, irq_status[5]); - v4l2_dbg(1, debug, sd, "%s: irq %x, %x, %x, %x, %x\n", __func__, + adv7842_irq_enable(sd, true); + + v4l2_dbg(1, debug, sd, "%s: irq %x, %x, %x, %x, %x, %x\n", __func__, irq_status[0], irq_status[1], irq_status[2], - irq_status[3], irq_status[4]); + irq_status[3], irq_status[4], irq_status[5]); /* format change CP */ fmt_change_cp = irq_status[0] & 0x9c; @@ -1871,25 +1897,72 @@ static int adv7842_isr(struct v4l2_subdev *sd, u32 status, bool *handled) else fmt_change_digital = 0; - /* notify */ + /* format change */ 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); + if (handled) + *handled = true; } - /* 5v cable detect */ - if (irq_status[2]) + /* HDMI/DVI mode */ + if (irq_status[5] & 0x08) { + v4l2_dbg(1, debug, sd, "%s: irq %s mode\n", __func__, + (io_read(sd, 0x65) & 0x08) ? "HDMI" : "DVI"); + if (handled) + *handled = true; + } + + /* tx 5v detect */ + if (irq_status[2] & 0x3) { + v4l2_dbg(1, debug, sd, "%s: irq tx_5v\n", __func__); adv7842_s_detect_tx_5v_ctrl(sd); + if (handled) + *handled = true; + } + return 0; +} + +static int adv7842_get_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid) +{ + struct adv7842_state *state = to_state(sd); + u8 *data = NULL; - if (handled) - *handled = true; + if (edid->pad > ADV7842_EDID_PORT_VGA) + return -EINVAL; + if (edid->blocks == 0) + return -EINVAL; + if (edid->blocks > 2) + return -EINVAL; + if (edid->start_block > 1) + return -EINVAL; + if (edid->start_block == 1) + edid->blocks = 1; + if (!edid->edid) + return -EINVAL; - /* re-enable irq-pin output */ - io_write(sd, 0x40, irq_cfg); + switch (edid->pad) { + case ADV7842_EDID_PORT_A: + case ADV7842_EDID_PORT_B: + if (state->hdmi_edid.present & (0x04 << edid->pad)) + data = state->hdmi_edid.edid; + break; + case ADV7842_EDID_PORT_VGA: + if (state->vga_edid.present) + data = state->vga_edid.edid; + break; + default: + return -EINVAL; + } + if (!data) + return -ENODATA; + memcpy(edid->edid, + data + edid->start_block * 128, + edid->blocks * 128); return 0; } @@ -1898,7 +1971,7 @@ 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) + if (e->pad > ADV7842_EDID_PORT_VGA) return -EINVAL; if (e->start_block != 0) return -EINVAL; @@ -1911,20 +1984,25 @@ static int adv7842_set_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *e) state->aspect_ratio = v4l2_calc_aspect_ratio(e->edid[0x15], e->edid[0x16]); - if (e->pad == 2) { + switch (e->pad) { + case ADV7842_EDID_PORT_VGA: 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; + break; + case ADV7842_EDID_PORT_A: + case ADV7842_EDID_PORT_B: memset(&state->hdmi_edid.edid, 0, 256); if (e->blocks) - state->hdmi_edid.present |= mask; + state->hdmi_edid.present |= 0x04 << e->pad; else - state->hdmi_edid.present &= ~mask; - memcpy(&state->hdmi_edid.edid, e->edid, 128*e->blocks); + state->hdmi_edid.present &= ~(0x04 << e->pad); + memcpy(&state->hdmi_edid.edid, e->edid, 128 * e->blocks); err = edid_write_hdmi_segment(sd, e->pad); + break; + default: + return -EINVAL; } if (err < 0) v4l2_err(sd, "error %d writing edid on port %d\n", err, e->pad); @@ -2156,7 +2234,7 @@ static int adv7842_cp_log_status(struct v4l2_subdev *sd) 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", + "xvYCC Bt.601", "xvYCC Bt.709", "YCbCr Bt.601 (0-255)", "YCbCr Bt.709 (0-255)", "invalid", "invalid", "invalid", "invalid", "invalid", "invalid", "invalid", "automatic" @@ -2175,8 +2253,6 @@ static int adv7842_cp_log_status(struct v4l2_subdev *sd) 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", @@ -2354,15 +2430,63 @@ static int adv7842_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) return 0; } +static void adv7842_s_sdp_io(struct v4l2_subdev *sd, struct adv7842_sdp_io_sync_adjustment *s) +{ + if (s && s->adjust) { + 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); + sdp_io_write(sd, 0xa8, s->vs_beg_o); + sdp_io_write(sd, 0xa9, s->vs_beg_e); + sdp_io_write(sd, 0xaa, s->vs_end_o); + sdp_io_write(sd, 0xab, s->vs_end_e); + sdp_io_write(sd, 0xac, s->de_v_beg_o); + sdp_io_write(sd, 0xad, s->de_v_beg_e); + sdp_io_write(sd, 0xae, s->de_v_end_o); + sdp_io_write(sd, 0xaf, s->de_v_end_e); + } else { + /* set to default */ + sdp_io_write(sd, 0x94, 0x00); + sdp_io_write(sd, 0x95, 0x00); + sdp_io_write(sd, 0x96, 0x00); + sdp_io_write(sd, 0x97, 0x20); + sdp_io_write(sd, 0x98, 0x00); + sdp_io_write(sd, 0x99, 0x00); + sdp_io_write(sd, 0x9a, 0x00); + sdp_io_write(sd, 0x9b, 0x00); + sdp_io_write(sd, 0xa8, 0x04); + sdp_io_write(sd, 0xa9, 0x04); + sdp_io_write(sd, 0xaa, 0x04); + sdp_io_write(sd, 0xab, 0x04); + sdp_io_write(sd, 0xac, 0x04); + sdp_io_write(sd, 0xad, 0x04); + sdp_io_write(sd, 0xae, 0x04); + sdp_io_write(sd, 0xaf, 0x04); + } +} + static int adv7842_s_std(struct v4l2_subdev *sd, v4l2_std_id norm) { struct adv7842_state *state = to_state(sd); + struct adv7842_platform_data *pdata = &state->pdata; v4l2_dbg(1, debug, sd, "%s:\n", __func__); if (state->mode != ADV7842_MODE_SDP) return -ENODATA; + if (norm & V4L2_STD_625_50) + adv7842_s_sdp_io(sd, &pdata->sdp_io_sync_625); + else if (norm & V4L2_STD_525_60) + adv7842_s_sdp_io(sd, &pdata->sdp_io_sync_525); + else + adv7842_s_sdp_io(sd, NULL); + if (norm & V4L2_STD_ALL) { state->norm = norm; return 0; @@ -2385,9 +2509,10 @@ static int adv7842_g_std(struct v4l2_subdev *sd, v4l2_std_id *norm) /* ----------------------------------------------------------------------- */ -static int adv7842_core_init(struct v4l2_subdev *sd, - const struct adv7842_platform_data *pdata) +static int adv7842_core_init(struct v4l2_subdev *sd) { + struct adv7842_state *state = to_state(sd); + struct adv7842_platform_data *pdata = &state->pdata; hdmi_write(sd, 0x48, (pdata->disable_pwrdnb ? 0x80 : 0) | (pdata->disable_cable_det_rst ? 0x40 : 0)); @@ -2400,7 +2525,7 @@ static int adv7842_core_init(struct v4l2_subdev *sd, /* video format */ io_write(sd, 0x02, - pdata->inp_color_space << 4 | + 0xf0 | pdata->alt_gamma << 3 | pdata->op_656_range << 2 | pdata->rgb_out << 1 | @@ -2412,13 +2537,24 @@ static int adv7842_core_init(struct v4l2_subdev *sd, pdata->replicate_av_codes << 1 | pdata->invert_cbcr << 0); + /* HDMI audio */ + hdmi_write_and_or(sd, 0x1a, 0xf1, 0x08); /* Wait 1 s before unmute */ + /* Drive strength */ - io_write_and_or(sd, 0x14, 0xc0, pdata->drive_strength.data<<4 | - pdata->drive_strength.clock<<2 | - pdata->drive_strength.sync); + io_write_and_or(sd, 0x14, 0xc0, + pdata->dr_str_data << 4 | + pdata->dr_str_clk << 2 | + pdata->dr_str_sync); /* HDMI free run */ - cp_write(sd, 0xba, (pdata->hdmi_free_run_mode << 1) | 0x01); + cp_write_and_or(sd, 0xba, 0xfc, pdata->hdmi_free_run_enable | + (pdata->hdmi_free_run_mode << 1)); + + /* SPD free run */ + sdp_write_and_or(sd, 0xdd, 0xf0, pdata->sdp_free_run_force | + (pdata->sdp_free_run_cbar_en << 1) | + (pdata->sdp_free_run_man_col_en << 2) | + (pdata->sdp_free_run_force << 3)); /* TODO from platform data */ cp_write(sd, 0x69, 0x14); /* Enable CP CSC */ @@ -2431,18 +2567,6 @@ static int adv7842_core_init(struct v4l2_subdev *sd, 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 */ @@ -2483,12 +2607,11 @@ static int adv7842_core_init(struct v4l2_subdev *sd, 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, 0x19, 0x80 | pdata->llc_dll_phase); io_write(sd, 0x33, 0x40); /* interrupts */ - io_write(sd, 0x40, 0xe2); /* Configure INT1 */ + io_write(sd, 0x40, 0xf2); /* Configure INT1 */ adv7842_irq_enable(sd, true); @@ -2588,6 +2711,7 @@ 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; + struct v4l2_dv_timings timings; int ret = 0; if (!pdata) @@ -2610,7 +2734,7 @@ static int adv7842_command_ram_test(struct v4l2_subdev *sd) adv7842_rewrite_i2c_addresses(sd, pdata); /* and re-init chip and state */ - adv7842_core_init(sd, pdata); + adv7842_core_init(sd); disable_input(sd); @@ -2618,11 +2742,15 @@ static int adv7842_command_ram_test(struct v4l2_subdev *sd) 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); + edid_write_hdmi_segment(sd, ADV7842_EDID_PORT_A); + edid_write_hdmi_segment(sd, ADV7842_EDID_PORT_B); + + timings = state->timings; + + memset(&state->timings, 0, sizeof(struct v4l2_dv_timings)); + + adv7842_s_dv_timings(sd, &timings); return ret; } @@ -2670,6 +2798,7 @@ static const struct v4l2_subdev_video_ops adv7842_video_ops = { }; static const struct v4l2_subdev_pad_ops adv7842_pad_ops = { + .get_edid = adv7842_get_edid, .set_edid = adv7842_set_edid, }; @@ -2712,8 +2841,9 @@ static const struct v4l2_ctrl_config adv7842_ctrl_free_run_color = { }; -static void adv7842_unregister_clients(struct adv7842_state *state) +static void adv7842_unregister_clients(struct v4l2_subdev *sd) { + struct adv7842_state *state = to_state(sd); if (state->i2c_avlink) i2c_unregister_device(state->i2c_avlink); if (state->i2c_cec) @@ -2736,21 +2866,79 @@ static void adv7842_unregister_clients(struct adv7842_state *state) i2c_unregister_device(state->i2c_cp); if (state->i2c_vdp) i2c_unregister_device(state->i2c_vdp); + + state->i2c_avlink = NULL; + state->i2c_cec = NULL; + state->i2c_infoframe = NULL; + state->i2c_sdp_io = NULL; + state->i2c_sdp = NULL; + state->i2c_afe = NULL; + state->i2c_repeater = NULL; + state->i2c_edid = NULL; + state->i2c_hdmi = NULL; + state->i2c_cp = NULL; + state->i2c_vdp = NULL; } -static struct i2c_client *adv7842_dummy_client(struct v4l2_subdev *sd, +static struct i2c_client *adv7842_dummy_client(struct v4l2_subdev *sd, const char *desc, u8 addr, u8 io_reg) { struct i2c_client *client = v4l2_get_subdevdata(sd); + struct i2c_client *cp; io_write(sd, io_reg, addr << 1); - return i2c_new_dummy(client->adapter, io_read(sd, io_reg) >> 1); + + if (addr == 0) { + v4l2_err(sd, "no %s i2c addr configured\n", desc); + return NULL; + } + + cp = i2c_new_dummy(client->adapter, io_read(sd, io_reg) >> 1); + if (!cp) + v4l2_err(sd, "register %s on i2c addr 0x%x failed\n", desc, addr); + + return cp; +} + +static int adv7842_register_clients(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + struct adv7842_platform_data *pdata = &state->pdata; + + state->i2c_avlink = adv7842_dummy_client(sd, "avlink", pdata->i2c_avlink, 0xf3); + state->i2c_cec = adv7842_dummy_client(sd, "cec", pdata->i2c_cec, 0xf4); + state->i2c_infoframe = adv7842_dummy_client(sd, "infoframe", pdata->i2c_infoframe, 0xf5); + state->i2c_sdp_io = adv7842_dummy_client(sd, "sdp_io", pdata->i2c_sdp_io, 0xf2); + state->i2c_sdp = adv7842_dummy_client(sd, "sdp", pdata->i2c_sdp, 0xf1); + state->i2c_afe = adv7842_dummy_client(sd, "afe", pdata->i2c_afe, 0xf8); + state->i2c_repeater = adv7842_dummy_client(sd, "repeater", pdata->i2c_repeater, 0xf9); + state->i2c_edid = adv7842_dummy_client(sd, "edid", pdata->i2c_edid, 0xfa); + state->i2c_hdmi = adv7842_dummy_client(sd, "hdmi", pdata->i2c_hdmi, 0xfb); + state->i2c_cp = adv7842_dummy_client(sd, "cp", pdata->i2c_cp, 0xfd); + state->i2c_vdp = adv7842_dummy_client(sd, "vdp", 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) + return -1; + + return 0; } static int adv7842_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct adv7842_state *state; + static const struct v4l2_dv_timings cea640x480 = + V4L2_DV_BT_CEA_640X480P59_94; struct adv7842_platform_data *pdata = client->dev.platform_data; struct v4l2_ctrl_handler *hdl; struct v4l2_subdev *sd; @@ -2775,13 +2963,17 @@ static int adv7842_probe(struct i2c_client *client, return -ENOMEM; } + /* platform data */ + state->pdata = *pdata; + state->timings = cea640x480; + 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; + state->hdmi_port_a = pdata->input == ADV7842_SELECT_HDMI_PORT_A; + state->restart_stdi_once = true; /* i2c access to adv7842? */ rev = adv_smbus_read_byte_data_check(client, 0xea, false) << 8 | @@ -2843,21 +3035,7 @@ static int adv7842_probe(struct i2c_client *client, 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) { + if (adv7842_register_clients(sd) < 0) { err = -ENOMEM; v4l2_err(sd, "failed to create all i2c clients\n"); goto err_i2c; @@ -2879,7 +3057,7 @@ static int adv7842_probe(struct i2c_client *client, if (err) goto err_work_queues; - err = adv7842_core_init(sd, pdata); + err = adv7842_core_init(sd); if (err) goto err_entity; @@ -2893,7 +3071,7 @@ err_work_queues: cancel_delayed_work(&state->delayed_work_enable_hotplug); destroy_workqueue(state->work_queues); err_i2c: - adv7842_unregister_clients(state); + adv7842_unregister_clients(sd); err_hdl: v4l2_ctrl_handler_free(hdl); return err; @@ -2912,7 +3090,7 @@ static int adv7842_remove(struct i2c_client *client) destroy_workqueue(state->work_queues); v4l2_device_unregister_subdev(sd); media_entity_cleanup(&sd->entity); - adv7842_unregister_clients(to_state(sd)); + adv7842_unregister_clients(sd); v4l2_ctrl_handler_free(sd->ctrl_handler); return 0; } diff --git a/drivers/media/i2c/lm3560.c b/drivers/media/i2c/lm3560.c index 3317a9ae3961..d98ca3aebe23 100644 --- a/drivers/media/i2c/lm3560.c +++ b/drivers/media/i2c/lm3560.c @@ -172,28 +172,28 @@ static int lm3560_flash_brt_ctrl(struct lm3560_flash *flash, static int lm3560_get_ctrl(struct v4l2_ctrl *ctrl, enum lm3560_led_id led_no) { struct lm3560_flash *flash = to_lm3560_flash(ctrl, led_no); + int rval = -EINVAL; mutex_lock(&flash->lock); if (ctrl->id == V4L2_CID_FLASH_FAULT) { - int rval; s32 fault = 0; unsigned int reg_val; rval = regmap_read(flash->regmap, REG_FLAG, ®_val); if (rval < 0) - return rval; - if (rval & FAULT_SHORT_CIRCUIT) + goto out; + if (reg_val & FAULT_SHORT_CIRCUIT) fault |= V4L2_FLASH_FAULT_SHORT_CIRCUIT; - if (rval & FAULT_OVERTEMP) + if (reg_val & FAULT_OVERTEMP) fault |= V4L2_FLASH_FAULT_OVER_TEMPERATURE; - if (rval & FAULT_TIMEOUT) + if (reg_val & FAULT_TIMEOUT) fault |= V4L2_FLASH_FAULT_TIMEOUT; ctrl->cur.val = fault; - return 0; } +out: mutex_unlock(&flash->lock); - return -EINVAL; + return rval; } static int lm3560_set_ctrl(struct v4l2_ctrl *ctrl, enum lm3560_led_id led_no) @@ -219,15 +219,19 @@ static int lm3560_set_ctrl(struct v4l2_ctrl *ctrl, enum lm3560_led_id led_no) break; case V4L2_CID_FLASH_STROBE: - if (flash->led_mode != V4L2_FLASH_LED_MODE_FLASH) - return -EBUSY; + if (flash->led_mode != V4L2_FLASH_LED_MODE_FLASH) { + rval = -EBUSY; + goto err_out; + } flash->led_mode = V4L2_FLASH_LED_MODE_FLASH; rval = lm3560_mode_ctrl(flash); break; case V4L2_CID_FLASH_STROBE_STOP: - if (flash->led_mode != V4L2_FLASH_LED_MODE_FLASH) - return -EBUSY; + if (flash->led_mode != V4L2_FLASH_LED_MODE_FLASH) { + rval = -EBUSY; + goto err_out; + } flash->led_mode = V4L2_FLASH_LED_MODE_NONE; rval = lm3560_mode_ctrl(flash); break; @@ -247,8 +251,8 @@ static int lm3560_set_ctrl(struct v4l2_ctrl *ctrl, enum lm3560_led_id led_no) break; } - mutex_unlock(&flash->lock); err_out: + mutex_unlock(&flash->lock); return rval; } @@ -444,14 +448,14 @@ static int lm3560_probe(struct i2c_client *client, if (rval < 0) return rval; + i2c_set_clientdata(client, flash); + return 0; } static int lm3560_remove(struct i2c_client *client) { - struct v4l2_subdev *subdev = i2c_get_clientdata(client); - struct lm3560_flash *flash = container_of(subdev, struct lm3560_flash, - subdev_led[LM3560_LED_MAX]); + struct lm3560_flash *flash = i2c_get_clientdata(client); unsigned int i; for (i = LM3560_LED0; i < LM3560_LED_MAX; i++) { diff --git a/drivers/media/i2c/mt9m032.c b/drivers/media/i2c/mt9m032.c index 846b15f0bf64..85ec3bacdf1c 100644 --- a/drivers/media/i2c/mt9m032.c +++ b/drivers/media/i2c/mt9m032.c @@ -459,13 +459,15 @@ static int mt9m032_set_pad_crop(struct v4l2_subdev *subdev, MT9M032_COLUMN_START_MAX); rect.top = clamp(ALIGN(crop->rect.top, 2), MT9M032_ROW_START_MIN, MT9M032_ROW_START_MAX); - rect.width = clamp(ALIGN(crop->rect.width, 2), MT9M032_COLUMN_SIZE_MIN, - MT9M032_COLUMN_SIZE_MAX); - rect.height = clamp(ALIGN(crop->rect.height, 2), MT9M032_ROW_SIZE_MIN, - MT9M032_ROW_SIZE_MAX); - - rect.width = min(rect.width, MT9M032_PIXEL_ARRAY_WIDTH - rect.left); - rect.height = min(rect.height, MT9M032_PIXEL_ARRAY_HEIGHT - rect.top); + rect.width = clamp_t(unsigned int, ALIGN(crop->rect.width, 2), + MT9M032_COLUMN_SIZE_MIN, MT9M032_COLUMN_SIZE_MAX); + rect.height = clamp_t(unsigned int, ALIGN(crop->rect.height, 2), + MT9M032_ROW_SIZE_MIN, MT9M032_ROW_SIZE_MAX); + + rect.width = min_t(unsigned int, rect.width, + MT9M032_PIXEL_ARRAY_WIDTH - rect.left); + rect.height = min_t(unsigned int, rect.height, + MT9M032_PIXEL_ARRAY_HEIGHT - rect.top); __crop = __mt9m032_get_pad_crop(sensor, fh, crop->which); diff --git a/drivers/media/i2c/mt9p031.c b/drivers/media/i2c/mt9p031.c index 1c2303d18bf4..e5ddf47030fd 100644 --- a/drivers/media/i2c/mt9p031.c +++ b/drivers/media/i2c/mt9p031.c @@ -519,11 +519,13 @@ static int mt9p031_set_format(struct v4l2_subdev *subdev, /* Clamp the width and height to avoid dividing by zero. */ width = clamp_t(unsigned int, ALIGN(format->format.width, 2), - max(__crop->width / 7, MT9P031_WINDOW_WIDTH_MIN), + max_t(unsigned int, __crop->width / 7, + MT9P031_WINDOW_WIDTH_MIN), __crop->width); height = clamp_t(unsigned int, ALIGN(format->format.height, 2), - max(__crop->height / 8, MT9P031_WINDOW_HEIGHT_MIN), - __crop->height); + max_t(unsigned int, __crop->height / 8, + MT9P031_WINDOW_HEIGHT_MIN), + __crop->height); hratio = DIV_ROUND_CLOSEST(__crop->width, width); vratio = DIV_ROUND_CLOSEST(__crop->height, height); @@ -565,15 +567,17 @@ static int mt9p031_set_crop(struct v4l2_subdev *subdev, MT9P031_COLUMN_START_MAX); rect.top = clamp(ALIGN(crop->rect.top, 2), MT9P031_ROW_START_MIN, MT9P031_ROW_START_MAX); - rect.width = clamp(ALIGN(crop->rect.width, 2), - MT9P031_WINDOW_WIDTH_MIN, - MT9P031_WINDOW_WIDTH_MAX); - rect.height = clamp(ALIGN(crop->rect.height, 2), - MT9P031_WINDOW_HEIGHT_MIN, - MT9P031_WINDOW_HEIGHT_MAX); - - rect.width = min(rect.width, MT9P031_PIXEL_ARRAY_WIDTH - rect.left); - rect.height = min(rect.height, MT9P031_PIXEL_ARRAY_HEIGHT - rect.top); + rect.width = clamp_t(unsigned int, ALIGN(crop->rect.width, 2), + MT9P031_WINDOW_WIDTH_MIN, + MT9P031_WINDOW_WIDTH_MAX); + rect.height = clamp_t(unsigned int, ALIGN(crop->rect.height, 2), + MT9P031_WINDOW_HEIGHT_MIN, + MT9P031_WINDOW_HEIGHT_MAX); + + rect.width = min_t(unsigned int, rect.width, + MT9P031_PIXEL_ARRAY_WIDTH - rect.left); + rect.height = min_t(unsigned int, rect.height, + MT9P031_PIXEL_ARRAY_HEIGHT - rect.top); __crop = __mt9p031_get_pad_crop(mt9p031, fh, crop->pad, crop->which); diff --git a/drivers/media/i2c/mt9t001.c b/drivers/media/i2c/mt9t001.c index 796463466ef0..d41c70eaf838 100644 --- a/drivers/media/i2c/mt9t001.c +++ b/drivers/media/i2c/mt9t001.c @@ -291,10 +291,12 @@ static int mt9t001_set_format(struct v4l2_subdev *subdev, /* Clamp the width and height to avoid dividing by zero. */ width = clamp_t(unsigned int, ALIGN(format->format.width, 2), - max(__crop->width / 8, MT9T001_WINDOW_HEIGHT_MIN + 1), + max_t(unsigned int, __crop->width / 8, + MT9T001_WINDOW_HEIGHT_MIN + 1), __crop->width); height = clamp_t(unsigned int, ALIGN(format->format.height, 2), - max(__crop->height / 8, MT9T001_WINDOW_HEIGHT_MIN + 1), + max_t(unsigned int, __crop->height / 8, + MT9T001_WINDOW_HEIGHT_MIN + 1), __crop->height); hratio = DIV_ROUND_CLOSEST(__crop->width, width); @@ -339,15 +341,17 @@ static int mt9t001_set_crop(struct v4l2_subdev *subdev, rect.top = clamp(ALIGN(crop->rect.top, 2), MT9T001_ROW_START_MIN, MT9T001_ROW_START_MAX); - rect.width = clamp(ALIGN(crop->rect.width, 2), - MT9T001_WINDOW_WIDTH_MIN + 1, - MT9T001_WINDOW_WIDTH_MAX + 1); - rect.height = clamp(ALIGN(crop->rect.height, 2), - MT9T001_WINDOW_HEIGHT_MIN + 1, - MT9T001_WINDOW_HEIGHT_MAX + 1); - - rect.width = min(rect.width, MT9T001_PIXEL_ARRAY_WIDTH - rect.left); - rect.height = min(rect.height, MT9T001_PIXEL_ARRAY_HEIGHT - rect.top); + rect.width = clamp_t(unsigned int, ALIGN(crop->rect.width, 2), + MT9T001_WINDOW_WIDTH_MIN + 1, + MT9T001_WINDOW_WIDTH_MAX + 1); + rect.height = clamp_t(unsigned int, ALIGN(crop->rect.height, 2), + MT9T001_WINDOW_HEIGHT_MIN + 1, + MT9T001_WINDOW_HEIGHT_MAX + 1); + + rect.width = min_t(unsigned int, rect.width, + MT9T001_PIXEL_ARRAY_WIDTH - rect.left); + rect.height = min_t(unsigned int, rect.height, + MT9T001_PIXEL_ARRAY_HEIGHT - rect.top); __crop = __mt9t001_get_pad_crop(mt9t001, fh, crop->pad, crop->which); diff --git a/drivers/media/i2c/mt9v032.c b/drivers/media/i2c/mt9v032.c index 2c50effaa334..36c504b78f2c 100644 --- a/drivers/media/i2c/mt9v032.c +++ b/drivers/media/i2c/mt9v032.c @@ -27,14 +27,16 @@ #include <media/v4l2-device.h> #include <media/v4l2-subdev.h> -#define MT9V032_PIXEL_ARRAY_HEIGHT 492 -#define MT9V032_PIXEL_ARRAY_WIDTH 782 +/* The first four rows are black rows. The active area spans 753x481 pixels. */ +#define MT9V032_PIXEL_ARRAY_HEIGHT 485 +#define MT9V032_PIXEL_ARRAY_WIDTH 753 #define MT9V032_SYSCLK_FREQ_DEF 26600000 #define MT9V032_CHIP_VERSION 0x00 #define MT9V032_CHIP_ID_REV1 0x1311 #define MT9V032_CHIP_ID_REV3 0x1313 +#define MT9V034_CHIP_ID_REV1 0X1324 #define MT9V032_COLUMN_START 0x01 #define MT9V032_COLUMN_START_MIN 1 #define MT9V032_COLUMN_START_DEF 1 @@ -53,12 +55,15 @@ #define MT9V032_WINDOW_WIDTH_MAX 752 #define MT9V032_HORIZONTAL_BLANKING 0x05 #define MT9V032_HORIZONTAL_BLANKING_MIN 43 +#define MT9V034_HORIZONTAL_BLANKING_MIN 61 #define MT9V032_HORIZONTAL_BLANKING_DEF 94 #define MT9V032_HORIZONTAL_BLANKING_MAX 1023 #define MT9V032_VERTICAL_BLANKING 0x06 #define MT9V032_VERTICAL_BLANKING_MIN 4 +#define MT9V034_VERTICAL_BLANKING_MIN 2 #define MT9V032_VERTICAL_BLANKING_DEF 45 #define MT9V032_VERTICAL_BLANKING_MAX 3000 +#define MT9V034_VERTICAL_BLANKING_MAX 32288 #define MT9V032_CHIP_CONTROL 0x07 #define MT9V032_CHIP_CONTROL_MASTER_MODE (1 << 3) #define MT9V032_CHIP_CONTROL_DOUT_ENABLE (1 << 7) @@ -68,8 +73,10 @@ #define MT9V032_SHUTTER_WIDTH_CONTROL 0x0a #define MT9V032_TOTAL_SHUTTER_WIDTH 0x0b #define MT9V032_TOTAL_SHUTTER_WIDTH_MIN 1 +#define MT9V034_TOTAL_SHUTTER_WIDTH_MIN 0 #define MT9V032_TOTAL_SHUTTER_WIDTH_DEF 480 #define MT9V032_TOTAL_SHUTTER_WIDTH_MAX 32767 +#define MT9V034_TOTAL_SHUTTER_WIDTH_MAX 32765 #define MT9V032_RESET 0x0c #define MT9V032_READ_MODE 0x0d #define MT9V032_READ_MODE_ROW_BIN_MASK (3 << 0) @@ -81,6 +88,8 @@ #define MT9V032_READ_MODE_DARK_COLUMNS (1 << 6) #define MT9V032_READ_MODE_DARK_ROWS (1 << 7) #define MT9V032_PIXEL_OPERATION_MODE 0x0f +#define MT9V034_PIXEL_OPERATION_MODE_HDR (1 << 0) +#define MT9V034_PIXEL_OPERATION_MODE_COLOR (1 << 1) #define MT9V032_PIXEL_OPERATION_MODE_COLOR (1 << 2) #define MT9V032_PIXEL_OPERATION_MODE_HDR (1 << 6) #define MT9V032_ANALOG_GAIN 0x35 @@ -96,9 +105,12 @@ #define MT9V032_DARK_AVG_HIGH_THRESH_MASK (255 << 8) #define MT9V032_DARK_AVG_HIGH_THRESH_SHIFT 8 #define MT9V032_ROW_NOISE_CORR_CONTROL 0x70 +#define MT9V034_ROW_NOISE_CORR_ENABLE (1 << 0) +#define MT9V034_ROW_NOISE_CORR_USE_BLK_AVG (1 << 1) #define MT9V032_ROW_NOISE_CORR_ENABLE (1 << 5) #define MT9V032_ROW_NOISE_CORR_USE_BLK_AVG (1 << 7) #define MT9V032_PIXEL_CLOCK 0x74 +#define MT9V034_PIXEL_CLOCK 0x72 #define MT9V032_PIXEL_CLOCK_INV_LINE (1 << 0) #define MT9V032_PIXEL_CLOCK_INV_FRAME (1 << 1) #define MT9V032_PIXEL_CLOCK_XOR_LINE (1 << 2) @@ -120,12 +132,88 @@ #define MT9V032_AGC_ENABLE (1 << 1) #define MT9V032_THERMAL_INFO 0xc1 +enum mt9v032_model { + MT9V032_MODEL_V032_COLOR, + MT9V032_MODEL_V032_MONO, + MT9V032_MODEL_V034_COLOR, + MT9V032_MODEL_V034_MONO, +}; + +struct mt9v032_model_version { + unsigned int version; + const char *name; +}; + +struct mt9v032_model_data { + unsigned int min_row_time; + unsigned int min_hblank; + unsigned int min_vblank; + unsigned int max_vblank; + unsigned int min_shutter; + unsigned int max_shutter; + unsigned int pclk_reg; +}; + +struct mt9v032_model_info { + const struct mt9v032_model_data *data; + bool color; +}; + +static const struct mt9v032_model_version mt9v032_versions[] = { + { MT9V032_CHIP_ID_REV1, "MT9V032 rev1/2" }, + { MT9V032_CHIP_ID_REV3, "MT9V032 rev3" }, + { MT9V034_CHIP_ID_REV1, "MT9V034 rev1" }, +}; + +static const struct mt9v032_model_data mt9v032_model_data[] = { + { + /* MT9V032 revisions 1/2/3 */ + .min_row_time = 660, + .min_hblank = MT9V032_HORIZONTAL_BLANKING_MIN, + .min_vblank = MT9V032_VERTICAL_BLANKING_MIN, + .max_vblank = MT9V032_VERTICAL_BLANKING_MAX, + .min_shutter = MT9V032_TOTAL_SHUTTER_WIDTH_MIN, + .max_shutter = MT9V032_TOTAL_SHUTTER_WIDTH_MAX, + .pclk_reg = MT9V032_PIXEL_CLOCK, + }, { + /* MT9V034 */ + .min_row_time = 690, + .min_hblank = MT9V034_HORIZONTAL_BLANKING_MIN, + .min_vblank = MT9V034_VERTICAL_BLANKING_MIN, + .max_vblank = MT9V034_VERTICAL_BLANKING_MAX, + .min_shutter = MT9V034_TOTAL_SHUTTER_WIDTH_MIN, + .max_shutter = MT9V034_TOTAL_SHUTTER_WIDTH_MAX, + .pclk_reg = MT9V034_PIXEL_CLOCK, + }, +}; + +static const struct mt9v032_model_info mt9v032_models[] = { + [MT9V032_MODEL_V032_COLOR] = { + .data = &mt9v032_model_data[0], + .color = true, + }, + [MT9V032_MODEL_V032_MONO] = { + .data = &mt9v032_model_data[0], + .color = false, + }, + [MT9V032_MODEL_V034_COLOR] = { + .data = &mt9v032_model_data[1], + .color = true, + }, + [MT9V032_MODEL_V034_MONO] = { + .data = &mt9v032_model_data[1], + .color = false, + }, +}; + struct mt9v032 { struct v4l2_subdev subdev; struct media_pad pad; struct v4l2_mbus_framefmt format; struct v4l2_rect crop; + unsigned int hratio; + unsigned int vratio; struct v4l2_ctrl_handler ctrls; struct { @@ -139,6 +227,8 @@ struct mt9v032 { struct clk *clk; struct mt9v032_platform_data *pdata; + const struct mt9v032_model_info *model; + const struct mt9v032_model_version *version; u32 sysclk; u16 chip_control; @@ -210,12 +300,17 @@ mt9v032_update_hblank(struct mt9v032 *mt9v032) { struct i2c_client *client = v4l2_get_subdevdata(&mt9v032->subdev); struct v4l2_rect *crop = &mt9v032->crop; + unsigned int min_hblank = mt9v032->model->data->min_hblank; + unsigned int hblank; - return mt9v032_write(client, MT9V032_HORIZONTAL_BLANKING, - max_t(s32, mt9v032->hblank, 660 - crop->width)); -} + if (mt9v032->version->version == MT9V034_CHIP_ID_REV1) + min_hblank += (mt9v032->hratio - 1) * 10; + min_hblank = max_t(unsigned int, (int)mt9v032->model->data->min_row_time - crop->width, + (int)min_hblank); + hblank = max_t(unsigned int, mt9v032->hblank, min_hblank); -#define EXT_CLK 25000000 + return mt9v032_write(client, MT9V032_HORIZONTAL_BLANKING, hblank); +} static int mt9v032_power_on(struct mt9v032 *mt9v032) { @@ -259,7 +354,7 @@ static int __mt9v032_set_power(struct mt9v032 *mt9v032, bool on) /* Configure the pixel clock polarity */ if (mt9v032->pdata && mt9v032->pdata->clk_pol) { - ret = mt9v032_write(client, MT9V032_PIXEL_CLOCK, + ret = mt9v032_write(client, mt9v032->model->data->pclk_reg, MT9V032_PIXEL_CLOCK_INV_PXL_CLK); if (ret < 0) return ret; @@ -312,22 +407,20 @@ static int mt9v032_s_stream(struct v4l2_subdev *subdev, int enable) | MT9V032_CHIP_CONTROL_SEQUENTIAL; struct i2c_client *client = v4l2_get_subdevdata(subdev); struct mt9v032 *mt9v032 = to_mt9v032(subdev); - struct v4l2_mbus_framefmt *format = &mt9v032->format; struct v4l2_rect *crop = &mt9v032->crop; - unsigned int hratio; - unsigned int vratio; + unsigned int hbin; + unsigned int vbin; int ret; if (!enable) return mt9v032_set_chip_control(mt9v032, mode, 0); /* Configure the window size and row/column bin */ - hratio = DIV_ROUND_CLOSEST(crop->width, format->width); - vratio = DIV_ROUND_CLOSEST(crop->height, format->height); - + hbin = fls(mt9v032->hratio) - 1; + vbin = fls(mt9v032->vratio) - 1; ret = mt9v032_write(client, MT9V032_READ_MODE, - (hratio - 1) << MT9V032_READ_MODE_ROW_BIN_SHIFT | - (vratio - 1) << MT9V032_READ_MODE_COLUMN_BIN_SHIFT); + hbin << MT9V032_READ_MODE_COLUMN_BIN_SHIFT | + vbin << MT9V032_READ_MODE_ROW_BIN_SHIFT); if (ret < 0) return ret; @@ -370,12 +463,12 @@ static int mt9v032_enum_frame_size(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, struct v4l2_subdev_frame_size_enum *fse) { - if (fse->index >= 8 || fse->code != V4L2_MBUS_FMT_SGRBG10_1X10) + if (fse->index >= 3 || fse->code != V4L2_MBUS_FMT_SGRBG10_1X10) return -EINVAL; - fse->min_width = MT9V032_WINDOW_WIDTH_DEF / fse->index; + fse->min_width = MT9V032_WINDOW_WIDTH_DEF / (1 << fse->index); fse->max_width = fse->min_width; - fse->min_height = MT9V032_WINDOW_HEIGHT_DEF / fse->index; + fse->min_height = MT9V032_WINDOW_HEIGHT_DEF / (1 << fse->index); fse->max_height = fse->min_height; return 0; @@ -392,18 +485,30 @@ static int mt9v032_get_format(struct v4l2_subdev *subdev, return 0; } -static void mt9v032_configure_pixel_rate(struct mt9v032 *mt9v032, - unsigned int hratio) +static void mt9v032_configure_pixel_rate(struct mt9v032 *mt9v032) { struct i2c_client *client = v4l2_get_subdevdata(&mt9v032->subdev); int ret; ret = v4l2_ctrl_s_ctrl_int64(mt9v032->pixel_rate, - mt9v032->sysclk / hratio); + mt9v032->sysclk / mt9v032->hratio); if (ret < 0) dev_warn(&client->dev, "failed to set pixel rate (%d)\n", ret); } +static unsigned int mt9v032_calc_ratio(unsigned int input, unsigned int output) +{ + /* Compute the power-of-two binning factor closest to the input size to + * output size ratio. Given that the output size is bounded by input/4 + * and input, a generic implementation would be an ineffective luxury. + */ + if (output * 3 > input * 2) + return 1; + if (output * 3 > input) + return 2; + return 4; +} + static int mt9v032_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, struct v4l2_subdev_format *format) @@ -420,22 +525,28 @@ static int mt9v032_set_format(struct v4l2_subdev *subdev, format->which); /* Clamp the width and height to avoid dividing by zero. */ - width = clamp_t(unsigned int, ALIGN(format->format.width, 2), - max(__crop->width / 8, MT9V032_WINDOW_WIDTH_MIN), - __crop->width); - height = clamp_t(unsigned int, ALIGN(format->format.height, 2), - max(__crop->height / 8, MT9V032_WINDOW_HEIGHT_MIN), - __crop->height); - - hratio = DIV_ROUND_CLOSEST(__crop->width, width); - vratio = DIV_ROUND_CLOSEST(__crop->height, height); + width = clamp(ALIGN(format->format.width, 2), + max_t(unsigned int, __crop->width / 4, + MT9V032_WINDOW_WIDTH_MIN), + __crop->width); + height = clamp(ALIGN(format->format.height, 2), + max_t(unsigned int, __crop->height / 4, + MT9V032_WINDOW_HEIGHT_MIN), + __crop->height); + + hratio = mt9v032_calc_ratio(__crop->width, width); + vratio = mt9v032_calc_ratio(__crop->height, height); __format = __mt9v032_get_pad_format(mt9v032, fh, format->pad, format->which); __format->width = __crop->width / hratio; __format->height = __crop->height / vratio; - if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) - mt9v032_configure_pixel_rate(mt9v032, hratio); + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + mt9v032->hratio = hratio; + mt9v032->vratio = vratio; + mt9v032_configure_pixel_rate(mt9v032); + } format->format = *__format; @@ -471,15 +582,17 @@ static int mt9v032_set_crop(struct v4l2_subdev *subdev, rect.top = clamp(ALIGN(crop->rect.top + 1, 2) - 1, MT9V032_ROW_START_MIN, MT9V032_ROW_START_MAX); - rect.width = clamp(ALIGN(crop->rect.width, 2), - MT9V032_WINDOW_WIDTH_MIN, - MT9V032_WINDOW_WIDTH_MAX); - rect.height = clamp(ALIGN(crop->rect.height, 2), - MT9V032_WINDOW_HEIGHT_MIN, - MT9V032_WINDOW_HEIGHT_MAX); - - rect.width = min(rect.width, MT9V032_PIXEL_ARRAY_WIDTH - rect.left); - rect.height = min(rect.height, MT9V032_PIXEL_ARRAY_HEIGHT - rect.top); + rect.width = clamp_t(unsigned int, ALIGN(crop->rect.width, 2), + MT9V032_WINDOW_WIDTH_MIN, + MT9V032_WINDOW_WIDTH_MAX); + rect.height = clamp_t(unsigned int, ALIGN(crop->rect.height, 2), + MT9V032_WINDOW_HEIGHT_MIN, + MT9V032_WINDOW_HEIGHT_MAX); + + rect.width = min_t(unsigned int, + rect.width, MT9V032_PIXEL_ARRAY_WIDTH - rect.left); + rect.height = min_t(unsigned int, + rect.height, MT9V032_PIXEL_ARRAY_HEIGHT - rect.top); __crop = __mt9v032_get_pad_crop(mt9v032, fh, crop->pad, crop->which); @@ -491,8 +604,11 @@ static int mt9v032_set_crop(struct v4l2_subdev *subdev, crop->which); __format->width = rect.width; __format->height = rect.height; - if (crop->which == V4L2_SUBDEV_FORMAT_ACTIVE) - mt9v032_configure_pixel_rate(mt9v032, 1); + if (crop->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + mt9v032->hratio = 1; + mt9v032->vratio = 1; + mt9v032_configure_pixel_rate(mt9v032); + } } *__crop = rect; @@ -641,7 +757,8 @@ static int mt9v032_registered(struct v4l2_subdev *subdev) { struct i2c_client *client = v4l2_get_subdevdata(subdev); struct mt9v032 *mt9v032 = to_mt9v032(subdev); - s32 data; + unsigned int i; + s32 version; int ret; dev_info(&client->dev, "Probing MT9V032 at address 0x%02x\n", @@ -654,25 +771,38 @@ static int mt9v032_registered(struct v4l2_subdev *subdev) } /* Read and check the sensor version */ - data = mt9v032_read(client, MT9V032_CHIP_VERSION); - if (data != MT9V032_CHIP_ID_REV1 && data != MT9V032_CHIP_ID_REV3) { - dev_err(&client->dev, "MT9V032 not detected, wrong version " - "0x%04x\n", data); + version = mt9v032_read(client, MT9V032_CHIP_VERSION); + if (version < 0) { + dev_err(&client->dev, "Failed reading chip version\n"); + return version; + } + + for (i = 0; i < ARRAY_SIZE(mt9v032_versions); ++i) { + if (mt9v032_versions[i].version == version) { + mt9v032->version = &mt9v032_versions[i]; + break; + } + } + + if (mt9v032->version == NULL) { + dev_err(&client->dev, "Unsupported chip version 0x%04x\n", + version); return -ENODEV; } mt9v032_power_off(mt9v032); - dev_info(&client->dev, "MT9V032 detected at address 0x%02x\n", - client->addr); + dev_info(&client->dev, "%s detected at address 0x%02x\n", + mt9v032->version->name, client->addr); - mt9v032_configure_pixel_rate(mt9v032, 1); + mt9v032_configure_pixel_rate(mt9v032); return ret; } static int mt9v032_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) { + struct mt9v032 *mt9v032 = to_mt9v032(subdev); struct v4l2_mbus_framefmt *format; struct v4l2_rect *crop; @@ -683,7 +813,12 @@ static int mt9v032_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) crop->height = MT9V032_WINDOW_HEIGHT_DEF; format = v4l2_subdev_get_try_format(fh, 0); - format->code = V4L2_MBUS_FMT_SGRBG10_1X10; + + if (mt9v032->model->color) + format->code = V4L2_MBUS_FMT_SGRBG10_1X10; + else + format->code = V4L2_MBUS_FMT_Y10_1X10; + format->width = MT9V032_WINDOW_WIDTH_DEF; format->height = MT9V032_WINDOW_HEIGHT_DEF; format->field = V4L2_FIELD_NONE; @@ -755,6 +890,7 @@ static int mt9v032_probe(struct i2c_client *client, mutex_init(&mt9v032->power_lock); mt9v032->pdata = pdata; + mt9v032->model = (const void *)did->driver_data; v4l2_ctrl_handler_init(&mt9v032->ctrls, 10); @@ -767,16 +903,16 @@ static int mt9v032_probe(struct i2c_client *client, V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_MANUAL, 0, V4L2_EXPOSURE_AUTO); v4l2_ctrl_new_std(&mt9v032->ctrls, &mt9v032_ctrl_ops, - V4L2_CID_EXPOSURE, MT9V032_TOTAL_SHUTTER_WIDTH_MIN, - MT9V032_TOTAL_SHUTTER_WIDTH_MAX, 1, + V4L2_CID_EXPOSURE, mt9v032->model->data->min_shutter, + mt9v032->model->data->max_shutter, 1, MT9V032_TOTAL_SHUTTER_WIDTH_DEF); v4l2_ctrl_new_std(&mt9v032->ctrls, &mt9v032_ctrl_ops, - V4L2_CID_HBLANK, MT9V032_HORIZONTAL_BLANKING_MIN, + V4L2_CID_HBLANK, mt9v032->model->data->min_hblank, MT9V032_HORIZONTAL_BLANKING_MAX, 1, MT9V032_HORIZONTAL_BLANKING_DEF); v4l2_ctrl_new_std(&mt9v032->ctrls, &mt9v032_ctrl_ops, - V4L2_CID_VBLANK, MT9V032_VERTICAL_BLANKING_MIN, - MT9V032_VERTICAL_BLANKING_MAX, 1, + V4L2_CID_VBLANK, mt9v032->model->data->min_vblank, + mt9v032->model->data->max_vblank, 1, MT9V032_VERTICAL_BLANKING_DEF); mt9v032->test_pattern = v4l2_ctrl_new_std_menu_items(&mt9v032->ctrls, &mt9v032_ctrl_ops, V4L2_CID_TEST_PATTERN, @@ -819,12 +955,19 @@ static int mt9v032_probe(struct i2c_client *client, mt9v032->crop.width = MT9V032_WINDOW_WIDTH_DEF; mt9v032->crop.height = MT9V032_WINDOW_HEIGHT_DEF; - mt9v032->format.code = V4L2_MBUS_FMT_SGRBG10_1X10; + if (mt9v032->model->color) + mt9v032->format.code = V4L2_MBUS_FMT_SGRBG10_1X10; + else + mt9v032->format.code = V4L2_MBUS_FMT_Y10_1X10; + mt9v032->format.width = MT9V032_WINDOW_WIDTH_DEF; mt9v032->format.height = MT9V032_WINDOW_HEIGHT_DEF; mt9v032->format.field = V4L2_FIELD_NONE; mt9v032->format.colorspace = V4L2_COLORSPACE_SRGB; + mt9v032->hratio = 1; + mt9v032->vratio = 1; + mt9v032->aec_agc = MT9V032_AEC_ENABLE | MT9V032_AGC_ENABLE; mt9v032->hblank = MT9V032_HORIZONTAL_BLANKING_DEF; mt9v032->sysclk = MT9V032_SYSCLK_FREQ_DEF; @@ -855,7 +998,10 @@ static int mt9v032_remove(struct i2c_client *client) } static const struct i2c_device_id mt9v032_id[] = { - { "mt9v032", 0 }, + { "mt9v032", (kernel_ulong_t)&mt9v032_models[MT9V032_MODEL_V032_COLOR] }, + { "mt9v032m", (kernel_ulong_t)&mt9v032_models[MT9V032_MODEL_V032_MONO] }, + { "mt9v034", (kernel_ulong_t)&mt9v032_models[MT9V032_MODEL_V034_COLOR] }, + { "mt9v034m", (kernel_ulong_t)&mt9v032_models[MT9V032_MODEL_V034_MONO] }, { } }; MODULE_DEVICE_TABLE(i2c, mt9v032_id); diff --git a/drivers/media/i2c/s5k5baf.c b/drivers/media/i2c/s5k5baf.c new file mode 100644 index 000000000000..4b8381111cbd --- /dev/null +++ b/drivers/media/i2c/s5k5baf.c @@ -0,0 +1,2045 @@ +/* + * Driver for Samsung S5K5BAF UXGA 1/5" 2M CMOS Image Sensor + * with embedded SoC ISP. + * + * Copyright (C) 2013, Samsung Electronics Co., Ltd. + * Andrzej Hajda <a.hajda@samsung.com> + * + * Based on S5K6AA driver authored by Sylwester Nawrocki + * Copyright (C) 2013, Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/media.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +#include <media/media-entity.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-mediabus.h> +#include <media/v4l2-of.h> + +static int debug; +module_param(debug, int, 0644); + +#define S5K5BAF_DRIVER_NAME "s5k5baf" +#define S5K5BAF_DEFAULT_MCLK_FREQ 24000000U +#define S5K5BAF_CLK_NAME "mclk" + +#define S5K5BAF_FW_FILENAME "s5k5baf-cfg.bin" +#define S5K5BAF_FW_TAG "SF00" +#define S5K5BAG_FW_TAG_LEN 2 +#define S5K5BAG_FW_MAX_COUNT 16 + +#define S5K5BAF_CIS_WIDTH 1600 +#define S5K5BAF_CIS_HEIGHT 1200 +#define S5K5BAF_WIN_WIDTH_MIN 8 +#define S5K5BAF_WIN_HEIGHT_MIN 8 +#define S5K5BAF_GAIN_RED_DEF 127 +#define S5K5BAF_GAIN_GREEN_DEF 95 +#define S5K5BAF_GAIN_BLUE_DEF 180 +/* Default number of MIPI CSI-2 data lanes used */ +#define S5K5BAF_DEF_NUM_LANES 1 + +#define AHB_MSB_ADDR_PTR 0xfcfc + +/* + * Register interface pages (the most significant word of the address) + */ +#define PAGE_IF_HW 0xd000 +#define PAGE_IF_SW 0x7000 + +/* + * H/W register Interface (PAGE_IF_HW) + */ +#define REG_SW_LOAD_COMPLETE 0x0014 +#define REG_CMDWR_PAGE 0x0028 +#define REG_CMDWR_ADDR 0x002a +#define REG_CMDRD_PAGE 0x002c +#define REG_CMDRD_ADDR 0x002e +#define REG_CMD_BUF 0x0f12 +#define REG_SET_HOST_INT 0x1000 +#define REG_CLEAR_HOST_INT 0x1030 +#define REG_PATTERN_SET 0x3100 +#define REG_PATTERN_WIDTH 0x3118 +#define REG_PATTERN_HEIGHT 0x311a +#define REG_PATTERN_PARAM 0x311c + +/* + * S/W register interface (PAGE_IF_SW) + */ + +/* Firmware revision information */ +#define REG_FW_APIVER 0x012e +#define S5K5BAF_FW_APIVER 0x0001 +#define REG_FW_REVISION 0x0130 +#define REG_FW_SENSOR_ID 0x0152 + +/* Initialization parameters */ +/* Master clock frequency in KHz */ +#define REG_I_INCLK_FREQ_L 0x01b8 +#define REG_I_INCLK_FREQ_H 0x01ba +#define MIN_MCLK_FREQ_KHZ 6000U +#define MAX_MCLK_FREQ_KHZ 48000U +#define REG_I_USE_NPVI_CLOCKS 0x01c6 +#define NPVI_CLOCKS 1 +#define REG_I_USE_NMIPI_CLOCKS 0x01c8 +#define NMIPI_CLOCKS 1 +#define REG_I_BLOCK_INTERNAL_PLL_CALC 0x01ca + +/* Clock configurations, n = 0..2. REG_I_* frequency unit is 4 kHz. */ +#define REG_I_OPCLK_4KHZ(n) ((n) * 6 + 0x01cc) +#define REG_I_MIN_OUTRATE_4KHZ(n) ((n) * 6 + 0x01ce) +#define REG_I_MAX_OUTRATE_4KHZ(n) ((n) * 6 + 0x01d0) +#define SCLK_PVI_FREQ 24000 +#define SCLK_MIPI_FREQ 48000 +#define PCLK_MIN_FREQ 6000 +#define PCLK_MAX_FREQ 48000 +#define REG_I_USE_REGS_API 0x01de +#define REG_I_INIT_PARAMS_UPDATED 0x01e0 +#define REG_I_ERROR_INFO 0x01e2 + +/* General purpose parameters */ +#define REG_USER_BRIGHTNESS 0x01e4 +#define REG_USER_CONTRAST 0x01e6 +#define REG_USER_SATURATION 0x01e8 +#define REG_USER_SHARPBLUR 0x01ea + +#define REG_G_SPEC_EFFECTS 0x01ee +#define REG_G_ENABLE_PREV 0x01f0 +#define REG_G_ENABLE_PREV_CHG 0x01f2 +#define REG_G_NEW_CFG_SYNC 0x01f8 +#define REG_G_PREVREQ_IN_WIDTH 0x01fa +#define REG_G_PREVREQ_IN_HEIGHT 0x01fc +#define REG_G_PREVREQ_IN_XOFFS 0x01fe +#define REG_G_PREVREQ_IN_YOFFS 0x0200 +#define REG_G_PREVZOOM_IN_WIDTH 0x020a +#define REG_G_PREVZOOM_IN_HEIGHT 0x020c +#define REG_G_PREVZOOM_IN_XOFFS 0x020e +#define REG_G_PREVZOOM_IN_YOFFS 0x0210 +#define REG_G_INPUTS_CHANGE_REQ 0x021a +#define REG_G_ACTIVE_PREV_CFG 0x021c +#define REG_G_PREV_CFG_CHG 0x021e +#define REG_G_PREV_OPEN_AFTER_CH 0x0220 +#define REG_G_PREV_CFG_ERROR 0x0222 +#define CFG_ERROR_RANGE 0x0b +#define REG_G_PREV_CFG_BYPASS_CHANGED 0x022a +#define REG_G_ACTUAL_P_FR_TIME 0x023a +#define REG_G_ACTUAL_P_OUT_RATE 0x023c +#define REG_G_ACTUAL_C_FR_TIME 0x023e +#define REG_G_ACTUAL_C_OUT_RATE 0x0240 + +/* Preview control section. n = 0...4. */ +#define PREG(n, x) ((n) * 0x26 + x) +#define REG_P_OUT_WIDTH(n) PREG(n, 0x0242) +#define REG_P_OUT_HEIGHT(n) PREG(n, 0x0244) +#define REG_P_FMT(n) PREG(n, 0x0246) +#define REG_P_MAX_OUT_RATE(n) PREG(n, 0x0248) +#define REG_P_MIN_OUT_RATE(n) PREG(n, 0x024a) +#define REG_P_PVI_MASK(n) PREG(n, 0x024c) +#define PVI_MASK_MIPI 0x52 +#define REG_P_CLK_INDEX(n) PREG(n, 0x024e) +#define CLK_PVI_INDEX 0 +#define CLK_MIPI_INDEX NPVI_CLOCKS +#define REG_P_FR_RATE_TYPE(n) PREG(n, 0x0250) +#define FR_RATE_DYNAMIC 0 +#define FR_RATE_FIXED 1 +#define FR_RATE_FIXED_ACCURATE 2 +#define REG_P_FR_RATE_Q_TYPE(n) PREG(n, 0x0252) +#define FR_RATE_Q_DYNAMIC 0 +#define FR_RATE_Q_BEST_FRRATE 1 /* Binning enabled */ +#define FR_RATE_Q_BEST_QUALITY 2 /* Binning disabled */ +/* Frame period in 0.1 ms units */ +#define REG_P_MAX_FR_TIME(n) PREG(n, 0x0254) +#define REG_P_MIN_FR_TIME(n) PREG(n, 0x0256) +#define S5K5BAF_MIN_FR_TIME 333 /* x100 us */ +#define S5K5BAF_MAX_FR_TIME 6500 /* x100 us */ +/* The below 5 registers are for "device correction" values */ +#define REG_P_SATURATION(n) PREG(n, 0x0258) +#define REG_P_SHARP_BLUR(n) PREG(n, 0x025a) +#define REG_P_GLAMOUR(n) PREG(n, 0x025c) +#define REG_P_COLORTEMP(n) PREG(n, 0x025e) +#define REG_P_GAMMA_INDEX(n) PREG(n, 0x0260) +#define REG_P_PREV_MIRROR(n) PREG(n, 0x0262) +#define REG_P_CAP_MIRROR(n) PREG(n, 0x0264) +#define REG_P_CAP_ROTATION(n) PREG(n, 0x0266) + +/* Extended image property controls */ +/* Exposure time in 10 us units */ +#define REG_SF_USR_EXPOSURE_L 0x03bc +#define REG_SF_USR_EXPOSURE_H 0x03be +#define REG_SF_USR_EXPOSURE_CHG 0x03c0 +#define REG_SF_USR_TOT_GAIN 0x03c2 +#define REG_SF_USR_TOT_GAIN_CHG 0x03c4 +#define REG_SF_RGAIN 0x03c6 +#define REG_SF_RGAIN_CHG 0x03c8 +#define REG_SF_GGAIN 0x03ca +#define REG_SF_GGAIN_CHG 0x03cc +#define REG_SF_BGAIN 0x03ce +#define REG_SF_BGAIN_CHG 0x03d0 +#define REG_SF_WBGAIN_CHG 0x03d2 +#define REG_SF_FLICKER_QUANT 0x03d4 +#define REG_SF_FLICKER_QUANT_CHG 0x03d6 + +/* Output interface (parallel/MIPI) setup */ +#define REG_OIF_EN_MIPI_LANES 0x03f2 +#define REG_OIF_EN_PACKETS 0x03f4 +#define EN_PACKETS_CSI2 0xc3 +#define REG_OIF_CFG_CHG 0x03f6 + +/* Auto-algorithms enable mask */ +#define REG_DBG_AUTOALG_EN 0x03f8 +#define AALG_ALL_EN BIT(0) +#define AALG_AE_EN BIT(1) +#define AALG_DIVLEI_EN BIT(2) +#define AALG_WB_EN BIT(3) +#define AALG_USE_WB_FOR_ISP BIT(4) +#define AALG_FLICKER_EN BIT(5) +#define AALG_FIT_EN BIT(6) +#define AALG_WRHW_EN BIT(7) + +/* Pointers to color correction matrices */ +#define REG_PTR_CCM_HORIZON 0x06d0 +#define REG_PTR_CCM_INCANDESCENT 0x06d4 +#define REG_PTR_CCM_WARM_WHITE 0x06d8 +#define REG_PTR_CCM_COOL_WHITE 0x06dc +#define REG_PTR_CCM_DL50 0x06e0 +#define REG_PTR_CCM_DL65 0x06e4 +#define REG_PTR_CCM_OUTDOOR 0x06ec + +#define REG_ARR_CCM(n) (0x2800 + 36 * (n)) + +static const char * const s5k5baf_supply_names[] = { + "vdda", /* Analog power supply 2.8V (2.6V to 3.0V) */ + "vddreg", /* Regulator input power supply 1.8V (1.7V to 1.9V) + or 2.8V (2.6V to 3.0) */ + "vddio", /* I/O power supply 1.8V (1.65V to 1.95V) + or 2.8V (2.5V to 3.1V) */ +}; +#define S5K5BAF_NUM_SUPPLIES ARRAY_SIZE(s5k5baf_supply_names) + +struct s5k5baf_gpio { + int gpio; + int level; +}; + +enum s5k5baf_gpio_id { + STBY, + RST, + NUM_GPIOS, +}; + +#define PAD_CIS 0 +#define PAD_OUT 1 +#define NUM_CIS_PADS 1 +#define NUM_ISP_PADS 2 + +struct s5k5baf_pixfmt { + enum v4l2_mbus_pixelcode code; + u32 colorspace; + /* REG_P_FMT(x) register value */ + u16 reg_p_fmt; +}; + +struct s5k5baf_ctrls { + struct v4l2_ctrl_handler handler; + struct { /* Auto / manual white balance cluster */ + struct v4l2_ctrl *awb; + struct v4l2_ctrl *gain_red; + struct v4l2_ctrl *gain_blue; + }; + struct { /* Mirror cluster */ + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + }; + struct { /* Auto exposure / manual exposure and gain cluster */ + struct v4l2_ctrl *auto_exp; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *gain; + }; +}; + +enum { + S5K5BAF_FW_ID_PATCH, + S5K5BAF_FW_ID_CCM, + S5K5BAF_FW_ID_CIS, +}; + +struct s5k5baf_fw { + u16 count; + struct { + u16 id; + u16 offset; + } seq[0]; + u16 data[0]; +}; + +struct s5k5baf { + struct s5k5baf_gpio gpios[NUM_GPIOS]; + enum v4l2_mbus_type bus_type; + u8 nlanes; + struct regulator_bulk_data supplies[S5K5BAF_NUM_SUPPLIES]; + + struct clk *clock; + u32 mclk_frequency; + + struct s5k5baf_fw *fw; + + struct v4l2_subdev cis_sd; + struct media_pad cis_pad; + + struct v4l2_subdev sd; + struct media_pad pads[NUM_ISP_PADS]; + + /* protects the struct members below */ + struct mutex lock; + + int error; + + struct v4l2_rect crop_sink; + struct v4l2_rect compose; + struct v4l2_rect crop_source; + /* index to s5k5baf_formats array */ + int pixfmt; + /* actual frame interval in 100us */ + u16 fiv; + /* requested frame interval in 100us */ + u16 req_fiv; + /* cache for REG_DBG_AUTOALG_EN register */ + u16 auto_alg; + + struct s5k5baf_ctrls ctrls; + + unsigned int streaming:1; + unsigned int apply_cfg:1; + unsigned int apply_crop:1; + unsigned int valid_auto_alg:1; + unsigned int power; +}; + +static const struct s5k5baf_pixfmt s5k5baf_formats[] = { + { V4L2_MBUS_FMT_VYUY8_2X8, V4L2_COLORSPACE_JPEG, 5 }, + /* range 16-240 */ + { V4L2_MBUS_FMT_VYUY8_2X8, V4L2_COLORSPACE_REC709, 6 }, + { V4L2_MBUS_FMT_RGB565_2X8_BE, V4L2_COLORSPACE_JPEG, 0 }, +}; + +static struct v4l2_rect s5k5baf_cis_rect = { + 0, 0, S5K5BAF_CIS_WIDTH, S5K5BAF_CIS_HEIGHT +}; + +/* Setfile contains set of I2C command sequences. Each sequence has its ID. + * setfile format: + * u8 magic[4]; + * u16 count; number of sequences + * struct { + * u16 id; sequence id + * u16 offset; sequence offset in data array + * } seq[count]; + * u16 data[*]; array containing sequences + * + */ +static int s5k5baf_fw_parse(struct device *dev, struct s5k5baf_fw **fw, + size_t count, const u16 *data) +{ + struct s5k5baf_fw *f; + u16 *d, i, *end; + int ret; + + if (count < S5K5BAG_FW_TAG_LEN + 1) { + dev_err(dev, "firmware file too short (%zu)\n", count); + return -EINVAL; + } + + ret = memcmp(data, S5K5BAF_FW_TAG, S5K5BAG_FW_TAG_LEN * sizeof(u16)); + if (ret != 0) { + dev_err(dev, "invalid firmware magic number\n"); + return -EINVAL; + } + + data += S5K5BAG_FW_TAG_LEN; + count -= S5K5BAG_FW_TAG_LEN; + + d = devm_kzalloc(dev, count * sizeof(u16), GFP_KERNEL); + + for (i = 0; i < count; ++i) + d[i] = le16_to_cpu(data[i]); + + f = (struct s5k5baf_fw *)d; + if (count < 1 + 2 * f->count) { + dev_err(dev, "invalid firmware header (count=%d size=%zu)\n", + f->count, 2 * (count + S5K5BAG_FW_TAG_LEN)); + return -EINVAL; + } + end = d + count; + d += 1 + 2 * f->count; + + for (i = 0; i < f->count; ++i) { + if (f->seq[i].offset + d <= end) + continue; + dev_err(dev, "invalid firmware header (seq=%d)\n", i); + return -EINVAL; + } + + *fw = f; + + return 0; +} + +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct s5k5baf, ctrls.handler)->sd; +} + +static inline bool s5k5baf_is_cis_subdev(struct v4l2_subdev *sd) +{ + return sd->entity.type == MEDIA_ENT_T_V4L2_SUBDEV_SENSOR; +} + +static inline struct s5k5baf *to_s5k5baf(struct v4l2_subdev *sd) +{ + if (s5k5baf_is_cis_subdev(sd)) + return container_of(sd, struct s5k5baf, cis_sd); + else + return container_of(sd, struct s5k5baf, sd); +} + +static u16 s5k5baf_i2c_read(struct s5k5baf *state, u16 addr) +{ + struct i2c_client *c = v4l2_get_subdevdata(&state->sd); + __be16 w, r; + struct i2c_msg msg[] = { + { .addr = c->addr, .flags = 0, + .len = 2, .buf = (u8 *)&w }, + { .addr = c->addr, .flags = I2C_M_RD, + .len = 2, .buf = (u8 *)&r }, + }; + int ret; + + if (state->error) + return 0; + + w = cpu_to_be16(addr); + ret = i2c_transfer(c->adapter, msg, 2); + r = be16_to_cpu(r); + + v4l2_dbg(3, debug, c, "i2c_read: 0x%04x : 0x%04x\n", addr, r); + + if (ret != 2) { + v4l2_err(c, "i2c_read: error during transfer (%d)\n", ret); + state->error = ret; + } + return r; +} + +static void s5k5baf_i2c_write(struct s5k5baf *state, u16 addr, u16 val) +{ + u8 buf[4] = { addr >> 8, addr & 0xFF, val >> 8, val & 0xFF }; + struct i2c_client *c = v4l2_get_subdevdata(&state->sd); + int ret; + + if (state->error) + return; + + ret = i2c_master_send(c, buf, 4); + v4l2_dbg(3, debug, c, "i2c_write: 0x%04x : 0x%04x\n", addr, val); + + if (ret != 4) { + v4l2_err(c, "i2c_write: error during transfer (%d)\n", ret); + state->error = ret; + } +} + +static u16 s5k5baf_read(struct s5k5baf *state, u16 addr) +{ + s5k5baf_i2c_write(state, REG_CMDRD_ADDR, addr); + return s5k5baf_i2c_read(state, REG_CMD_BUF); +} + +static void s5k5baf_write(struct s5k5baf *state, u16 addr, u16 val) +{ + s5k5baf_i2c_write(state, REG_CMDWR_ADDR, addr); + s5k5baf_i2c_write(state, REG_CMD_BUF, val); +} + +static void s5k5baf_write_arr_seq(struct s5k5baf *state, u16 addr, + u16 count, const u16 *seq) +{ + struct i2c_client *c = v4l2_get_subdevdata(&state->sd); + __be16 buf[count + 1]; + int ret, n; + + s5k5baf_i2c_write(state, REG_CMDWR_ADDR, addr); + if (state->error) + return; + + buf[0] = __constant_cpu_to_be16(REG_CMD_BUF); + for (n = 1; n <= count; ++n) + buf[n] = cpu_to_be16(*seq++); + + n *= 2; + ret = i2c_master_send(c, (char *)buf, n); + v4l2_dbg(3, debug, c, "i2c_write_seq(count=%d): %*ph\n", count, + min(2 * count, 64), seq - count); + + if (ret != n) { + v4l2_err(c, "i2c_write_seq: error during transfer (%d)\n", ret); + state->error = ret; + } +} + +#define s5k5baf_write_seq(state, addr, seq...) \ + s5k5baf_write_arr_seq(state, addr, sizeof((char[]){ seq }), \ + (const u16 []){ seq }); + +/* add items count at the beginning of the list */ +#define NSEQ(seq...) sizeof((char[]){ seq }), seq + +/* + * s5k5baf_write_nseq() - Writes sequences of values to sensor memory via i2c + * @nseq: sequence of u16 words in format: + * (N, address, value[1]...value[N-1])*,0 + * Ex.: + * u16 seq[] = { NSEQ(0x4000, 1, 1), NSEQ(0x4010, 640, 480), 0 }; + * ret = s5k5baf_write_nseq(c, seq); + */ +static void s5k5baf_write_nseq(struct s5k5baf *state, const u16 *nseq) +{ + int count; + + while ((count = *nseq++)) { + u16 addr = *nseq++; + --count; + + s5k5baf_write_arr_seq(state, addr, count, nseq); + nseq += count; + } +} + +static void s5k5baf_synchronize(struct s5k5baf *state, int timeout, u16 addr) +{ + unsigned long end = jiffies + msecs_to_jiffies(timeout); + u16 reg; + + s5k5baf_write(state, addr, 1); + do { + reg = s5k5baf_read(state, addr); + if (state->error || !reg) + return; + usleep_range(5000, 10000); + } while (time_is_after_jiffies(end)); + + v4l2_err(&state->sd, "timeout on register synchronize (%#x)\n", addr); + state->error = -ETIMEDOUT; +} + +static u16 *s5k5baf_fw_get_seq(struct s5k5baf *state, u16 seq_id) +{ + struct s5k5baf_fw *fw = state->fw; + u16 *data; + int i; + + if (fw == NULL) + return NULL; + + data = fw->data + 2 * fw->count; + + for (i = 0; i < fw->count; ++i) { + if (fw->seq[i].id == seq_id) + return data + fw->seq[i].offset; + } + + return NULL; +} + +static void s5k5baf_hw_patch(struct s5k5baf *state) +{ + u16 *seq = s5k5baf_fw_get_seq(state, S5K5BAF_FW_ID_PATCH); + + if (seq) + s5k5baf_write_nseq(state, seq); +} + +static void s5k5baf_hw_set_clocks(struct s5k5baf *state) +{ + unsigned long mclk = state->mclk_frequency / 1000; + u16 status; + static const u16 nseq_clk_cfg[] = { + NSEQ(REG_I_USE_NPVI_CLOCKS, + NPVI_CLOCKS, NMIPI_CLOCKS, 0, + SCLK_PVI_FREQ / 4, PCLK_MIN_FREQ / 4, PCLK_MAX_FREQ / 4, + SCLK_MIPI_FREQ / 4, PCLK_MIN_FREQ / 4, PCLK_MAX_FREQ / 4), + NSEQ(REG_I_USE_REGS_API, 1), + 0 + }; + + s5k5baf_write_seq(state, REG_I_INCLK_FREQ_L, mclk & 0xffff, mclk >> 16); + s5k5baf_write_nseq(state, nseq_clk_cfg); + + s5k5baf_synchronize(state, 250, REG_I_INIT_PARAMS_UPDATED); + status = s5k5baf_read(state, REG_I_ERROR_INFO); + if (!state->error && status) { + v4l2_err(&state->sd, "error configuring PLL (%d)\n", status); + state->error = -EINVAL; + } +} + +/* set custom color correction matrices for various illuminations */ +static void s5k5baf_hw_set_ccm(struct s5k5baf *state) +{ + u16 *seq = s5k5baf_fw_get_seq(state, S5K5BAF_FW_ID_CCM); + + if (seq) + s5k5baf_write_nseq(state, seq); +} + +/* CIS sensor tuning, based on undocumented android driver code */ +static void s5k5baf_hw_set_cis(struct s5k5baf *state) +{ + u16 *seq = s5k5baf_fw_get_seq(state, S5K5BAF_FW_ID_CIS); + + if (!seq) + return; + + s5k5baf_i2c_write(state, REG_CMDWR_PAGE, PAGE_IF_HW); + s5k5baf_write_nseq(state, seq); + s5k5baf_i2c_write(state, REG_CMDWR_PAGE, PAGE_IF_SW); +} + +static void s5k5baf_hw_sync_cfg(struct s5k5baf *state) +{ + s5k5baf_write(state, REG_G_PREV_CFG_CHG, 1); + if (state->apply_crop) { + s5k5baf_write(state, REG_G_INPUTS_CHANGE_REQ, 1); + s5k5baf_write(state, REG_G_PREV_CFG_BYPASS_CHANGED, 1); + } + s5k5baf_synchronize(state, 500, REG_G_NEW_CFG_SYNC); +} +/* Set horizontal and vertical image flipping */ +static void s5k5baf_hw_set_mirror(struct s5k5baf *state) +{ + u16 flip = state->ctrls.vflip->val | (state->ctrls.vflip->val << 1); + + s5k5baf_write(state, REG_P_PREV_MIRROR(0), flip); + if (state->streaming) + s5k5baf_hw_sync_cfg(state); +} + +static void s5k5baf_hw_set_alg(struct s5k5baf *state, u16 alg, bool enable) +{ + u16 cur_alg, new_alg; + + if (!state->valid_auto_alg) + cur_alg = s5k5baf_read(state, REG_DBG_AUTOALG_EN); + else + cur_alg = state->auto_alg; + + new_alg = enable ? (cur_alg | alg) : (cur_alg & ~alg); + + if (new_alg != cur_alg) + s5k5baf_write(state, REG_DBG_AUTOALG_EN, new_alg); + + if (state->error) + return; + + state->valid_auto_alg = 1; + state->auto_alg = new_alg; +} + +/* Configure auto/manual white balance and R/G/B gains */ +static void s5k5baf_hw_set_awb(struct s5k5baf *state, int awb) +{ + struct s5k5baf_ctrls *ctrls = &state->ctrls; + + if (!awb) + s5k5baf_write_seq(state, REG_SF_RGAIN, + ctrls->gain_red->val, 1, + S5K5BAF_GAIN_GREEN_DEF, 1, + ctrls->gain_blue->val, 1, + 1); + + s5k5baf_hw_set_alg(state, AALG_WB_EN, awb); +} + +/* Program FW with exposure time, 'exposure' in us units */ +static void s5k5baf_hw_set_user_exposure(struct s5k5baf *state, int exposure) +{ + unsigned int time = exposure / 10; + + s5k5baf_write_seq(state, REG_SF_USR_EXPOSURE_L, + time & 0xffff, time >> 16, 1); +} + +static void s5k5baf_hw_set_user_gain(struct s5k5baf *state, int gain) +{ + s5k5baf_write_seq(state, REG_SF_USR_TOT_GAIN, gain, 1); +} + +/* Set auto/manual exposure and total gain */ +static void s5k5baf_hw_set_auto_exposure(struct s5k5baf *state, int value) +{ + if (value == V4L2_EXPOSURE_AUTO) { + s5k5baf_hw_set_alg(state, AALG_AE_EN | AALG_DIVLEI_EN, true); + } else { + unsigned int exp_time = state->ctrls.exposure->val; + + s5k5baf_hw_set_user_exposure(state, exp_time); + s5k5baf_hw_set_user_gain(state, state->ctrls.gain->val); + s5k5baf_hw_set_alg(state, AALG_AE_EN | AALG_DIVLEI_EN, false); + } +} + +static void s5k5baf_hw_set_anti_flicker(struct s5k5baf *state, int v) +{ + if (v == V4L2_CID_POWER_LINE_FREQUENCY_AUTO) { + s5k5baf_hw_set_alg(state, AALG_FLICKER_EN, true); + } else { + /* The V4L2_CID_LINE_FREQUENCY control values match + * the register values */ + s5k5baf_write_seq(state, REG_SF_FLICKER_QUANT, v, 1); + s5k5baf_hw_set_alg(state, AALG_FLICKER_EN, false); + } +} + +static void s5k5baf_hw_set_colorfx(struct s5k5baf *state, int val) +{ + static const u16 colorfx[] = { + [V4L2_COLORFX_NONE] = 0, + [V4L2_COLORFX_BW] = 1, + [V4L2_COLORFX_NEGATIVE] = 2, + [V4L2_COLORFX_SEPIA] = 3, + [V4L2_COLORFX_SKY_BLUE] = 4, + [V4L2_COLORFX_SKETCH] = 5, + }; + + s5k5baf_write(state, REG_G_SPEC_EFFECTS, colorfx[val]); +} + +static int s5k5baf_find_pixfmt(struct v4l2_mbus_framefmt *mf) +{ + int i, c = -1; + + for (i = 0; i < ARRAY_SIZE(s5k5baf_formats); i++) { + if (mf->colorspace != s5k5baf_formats[i].colorspace) + continue; + if (mf->code == s5k5baf_formats[i].code) + return i; + if (c < 0) + c = i; + } + return (c < 0) ? 0 : c; +} + +static int s5k5baf_clear_error(struct s5k5baf *state) +{ + int ret = state->error; + + state->error = 0; + return ret; +} + +static int s5k5baf_hw_set_video_bus(struct s5k5baf *state) +{ + u16 en_pkts; + + if (state->bus_type == V4L2_MBUS_CSI2) + en_pkts = EN_PACKETS_CSI2; + else + en_pkts = 0; + + s5k5baf_write_seq(state, REG_OIF_EN_MIPI_LANES, + state->nlanes, en_pkts, 1); + + return s5k5baf_clear_error(state); +} + +static u16 s5k5baf_get_cfg_error(struct s5k5baf *state) +{ + u16 err = s5k5baf_read(state, REG_G_PREV_CFG_ERROR); + if (err) + s5k5baf_write(state, REG_G_PREV_CFG_ERROR, 0); + return err; +} + +static void s5k5baf_hw_set_fiv(struct s5k5baf *state, u16 fiv) +{ + s5k5baf_write(state, REG_P_MAX_FR_TIME(0), fiv); + s5k5baf_hw_sync_cfg(state); +} + +static void s5k5baf_hw_find_min_fiv(struct s5k5baf *state) +{ + u16 err, fiv; + int n; + + fiv = s5k5baf_read(state, REG_G_ACTUAL_P_FR_TIME); + if (state->error) + return; + + for (n = 5; n > 0; --n) { + s5k5baf_hw_set_fiv(state, fiv); + err = s5k5baf_get_cfg_error(state); + if (state->error) + return; + switch (err) { + case CFG_ERROR_RANGE: + ++fiv; + break; + case 0: + state->fiv = fiv; + v4l2_info(&state->sd, + "found valid frame interval: %d00us\n", fiv); + return; + default: + v4l2_err(&state->sd, + "error setting frame interval: %d\n", err); + state->error = -EINVAL; + } + }; + v4l2_err(&state->sd, "cannot find correct frame interval\n"); + state->error = -ERANGE; +} + +static void s5k5baf_hw_validate_cfg(struct s5k5baf *state) +{ + u16 err; + + err = s5k5baf_get_cfg_error(state); + if (state->error) + return; + + switch (err) { + case 0: + state->apply_cfg = 1; + return; + case CFG_ERROR_RANGE: + s5k5baf_hw_find_min_fiv(state); + if (!state->error) + state->apply_cfg = 1; + return; + default: + v4l2_err(&state->sd, + "error setting format: %d\n", err); + state->error = -EINVAL; + } +} + +static void s5k5baf_rescale(struct v4l2_rect *r, const struct v4l2_rect *v, + const struct v4l2_rect *n, + const struct v4l2_rect *d) +{ + r->left = v->left * n->width / d->width; + r->top = v->top * n->height / d->height; + r->width = v->width * n->width / d->width; + r->height = v->height * n->height / d->height; +} + +static int s5k5baf_hw_set_crop_rects(struct s5k5baf *state) +{ + struct v4l2_rect *p, r; + u16 err; + int ret; + + p = &state->crop_sink; + s5k5baf_write_seq(state, REG_G_PREVREQ_IN_WIDTH, p->width, p->height, + p->left, p->top); + + s5k5baf_rescale(&r, &state->crop_source, &state->crop_sink, + &state->compose); + s5k5baf_write_seq(state, REG_G_PREVZOOM_IN_WIDTH, r.width, r.height, + r.left, r.top); + + s5k5baf_synchronize(state, 500, REG_G_INPUTS_CHANGE_REQ); + s5k5baf_synchronize(state, 500, REG_G_PREV_CFG_BYPASS_CHANGED); + err = s5k5baf_get_cfg_error(state); + ret = s5k5baf_clear_error(state); + if (ret < 0) + return ret; + + switch (err) { + case 0: + break; + case CFG_ERROR_RANGE: + /* retry crop with frame interval set to max */ + s5k5baf_hw_set_fiv(state, S5K5BAF_MAX_FR_TIME); + err = s5k5baf_get_cfg_error(state); + ret = s5k5baf_clear_error(state); + if (ret < 0) + return ret; + if (err) { + v4l2_err(&state->sd, + "crop error on max frame interval: %d\n", err); + state->error = -EINVAL; + } + s5k5baf_hw_set_fiv(state, state->req_fiv); + s5k5baf_hw_validate_cfg(state); + break; + default: + v4l2_err(&state->sd, "crop error: %d\n", err); + return -EINVAL; + } + + if (!state->apply_cfg) + return 0; + + p = &state->crop_source; + s5k5baf_write_seq(state, REG_P_OUT_WIDTH(0), p->width, p->height); + s5k5baf_hw_set_fiv(state, state->req_fiv); + s5k5baf_hw_validate_cfg(state); + + return s5k5baf_clear_error(state); +} + +static void s5k5baf_hw_set_config(struct s5k5baf *state) +{ + u16 reg_fmt = s5k5baf_formats[state->pixfmt].reg_p_fmt; + struct v4l2_rect *r = &state->crop_source; + + s5k5baf_write_seq(state, REG_P_OUT_WIDTH(0), + r->width, r->height, reg_fmt, + PCLK_MAX_FREQ >> 2, PCLK_MIN_FREQ >> 2, + PVI_MASK_MIPI, CLK_MIPI_INDEX, + FR_RATE_FIXED, FR_RATE_Q_DYNAMIC, + state->req_fiv, S5K5BAF_MIN_FR_TIME); + s5k5baf_hw_sync_cfg(state); + s5k5baf_hw_validate_cfg(state); +} + + +static void s5k5baf_hw_set_test_pattern(struct s5k5baf *state, int id) +{ + s5k5baf_i2c_write(state, REG_PATTERN_WIDTH, 800); + s5k5baf_i2c_write(state, REG_PATTERN_HEIGHT, 511); + s5k5baf_i2c_write(state, REG_PATTERN_PARAM, 0); + s5k5baf_i2c_write(state, REG_PATTERN_SET, id); +} + +static void s5k5baf_gpio_assert(struct s5k5baf *state, int id) +{ + struct s5k5baf_gpio *gpio = &state->gpios[id]; + + gpio_set_value(gpio->gpio, gpio->level); +} + +static void s5k5baf_gpio_deassert(struct s5k5baf *state, int id) +{ + struct s5k5baf_gpio *gpio = &state->gpios[id]; + + gpio_set_value(gpio->gpio, !gpio->level); +} + +static int s5k5baf_power_on(struct s5k5baf *state) +{ + int ret; + + ret = regulator_bulk_enable(S5K5BAF_NUM_SUPPLIES, state->supplies); + if (ret < 0) + goto err; + + ret = clk_set_rate(state->clock, state->mclk_frequency); + if (ret < 0) + goto err_reg_dis; + + ret = clk_prepare_enable(state->clock); + if (ret < 0) + goto err_reg_dis; + + v4l2_dbg(1, debug, &state->sd, "clock frequency: %ld\n", + clk_get_rate(state->clock)); + + s5k5baf_gpio_deassert(state, STBY); + usleep_range(50, 100); + s5k5baf_gpio_deassert(state, RST); + return 0; + +err_reg_dis: + regulator_bulk_disable(S5K5BAF_NUM_SUPPLIES, state->supplies); +err: + v4l2_err(&state->sd, "%s() failed (%d)\n", __func__, ret); + return ret; +} + +static int s5k5baf_power_off(struct s5k5baf *state) +{ + int ret; + + state->streaming = 0; + state->apply_cfg = 0; + state->apply_crop = 0; + + s5k5baf_gpio_assert(state, RST); + s5k5baf_gpio_assert(state, STBY); + + if (!IS_ERR(state->clock)) + clk_disable_unprepare(state->clock); + + ret = regulator_bulk_disable(S5K5BAF_NUM_SUPPLIES, + state->supplies); + if (ret < 0) + v4l2_err(&state->sd, "failed to disable regulators\n"); + + return 0; +} + +static void s5k5baf_hw_init(struct s5k5baf *state) +{ + s5k5baf_i2c_write(state, AHB_MSB_ADDR_PTR, PAGE_IF_HW); + s5k5baf_i2c_write(state, REG_CLEAR_HOST_INT, 0); + s5k5baf_i2c_write(state, REG_SW_LOAD_COMPLETE, 1); + s5k5baf_i2c_write(state, REG_CMDRD_PAGE, PAGE_IF_SW); + s5k5baf_i2c_write(state, REG_CMDWR_PAGE, PAGE_IF_SW); +} + +/* + * V4L2 subdev core and video operations + */ + +static void s5k5baf_initialize_data(struct s5k5baf *state) +{ + state->pixfmt = 0; + state->req_fiv = 10000 / 15; + state->fiv = state->req_fiv; + state->valid_auto_alg = 0; +} + +static int s5k5baf_load_setfile(struct s5k5baf *state) +{ + struct i2c_client *c = v4l2_get_subdevdata(&state->sd); + const struct firmware *fw; + int ret; + + ret = request_firmware(&fw, S5K5BAF_FW_FILENAME, &c->dev); + if (ret < 0) { + dev_warn(&c->dev, "firmware file (%s) not loaded\n", + S5K5BAF_FW_FILENAME); + return ret; + } + + ret = s5k5baf_fw_parse(&c->dev, &state->fw, fw->size / 2, + (u16 *)fw->data); + + release_firmware(fw); + + return ret; +} + +static int s5k5baf_set_power(struct v4l2_subdev *sd, int on) +{ + struct s5k5baf *state = to_s5k5baf(sd); + int ret = 0; + + mutex_lock(&state->lock); + + if (!on != state->power) + goto out; + + if (on) { + if (state->fw == NULL) + s5k5baf_load_setfile(state); + + s5k5baf_initialize_data(state); + ret = s5k5baf_power_on(state); + if (ret < 0) + goto out; + + s5k5baf_hw_init(state); + s5k5baf_hw_patch(state); + s5k5baf_i2c_write(state, REG_SET_HOST_INT, 1); + s5k5baf_hw_set_clocks(state); + + ret = s5k5baf_hw_set_video_bus(state); + if (ret < 0) + goto out; + + s5k5baf_hw_set_cis(state); + s5k5baf_hw_set_ccm(state); + + ret = s5k5baf_clear_error(state); + if (!ret) + state->power++; + } else { + s5k5baf_power_off(state); + state->power--; + } + +out: + mutex_unlock(&state->lock); + + if (!ret && on) + ret = v4l2_ctrl_handler_setup(&state->ctrls.handler); + + return ret; +} + +static void s5k5baf_hw_set_stream(struct s5k5baf *state, int enable) +{ + s5k5baf_write_seq(state, REG_G_ENABLE_PREV, enable, 1); +} + +static int s5k5baf_s_stream(struct v4l2_subdev *sd, int on) +{ + struct s5k5baf *state = to_s5k5baf(sd); + int ret; + + mutex_lock(&state->lock); + + if (state->streaming == !!on) { + ret = 0; + goto out; + } + + if (on) { + s5k5baf_hw_set_config(state); + ret = s5k5baf_hw_set_crop_rects(state); + if (ret < 0) + goto out; + s5k5baf_hw_set_stream(state, 1); + s5k5baf_i2c_write(state, 0xb0cc, 0x000b); + } else { + s5k5baf_hw_set_stream(state, 0); + } + ret = s5k5baf_clear_error(state); + if (!ret) + state->streaming = !state->streaming; + +out: + mutex_unlock(&state->lock); + + return ret; +} + +static int s5k5baf_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct s5k5baf *state = to_s5k5baf(sd); + + mutex_lock(&state->lock); + fi->interval.numerator = state->fiv; + fi->interval.denominator = 10000; + mutex_unlock(&state->lock); + + return 0; +} + +static void s5k5baf_set_frame_interval(struct s5k5baf *state, + struct v4l2_subdev_frame_interval *fi) +{ + struct v4l2_fract *i = &fi->interval; + + if (fi->interval.denominator == 0) + state->req_fiv = S5K5BAF_MAX_FR_TIME; + else + state->req_fiv = clamp_t(u32, + i->numerator * 10000 / i->denominator, + S5K5BAF_MIN_FR_TIME, + S5K5BAF_MAX_FR_TIME); + + state->fiv = state->req_fiv; + if (state->apply_cfg) { + s5k5baf_hw_set_fiv(state, state->req_fiv); + s5k5baf_hw_validate_cfg(state); + } + *i = (struct v4l2_fract){ state->fiv, 10000 }; + if (state->fiv == state->req_fiv) + v4l2_info(&state->sd, "frame interval changed to %d00us\n", + state->fiv); +} + +static int s5k5baf_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct s5k5baf *state = to_s5k5baf(sd); + + mutex_lock(&state->lock); + s5k5baf_set_frame_interval(state, fi); + mutex_unlock(&state->lock); + return 0; +} + +/* + * V4L2 subdev pad level and video operations + */ +static int s5k5baf_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_interval_enum *fie) +{ + if (fie->index > S5K5BAF_MAX_FR_TIME - S5K5BAF_MIN_FR_TIME || + fie->pad != PAD_CIS) + return -EINVAL; + + v4l_bound_align_image(&fie->width, S5K5BAF_WIN_WIDTH_MIN, + S5K5BAF_CIS_WIDTH, 1, + &fie->height, S5K5BAF_WIN_HEIGHT_MIN, + S5K5BAF_CIS_HEIGHT, 1, 0); + + fie->interval.numerator = S5K5BAF_MIN_FR_TIME + fie->index; + fie->interval.denominator = 10000; + + return 0; +} + +static int s5k5baf_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad == PAD_CIS) { + if (code->index > 0) + return -EINVAL; + code->code = V4L2_MBUS_FMT_FIXED; + return 0; + } + + if (code->index >= ARRAY_SIZE(s5k5baf_formats)) + return -EINVAL; + + code->code = s5k5baf_formats[code->index].code; + return 0; +} + +static int s5k5baf_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + int i; + + if (fse->index > 0) + return -EINVAL; + + if (fse->pad == PAD_CIS) { + fse->code = V4L2_MBUS_FMT_FIXED; + fse->min_width = S5K5BAF_CIS_WIDTH; + fse->max_width = S5K5BAF_CIS_WIDTH; + fse->min_height = S5K5BAF_CIS_HEIGHT; + fse->max_height = S5K5BAF_CIS_HEIGHT; + return 0; + } + + i = ARRAY_SIZE(s5k5baf_formats); + while (--i) + if (fse->code == s5k5baf_formats[i].code) + break; + fse->code = s5k5baf_formats[i].code; + fse->min_width = S5K5BAF_WIN_WIDTH_MIN; + fse->max_width = S5K5BAF_CIS_WIDTH; + fse->max_height = S5K5BAF_WIN_HEIGHT_MIN; + fse->min_height = S5K5BAF_CIS_HEIGHT; + + return 0; +} + +static void s5k5baf_try_cis_format(struct v4l2_mbus_framefmt *mf) +{ + mf->width = S5K5BAF_CIS_WIDTH; + mf->height = S5K5BAF_CIS_HEIGHT; + mf->code = V4L2_MBUS_FMT_FIXED; + mf->colorspace = V4L2_COLORSPACE_JPEG; + mf->field = V4L2_FIELD_NONE; +} + +static int s5k5baf_try_isp_format(struct v4l2_mbus_framefmt *mf) +{ + int pixfmt; + + v4l_bound_align_image(&mf->width, S5K5BAF_WIN_WIDTH_MIN, + S5K5BAF_CIS_WIDTH, 1, + &mf->height, S5K5BAF_WIN_HEIGHT_MIN, + S5K5BAF_CIS_HEIGHT, 1, 0); + + pixfmt = s5k5baf_find_pixfmt(mf); + + mf->colorspace = s5k5baf_formats[pixfmt].colorspace; + mf->code = s5k5baf_formats[pixfmt].code; + mf->field = V4L2_FIELD_NONE; + + return pixfmt; +} + +static int s5k5baf_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct s5k5baf *state = to_s5k5baf(sd); + const struct s5k5baf_pixfmt *pixfmt; + struct v4l2_mbus_framefmt *mf; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + mf = v4l2_subdev_get_try_format(fh, fmt->pad); + fmt->format = *mf; + return 0; + } + + mf = &fmt->format; + if (fmt->pad == PAD_CIS) { + s5k5baf_try_cis_format(mf); + return 0; + } + mf->field = V4L2_FIELD_NONE; + mutex_lock(&state->lock); + pixfmt = &s5k5baf_formats[state->pixfmt]; + mf->width = state->crop_source.width; + mf->height = state->crop_source.height; + mf->code = pixfmt->code; + mf->colorspace = pixfmt->colorspace; + mutex_unlock(&state->lock); + + return 0; +} + +static int s5k5baf_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *mf = &fmt->format; + struct s5k5baf *state = to_s5k5baf(sd); + const struct s5k5baf_pixfmt *pixfmt; + int ret = 0; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_get_try_format(fh, fmt->pad) = *mf; + return 0; + } + + if (fmt->pad == PAD_CIS) { + s5k5baf_try_cis_format(mf); + return 0; + } + + mutex_lock(&state->lock); + + if (state->streaming) { + mutex_unlock(&state->lock); + return -EBUSY; + } + + state->pixfmt = s5k5baf_try_isp_format(mf); + pixfmt = &s5k5baf_formats[state->pixfmt]; + mf->code = pixfmt->code; + mf->colorspace = pixfmt->colorspace; + mf->width = state->crop_source.width; + mf->height = state->crop_source.height; + + mutex_unlock(&state->lock); + return ret; +} + +enum selection_rect { R_CIS, R_CROP_SINK, R_COMPOSE, R_CROP_SOURCE, R_INVALID }; + +static enum selection_rect s5k5baf_get_sel_rect(u32 pad, u32 target) +{ + switch (target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + return pad ? R_COMPOSE : R_CIS; + case V4L2_SEL_TGT_CROP: + return pad ? R_CROP_SOURCE : R_CROP_SINK; + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + return pad ? R_INVALID : R_CROP_SINK; + case V4L2_SEL_TGT_COMPOSE: + return pad ? R_INVALID : R_COMPOSE; + default: + return R_INVALID; + } +} + +static int s5k5baf_is_bound_target(u32 target) +{ + return target == V4L2_SEL_TGT_CROP_BOUNDS || + target == V4L2_SEL_TGT_COMPOSE_BOUNDS; +} + +static int s5k5baf_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_selection *sel) +{ + static enum selection_rect rtype; + struct s5k5baf *state = to_s5k5baf(sd); + + rtype = s5k5baf_get_sel_rect(sel->pad, sel->target); + + switch (rtype) { + case R_INVALID: + return -EINVAL; + case R_CIS: + sel->r = s5k5baf_cis_rect; + return 0; + default: + break; + } + + if (sel->which == V4L2_SUBDEV_FORMAT_TRY) { + if (rtype == R_COMPOSE) + sel->r = *v4l2_subdev_get_try_compose(fh, sel->pad); + else + sel->r = *v4l2_subdev_get_try_crop(fh, sel->pad); + return 0; + } + + mutex_lock(&state->lock); + switch (rtype) { + case R_CROP_SINK: + sel->r = state->crop_sink; + break; + case R_COMPOSE: + sel->r = state->compose; + break; + case R_CROP_SOURCE: + sel->r = state->crop_source; + break; + default: + break; + } + if (s5k5baf_is_bound_target(sel->target)) { + sel->r.left = 0; + sel->r.top = 0; + } + mutex_unlock(&state->lock); + + return 0; +} + +/* bounds range [start, start+len) to [0, max) and aligns to 2 */ +static void s5k5baf_bound_range(u32 *start, u32 *len, u32 max) +{ + if (*len > max) + *len = max; + if (*start + *len > max) + *start = max - *len; + *start &= ~1; + *len &= ~1; + if (*len < S5K5BAF_WIN_WIDTH_MIN) + *len = S5K5BAF_WIN_WIDTH_MIN; +} + +static void s5k5baf_bound_rect(struct v4l2_rect *r, u32 width, u32 height) +{ + s5k5baf_bound_range(&r->left, &r->width, width); + s5k5baf_bound_range(&r->top, &r->height, height); +} + +static void s5k5baf_set_rect_and_adjust(struct v4l2_rect **rects, + enum selection_rect first, + struct v4l2_rect *v) +{ + struct v4l2_rect *r, *br; + enum selection_rect i = first; + + *rects[first] = *v; + do { + r = rects[i]; + br = rects[i - 1]; + s5k5baf_bound_rect(r, br->width, br->height); + } while (++i != R_INVALID); + *v = *rects[first]; +} + +static bool s5k5baf_cmp_rect(const struct v4l2_rect *r1, + const struct v4l2_rect *r2) +{ + return !memcmp(r1, r2, sizeof(*r1)); +} + +static int s5k5baf_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_selection *sel) +{ + static enum selection_rect rtype; + struct s5k5baf *state = to_s5k5baf(sd); + struct v4l2_rect **rects; + int ret = 0; + + rtype = s5k5baf_get_sel_rect(sel->pad, sel->target); + if (rtype == R_INVALID || s5k5baf_is_bound_target(sel->target)) + return -EINVAL; + + /* allow only scaling on compose */ + if (rtype == R_COMPOSE) { + sel->r.left = 0; + sel->r.top = 0; + } + + if (sel->which == V4L2_SUBDEV_FORMAT_TRY) { + rects = (struct v4l2_rect * []) { + &s5k5baf_cis_rect, + v4l2_subdev_get_try_crop(fh, PAD_CIS), + v4l2_subdev_get_try_compose(fh, PAD_CIS), + v4l2_subdev_get_try_crop(fh, PAD_OUT) + }; + s5k5baf_set_rect_and_adjust(rects, rtype, &sel->r); + return 0; + } + + rects = (struct v4l2_rect * []) { + &s5k5baf_cis_rect, + &state->crop_sink, + &state->compose, + &state->crop_source + }; + mutex_lock(&state->lock); + if (state->streaming) { + /* adjust sel->r to avoid output resolution change */ + if (rtype < R_CROP_SOURCE) { + if (sel->r.width < state->crop_source.width) + sel->r.width = state->crop_source.width; + if (sel->r.height < state->crop_source.height) + sel->r.height = state->crop_source.height; + } else { + sel->r.width = state->crop_source.width; + sel->r.height = state->crop_source.height; + } + } + s5k5baf_set_rect_and_adjust(rects, rtype, &sel->r); + if (!s5k5baf_cmp_rect(&state->crop_sink, &s5k5baf_cis_rect) || + !s5k5baf_cmp_rect(&state->compose, &s5k5baf_cis_rect)) + state->apply_crop = 1; + if (state->streaming) + ret = s5k5baf_hw_set_crop_rects(state); + mutex_unlock(&state->lock); + + return ret; +} + +static const struct v4l2_subdev_pad_ops s5k5baf_cis_pad_ops = { + .enum_mbus_code = s5k5baf_enum_mbus_code, + .enum_frame_size = s5k5baf_enum_frame_size, + .get_fmt = s5k5baf_get_fmt, + .set_fmt = s5k5baf_set_fmt, +}; + +static const struct v4l2_subdev_pad_ops s5k5baf_pad_ops = { + .enum_mbus_code = s5k5baf_enum_mbus_code, + .enum_frame_size = s5k5baf_enum_frame_size, + .enum_frame_interval = s5k5baf_enum_frame_interval, + .get_fmt = s5k5baf_get_fmt, + .set_fmt = s5k5baf_set_fmt, + .get_selection = s5k5baf_get_selection, + .set_selection = s5k5baf_set_selection, +}; + +static const struct v4l2_subdev_video_ops s5k5baf_video_ops = { + .g_frame_interval = s5k5baf_g_frame_interval, + .s_frame_interval = s5k5baf_s_frame_interval, + .s_stream = s5k5baf_s_stream, +}; + +/* + * V4L2 subdev controls + */ + +static int s5k5baf_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct s5k5baf *state = to_s5k5baf(sd); + int ret; + + v4l2_dbg(1, debug, sd, "ctrl: %s, value: %d\n", ctrl->name, ctrl->val); + + mutex_lock(&state->lock); + + if (state->power == 0) + goto unlock; + + switch (ctrl->id) { + case V4L2_CID_AUTO_WHITE_BALANCE: + s5k5baf_hw_set_awb(state, ctrl->val); + break; + + case V4L2_CID_BRIGHTNESS: + s5k5baf_write(state, REG_USER_BRIGHTNESS, ctrl->val); + break; + + case V4L2_CID_COLORFX: + s5k5baf_hw_set_colorfx(state, ctrl->val); + break; + + case V4L2_CID_CONTRAST: + s5k5baf_write(state, REG_USER_CONTRAST, ctrl->val); + break; + + case V4L2_CID_EXPOSURE_AUTO: + s5k5baf_hw_set_auto_exposure(state, ctrl->val); + break; + + case V4L2_CID_HFLIP: + s5k5baf_hw_set_mirror(state); + break; + + case V4L2_CID_POWER_LINE_FREQUENCY: + s5k5baf_hw_set_anti_flicker(state, ctrl->val); + break; + + case V4L2_CID_SATURATION: + s5k5baf_write(state, REG_USER_SATURATION, ctrl->val); + break; + + case V4L2_CID_SHARPNESS: + s5k5baf_write(state, REG_USER_SHARPBLUR, ctrl->val); + break; + + case V4L2_CID_WHITE_BALANCE_TEMPERATURE: + s5k5baf_write(state, REG_P_COLORTEMP(0), ctrl->val); + if (state->apply_cfg) + s5k5baf_hw_sync_cfg(state); + break; + + case V4L2_CID_TEST_PATTERN: + s5k5baf_hw_set_test_pattern(state, ctrl->val); + break; + } +unlock: + ret = s5k5baf_clear_error(state); + mutex_unlock(&state->lock); + return ret; +} + +static const struct v4l2_ctrl_ops s5k5baf_ctrl_ops = { + .s_ctrl = s5k5baf_s_ctrl, +}; + +static const char * const s5k5baf_test_pattern_menu[] = { + "Disabled", + "Blank", + "Bars", + "Gradients", + "Textile", + "Textile2", + "Squares" +}; + +static int s5k5baf_initialize_ctrls(struct s5k5baf *state) +{ + const struct v4l2_ctrl_ops *ops = &s5k5baf_ctrl_ops; + struct s5k5baf_ctrls *ctrls = &state->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + int ret; + + ret = v4l2_ctrl_handler_init(hdl, 16); + if (ret < 0) { + v4l2_err(&state->sd, "cannot init ctrl handler (%d)\n", ret); + return ret; + } + + /* Auto white balance cluster */ + ctrls->awb = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_AUTO_WHITE_BALANCE, + 0, 1, 1, 1); + ctrls->gain_red = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_RED_BALANCE, + 0, 255, 1, S5K5BAF_GAIN_RED_DEF); + ctrls->gain_blue = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BLUE_BALANCE, + 0, 255, 1, S5K5BAF_GAIN_BLUE_DEF); + v4l2_ctrl_auto_cluster(3, &ctrls->awb, 0, false); + + ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_cluster(2, &ctrls->hflip); + + ctrls->auto_exp = v4l2_ctrl_new_std_menu(hdl, ops, + V4L2_CID_EXPOSURE_AUTO, + V4L2_EXPOSURE_MANUAL, 0, V4L2_EXPOSURE_AUTO); + /* Exposure time: x 1 us */ + ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, + 0, 6000000U, 1, 100000U); + /* Total gain: 256 <=> 1x */ + ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAIN, + 0, 256, 1, 256); + v4l2_ctrl_auto_cluster(3, &ctrls->auto_exp, 0, false); + + v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_POWER_LINE_FREQUENCY, + V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0, + V4L2_CID_POWER_LINE_FREQUENCY_AUTO); + + v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_COLORFX, + V4L2_COLORFX_SKY_BLUE, ~0x6f, V4L2_COLORFX_NONE); + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_WHITE_BALANCE_TEMPERATURE, + 0, 256, 1, 0); + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION, -127, 127, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -127, 127, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, -127, 127, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SHARPNESS, -127, 127, 1, 0); + + v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(s5k5baf_test_pattern_menu) - 1, + 0, 0, s5k5baf_test_pattern_menu); + + if (hdl->error) { + v4l2_err(&state->sd, "error creating controls (%d)\n", + hdl->error); + ret = hdl->error; + v4l2_ctrl_handler_free(hdl); + return ret; + } + + state->sd.ctrl_handler = hdl; + return 0; +} + +/* + * V4L2 subdev internal operations + */ +static int s5k5baf_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *mf; + + mf = v4l2_subdev_get_try_format(fh, PAD_CIS); + s5k5baf_try_cis_format(mf); + + if (s5k5baf_is_cis_subdev(sd)) + return 0; + + mf = v4l2_subdev_get_try_format(fh, PAD_OUT); + mf->colorspace = s5k5baf_formats[0].colorspace; + mf->code = s5k5baf_formats[0].code; + mf->width = s5k5baf_cis_rect.width; + mf->height = s5k5baf_cis_rect.height; + mf->field = V4L2_FIELD_NONE; + + *v4l2_subdev_get_try_crop(fh, PAD_CIS) = s5k5baf_cis_rect; + *v4l2_subdev_get_try_compose(fh, PAD_CIS) = s5k5baf_cis_rect; + *v4l2_subdev_get_try_crop(fh, PAD_OUT) = s5k5baf_cis_rect; + + return 0; +} + +static int s5k5baf_check_fw_revision(struct s5k5baf *state) +{ + u16 api_ver = 0, fw_rev = 0, s_id = 0; + int ret; + + api_ver = s5k5baf_read(state, REG_FW_APIVER); + fw_rev = s5k5baf_read(state, REG_FW_REVISION) & 0xff; + s_id = s5k5baf_read(state, REG_FW_SENSOR_ID); + ret = s5k5baf_clear_error(state); + if (ret < 0) + return ret; + + v4l2_info(&state->sd, "FW API=%#x, revision=%#x sensor_id=%#x\n", + api_ver, fw_rev, s_id); + + if (api_ver != S5K5BAF_FW_APIVER) { + v4l2_err(&state->sd, "FW API version not supported\n"); + return -ENODEV; + } + + return 0; +} + +static int s5k5baf_registered(struct v4l2_subdev *sd) +{ + struct s5k5baf *state = to_s5k5baf(sd); + int ret; + + ret = v4l2_device_register_subdev(sd->v4l2_dev, &state->cis_sd); + if (ret < 0) + v4l2_err(sd, "failed to register subdev %s\n", + state->cis_sd.name); + else + ret = media_entity_create_link(&state->cis_sd.entity, PAD_CIS, + &state->sd.entity, PAD_CIS, + MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + return ret; +} + +static void s5k5baf_unregistered(struct v4l2_subdev *sd) +{ + struct s5k5baf *state = to_s5k5baf(sd); + v4l2_device_unregister_subdev(&state->cis_sd); +} + +static const struct v4l2_subdev_ops s5k5baf_cis_subdev_ops = { + .pad = &s5k5baf_cis_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops s5k5baf_cis_subdev_internal_ops = { + .open = s5k5baf_open, +}; + +static const struct v4l2_subdev_internal_ops s5k5baf_subdev_internal_ops = { + .registered = s5k5baf_registered, + .unregistered = s5k5baf_unregistered, + .open = s5k5baf_open, +}; + +static const struct v4l2_subdev_core_ops s5k5baf_core_ops = { + .s_power = s5k5baf_set_power, + .log_status = v4l2_ctrl_subdev_log_status, +}; + +static const struct v4l2_subdev_ops s5k5baf_subdev_ops = { + .core = &s5k5baf_core_ops, + .pad = &s5k5baf_pad_ops, + .video = &s5k5baf_video_ops, +}; + +static int s5k5baf_configure_gpios(struct s5k5baf *state) +{ + static const char const *name[] = { "S5K5BAF_STBY", "S5K5BAF_RST" }; + struct i2c_client *c = v4l2_get_subdevdata(&state->sd); + struct s5k5baf_gpio *g = state->gpios; + int ret, i; + + for (i = 0; i < NUM_GPIOS; ++i) { + int flags = GPIOF_DIR_OUT; + if (g[i].level) + flags |= GPIOF_INIT_HIGH; + ret = devm_gpio_request_one(&c->dev, g[i].gpio, flags, name[i]); + if (ret < 0) { + v4l2_err(c, "failed to request gpio %s\n", name[i]); + return ret; + } + } + return 0; +} + +static int s5k5baf_parse_gpios(struct s5k5baf_gpio *gpios, struct device *dev) +{ + static const char * const names[] = { + "stbyn-gpios", + "rstn-gpios", + }; + struct device_node *node = dev->of_node; + enum of_gpio_flags flags; + int ret, i; + + for (i = 0; i < NUM_GPIOS; ++i) { + ret = of_get_named_gpio_flags(node, names[i], 0, &flags); + if (ret < 0) { + dev_err(dev, "no %s GPIO pin provided\n", names[i]); + return ret; + } + gpios[i].gpio = ret; + gpios[i].level = !(flags & OF_GPIO_ACTIVE_LOW); + } + + return 0; +} + +static int s5k5baf_parse_device_node(struct s5k5baf *state, struct device *dev) +{ + struct device_node *node = dev->of_node; + struct device_node *node_ep; + struct v4l2_of_endpoint ep; + int ret; + + if (!node) { + dev_err(dev, "no device-tree node provided\n"); + return -EINVAL; + } + + ret = of_property_read_u32(node, "clock-frequency", + &state->mclk_frequency); + if (ret < 0) { + state->mclk_frequency = S5K5BAF_DEFAULT_MCLK_FREQ; + dev_info(dev, "using default %u Hz clock frequency\n", + state->mclk_frequency); + } + + ret = s5k5baf_parse_gpios(state->gpios, dev); + if (ret < 0) + return ret; + + node_ep = v4l2_of_get_next_endpoint(node, NULL); + if (!node_ep) { + dev_err(dev, "no endpoint defined at node %s\n", + node->full_name); + return -EINVAL; + } + + v4l2_of_parse_endpoint(node_ep, &ep); + of_node_put(node_ep); + state->bus_type = ep.bus_type; + + switch (state->bus_type) { + case V4L2_MBUS_CSI2: + state->nlanes = ep.bus.mipi_csi2.num_data_lanes; + break; + case V4L2_MBUS_PARALLEL: + break; + default: + dev_err(dev, "unsupported bus in endpoint defined at node %s\n", + node->full_name); + return -EINVAL; + } + + return 0; +} + +static int s5k5baf_configure_subdevs(struct s5k5baf *state, + struct i2c_client *c) +{ + struct v4l2_subdev *sd; + int ret; + + sd = &state->cis_sd; + v4l2_subdev_init(sd, &s5k5baf_cis_subdev_ops); + sd->owner = THIS_MODULE; + v4l2_set_subdevdata(sd, state); + snprintf(sd->name, sizeof(sd->name), "S5K5BAF-CIS %d-%04x", + i2c_adapter_id(c->adapter), c->addr); + + sd->internal_ops = &s5k5baf_cis_subdev_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + state->cis_pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV_SENSOR; + ret = media_entity_init(&sd->entity, NUM_CIS_PADS, &state->cis_pad, 0); + if (ret < 0) + goto err; + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, c, &s5k5baf_subdev_ops); + snprintf(sd->name, sizeof(sd->name), "S5K5BAF-ISP %d-%04x", + i2c_adapter_id(c->adapter), c->addr); + + sd->internal_ops = &s5k5baf_subdev_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + state->pads[PAD_CIS].flags = MEDIA_PAD_FL_SINK; + state->pads[PAD_OUT].flags = MEDIA_PAD_FL_SOURCE; + sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV; + ret = media_entity_init(&sd->entity, NUM_ISP_PADS, state->pads, 0); + + if (!ret) + return 0; + + media_entity_cleanup(&state->cis_sd.entity); +err: + dev_err(&c->dev, "cannot init media entity %s\n", sd->name); + return ret; +} + +static int s5k5baf_configure_regulators(struct s5k5baf *state) +{ + struct i2c_client *c = v4l2_get_subdevdata(&state->sd); + int ret; + int i; + + for (i = 0; i < S5K5BAF_NUM_SUPPLIES; i++) + state->supplies[i].supply = s5k5baf_supply_names[i]; + + ret = devm_regulator_bulk_get(&c->dev, S5K5BAF_NUM_SUPPLIES, + state->supplies); + if (ret < 0) + v4l2_err(c, "failed to get regulators\n"); + return ret; +} + +static int s5k5baf_probe(struct i2c_client *c, + const struct i2c_device_id *id) +{ + struct s5k5baf *state; + int ret; + + state = devm_kzalloc(&c->dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + mutex_init(&state->lock); + state->crop_sink = s5k5baf_cis_rect; + state->compose = s5k5baf_cis_rect; + state->crop_source = s5k5baf_cis_rect; + + ret = s5k5baf_parse_device_node(state, &c->dev); + if (ret < 0) + return ret; + + ret = s5k5baf_configure_subdevs(state, c); + if (ret < 0) + return ret; + + ret = s5k5baf_configure_gpios(state); + if (ret < 0) + goto err_me; + + ret = s5k5baf_configure_regulators(state); + if (ret < 0) + goto err_me; + + state->clock = devm_clk_get(state->sd.dev, S5K5BAF_CLK_NAME); + if (IS_ERR(state->clock)) { + ret = -EPROBE_DEFER; + goto err_me; + } + + ret = s5k5baf_power_on(state); + if (ret < 0) { + ret = -EPROBE_DEFER; + goto err_me; + } + s5k5baf_hw_init(state); + ret = s5k5baf_check_fw_revision(state); + + s5k5baf_power_off(state); + if (ret < 0) + goto err_me; + + ret = s5k5baf_initialize_ctrls(state); + if (ret < 0) + goto err_me; + + ret = v4l2_async_register_subdev(&state->sd); + if (ret < 0) + goto err_ctrl; + + return 0; + +err_ctrl: + v4l2_ctrl_handler_free(state->sd.ctrl_handler); +err_me: + media_entity_cleanup(&state->sd.entity); + media_entity_cleanup(&state->cis_sd.entity); + return ret; +} + +static int s5k5baf_remove(struct i2c_client *c) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(c); + struct s5k5baf *state = to_s5k5baf(sd); + + v4l2_async_unregister_subdev(sd); + v4l2_ctrl_handler_free(sd->ctrl_handler); + media_entity_cleanup(&sd->entity); + + sd = &state->cis_sd; + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + + return 0; +} + +static const struct i2c_device_id s5k5baf_id[] = { + { S5K5BAF_DRIVER_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, s5k5baf_id); + +static const struct of_device_id s5k5baf_of_match[] = { + { .compatible = "samsung,s5k5baf" }, + { } +}; +MODULE_DEVICE_TABLE(of, s5k5baf_of_match); + +static struct i2c_driver s5k5baf_i2c_driver = { + .driver = { + .of_match_table = s5k5baf_of_match, + .name = S5K5BAF_DRIVER_NAME + }, + .probe = s5k5baf_probe, + .remove = s5k5baf_remove, + .id_table = s5k5baf_id, +}; + +module_i2c_driver(s5k5baf_i2c_driver); + +MODULE_DESCRIPTION("Samsung S5K5BAF(X) UXGA camera driver"); +MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/saa6588.c b/drivers/media/i2c/saa6588.c index 70bc72e795d0..2960b5a8362a 100644 --- a/drivers/media/i2c/saa6588.c +++ b/drivers/media/i2c/saa6588.c @@ -150,14 +150,14 @@ static inline struct saa6588 *to_saa6588(struct v4l2_subdev *sd) /* ---------------------------------------------------------------------- */ -static int block_to_user_buf(struct saa6588 *s, unsigned char __user *user_buf) +static bool block_from_buf(struct saa6588 *s, unsigned char *buf) { int i; if (s->rd_index == s->wr_index) { if (debug > 2) dprintk(PREFIX "Read: buffer empty.\n"); - return 0; + return false; } if (debug > 2) { @@ -166,8 +166,7 @@ static int block_to_user_buf(struct saa6588 *s, unsigned char __user *user_buf) dprintk("0x%02x ", s->buffer[i]); } - if (copy_to_user(user_buf, &s->buffer[s->rd_index], 3)) - return -EFAULT; + memcpy(buf, &s->buffer[s->rd_index], 3); s->rd_index += 3; if (s->rd_index >= s->buf_size) @@ -177,22 +176,22 @@ static int block_to_user_buf(struct saa6588 *s, unsigned char __user *user_buf) if (debug > 2) dprintk("%d blocks total.\n", s->block_count); - return 1; + return true; } static void read_from_buf(struct saa6588 *s, struct saa6588_command *a) { - unsigned long flags; - unsigned char __user *buf_ptr = a->buffer; - unsigned int i; + unsigned char buf[3]; + unsigned long flags; unsigned int rd_blocks; + unsigned int i; a->result = 0; if (!a->buffer) return; - while (!s->data_available_for_read) { + while (!a->nonblocking && !s->data_available_for_read) { int ret = wait_event_interruptible(s->read_queue, s->data_available_for_read); if (ret == -ERESTARTSYS) { @@ -201,24 +200,31 @@ static void read_from_buf(struct saa6588 *s, struct saa6588_command *a) } } - spin_lock_irqsave(&s->lock, flags); rd_blocks = a->block_count; + spin_lock_irqsave(&s->lock, flags); if (rd_blocks > s->block_count) rd_blocks = s->block_count; + spin_unlock_irqrestore(&s->lock, flags); - if (!rd_blocks) { - spin_unlock_irqrestore(&s->lock, flags); + if (!rd_blocks) return; - } for (i = 0; i < rd_blocks; i++) { - if (block_to_user_buf(s, buf_ptr)) { - buf_ptr += 3; - a->result++; - } else + bool got_block; + + spin_lock_irqsave(&s->lock, flags); + got_block = block_from_buf(s, buf); + spin_unlock_irqrestore(&s->lock, flags); + if (!got_block) break; + if (copy_to_user(buf_ptr, buf, 3)) { + a->result = -EFAULT; + return; + } + buf_ptr += 3; + a->result += 3; } - a->result *= 3; + spin_lock_irqsave(&s->lock, flags); s->data_available_for_read = (s->block_count > 0); spin_unlock_irqrestore(&s->lock, flags); } @@ -394,14 +400,11 @@ static long saa6588_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) struct saa6588_command *a = arg; switch (cmd) { - /* --- open() for /dev/radio --- */ - case SAA6588_CMD_OPEN: - a->result = 0; /* return error if chip doesn't work ??? */ - break; /* --- close() for /dev/radio --- */ case SAA6588_CMD_CLOSE: s->data_available_for_read = 1; wake_up_interruptible(&s->read_queue); + s->data_available_for_read = 0; a->result = 0; break; /* --- read() for /dev/radio --- */ @@ -411,9 +414,8 @@ static long saa6588_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) /* --- poll() for /dev/radio --- */ case SAA6588_CMD_POLL: a->result = 0; - if (s->data_available_for_read) { + if (s->data_available_for_read) a->result |= POLLIN | POLLRDNORM; - } poll_wait(a->instance, &s->read_queue, a->event_list); break; diff --git a/drivers/media/pci/saa7134/saa6752hs.c b/drivers/media/i2c/saa6752hs.c index 8ac4b1f2322d..8272c0b9c5bf 100644 --- a/drivers/media/pci/saa7134/saa6752hs.c +++ b/drivers/media/i2c/saa6752hs.c @@ -33,11 +33,11 @@ #include <linux/i2c.h> #include <linux/types.h> #include <linux/videodev2.h> +#include <linux/init.h> +#include <linux/crc32.h> #include <media/v4l2-device.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-common.h> -#include <linux/init.h> -#include <linux/crc32.h> #define MPEG_VIDEO_TARGET_BITRATE_MAX 27000 #define MPEG_VIDEO_MAX_BITRATE_MAX 27000 @@ -124,7 +124,7 @@ static inline struct saa6752hs_state *to_state(struct v4l2_subdev *sd) /* ---------------------------------------------------------------------- */ -static u8 PAT[] = { +static const u8 PAT[] = { 0xc2, /* i2c register */ 0x00, /* table number for encoder */ @@ -150,7 +150,7 @@ static u8 PAT[] = { 0x00, 0x00, 0x00, 0x00 /* CRC32 */ }; -static u8 PMT[] = { +static const u8 PMT[] = { 0xc2, /* i2c register */ 0x01, /* table number for encoder */ @@ -179,7 +179,7 @@ static u8 PMT[] = { 0x00, 0x00, 0x00, 0x00 /* CRC32 */ }; -static u8 PMT_AC3[] = { +static const u8 PMT_AC3[] = { 0xc2, /* i2c register */ 0x01, /* table number for encoder(1) */ 0x47, /* sync */ @@ -212,7 +212,7 @@ static u8 PMT_AC3[] = { 0xED, 0xDE, 0x2D, 0xF3 /* CRC32 BE */ }; -static struct saa6752hs_mpeg_params param_defaults = +static const struct saa6752hs_mpeg_params param_defaults = { .ts_pid_pmt = 16, .ts_pid_video = 260, @@ -643,13 +643,6 @@ static const struct v4l2_ctrl_ops saa6752hs_ctrl_ops = { static const struct v4l2_subdev_core_ops saa6752hs_core_ops = { .init = saa6752hs_init, - .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, - .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, - .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, - .g_ctrl = v4l2_subdev_g_ctrl, - .s_ctrl = v4l2_subdev_s_ctrl, - .queryctrl = v4l2_subdev_queryctrl, - .querymenu = v4l2_subdev_querymenu, .s_std = saa6752hs_s_std, }; diff --git a/drivers/media/i2c/smiapp/smiapp-core.c b/drivers/media/i2c/smiapp/smiapp-core.c index ae66d91bf713..8741cae9c9f2 100644 --- a/drivers/media/i2c/smiapp/smiapp-core.c +++ b/drivers/media/i2c/smiapp/smiapp-core.c @@ -399,7 +399,6 @@ static void smiapp_update_mbus_formats(struct smiapp_sensor *sensor) BUG_ON(max(internal_csi_format_idx, csi_format_idx) + pixel_order >= ARRAY_SIZE(smiapp_csi_data_formats)); - BUG_ON(min(internal_csi_format_idx, csi_format_idx) < 0); dev_dbg(&client->dev, "new pixel order %s\n", pixel_order_str[pixel_order]); @@ -2028,8 +2027,8 @@ static int smiapp_set_crop(struct v4l2_subdev *subdev, sel->r.width = min(sel->r.width, src_size->width); sel->r.height = min(sel->r.height, src_size->height); - sel->r.left = min(sel->r.left, src_size->width - sel->r.width); - sel->r.top = min(sel->r.top, src_size->height - sel->r.height); + sel->r.left = min_t(int, sel->r.left, src_size->width - sel->r.width); + sel->r.top = min_t(int, sel->r.top, src_size->height - sel->r.height); *crops[sel->pad] = sel->r; @@ -2121,8 +2120,8 @@ static int smiapp_set_selection(struct v4l2_subdev *subdev, sel->r.left = max(0, sel->r.left & ~1); sel->r.top = max(0, sel->r.top & ~1); - sel->r.width = max(0, SMIAPP_ALIGN_DIM(sel->r.width, sel->flags)); - sel->r.height = max(0, SMIAPP_ALIGN_DIM(sel->r.height, sel->flags)); + sel->r.width = SMIAPP_ALIGN_DIM(sel->r.width, sel->flags); + sel->r.height = SMIAPP_ALIGN_DIM(sel->r.height, sel->flags); sel->r.width = max_t(unsigned int, sensor->limits[SMIAPP_LIMIT_MIN_X_OUTPUT_SIZE], diff --git a/drivers/media/i2c/soc_camera/mt9m111.c b/drivers/media/i2c/soc_camera/mt9m111.c index 6f4056668bbc..ccf59406a172 100644 --- a/drivers/media/i2c/soc_camera/mt9m111.c +++ b/drivers/media/i2c/soc_camera/mt9m111.c @@ -208,8 +208,8 @@ struct mt9m111 { struct mt9m111_context *ctx; struct v4l2_rect rect; /* cropping rectangle */ struct v4l2_clk *clk; - int width; /* output */ - int height; /* sizes */ + unsigned int width; /* output */ + unsigned int height; /* sizes */ struct mutex power_lock; /* lock to protect power_count */ int power_count; const struct mt9m111_datafmt *fmt; diff --git a/drivers/media/i2c/tvp5150.c b/drivers/media/i2c/tvp5150.c index 89c0b13463b7..542d2528b3f9 100644 --- a/drivers/media/i2c/tvp5150.c +++ b/drivers/media/i2c/tvp5150.c @@ -58,21 +58,17 @@ static int tvp5150_read(struct v4l2_subdev *sd, unsigned char addr) struct i2c_client *c = v4l2_get_subdevdata(sd); unsigned char buffer[1]; int rc; - - buffer[0] = addr; - - rc = i2c_master_send(c, buffer, 1); - if (rc < 0) { - v4l2_err(sd, "i2c i/o error: rc == %d (should be 1)\n", rc); - return rc; - } - - msleep(10); - - rc = i2c_master_recv(c, buffer, 1); - if (rc < 0) { - v4l2_err(sd, "i2c i/o error: rc == %d (should be 1)\n", rc); - return rc; + struct i2c_msg msg[] = { + { .addr = c->addr, .flags = 0, + .buf = &addr, .len = 1 }, + { .addr = c->addr, .flags = I2C_M_RD, + .buf = buffer, .len = 1 } + }; + + rc = i2c_transfer(c->adapter, msg, 2); + if (rc < 0 || rc != 2) { + v4l2_err(sd, "i2c i/o error: rc == %d (should be 2)\n", rc); + return rc < 0 ? rc : -EIO; } v4l2_dbg(2, debug, sd, "tvp5150: read 0x%02x = 0x%02x\n", addr, buffer[0]); @@ -867,7 +863,7 @@ static int tvp5150_s_crop(struct v4l2_subdev *sd, const struct v4l2_crop *a) struct v4l2_rect rect = a->c; struct tvp5150 *decoder = to_tvp5150(sd); v4l2_std_id std; - int hmax; + unsigned int hmax; v4l2_dbg(1, debug, sd, "%s left=%d, top=%d, width=%d, height=%d\n", __func__, rect.left, rect.top, rect.width, rect.height); @@ -877,9 +873,9 @@ static int tvp5150_s_crop(struct v4l2_subdev *sd, const struct v4l2_crop *a) /* tvp5150 has some special limits */ rect.left = clamp(rect.left, 0, TVP5150_MAX_CROP_LEFT); - rect.width = clamp(rect.width, - TVP5150_H_MAX - TVP5150_MAX_CROP_LEFT - rect.left, - TVP5150_H_MAX - rect.left); + rect.width = clamp_t(unsigned int, rect.width, + TVP5150_H_MAX - TVP5150_MAX_CROP_LEFT - rect.left, + TVP5150_H_MAX - rect.left); rect.top = clamp(rect.top, 0, TVP5150_MAX_CROP_TOP); /* Calculate height based on current standard */ @@ -893,9 +889,9 @@ static int tvp5150_s_crop(struct v4l2_subdev *sd, const struct v4l2_crop *a) else hmax = TVP5150_V_MAX_OTHERS; - rect.height = clamp(rect.height, - hmax - TVP5150_MAX_CROP_TOP - rect.top, - hmax - rect.top); + rect.height = clamp_t(unsigned int, rect.height, + hmax - TVP5150_MAX_CROP_TOP - rect.top, + hmax - rect.top); tvp5150_write(sd, TVP5150_VERT_BLANKING_START, rect.top); tvp5150_write(sd, TVP5150_VERT_BLANKING_STOP, diff --git a/drivers/media/i2c/vs6624.c b/drivers/media/i2c/vs6624.c index 25bdd9312fea..23f4f65fccd7 100644 --- a/drivers/media/i2c/vs6624.c +++ b/drivers/media/i2c/vs6624.c @@ -503,6 +503,7 @@ static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) return &container_of(ctrl->handler, struct vs6624, hdl)->sd; } +#ifdef CONFIG_VIDEO_ADV_DEBUG static int vs6624_read(struct v4l2_subdev *sd, u16 index) { struct i2c_client *client = v4l2_get_subdevdata(sd); @@ -515,6 +516,7 @@ static int vs6624_read(struct v4l2_subdev *sd, u16 index) return buf[0]; } +#endif static int vs6624_write(struct v4l2_subdev *sd, u16 index, u8 value) diff --git a/drivers/media/media-entity.c b/drivers/media/media-entity.c index 2c286c307145..37c334edc7e8 100644 --- a/drivers/media/media-entity.c +++ b/drivers/media/media-entity.c @@ -235,6 +235,8 @@ __must_check int media_entity_pipeline_start(struct media_entity *entity, media_entity_graph_walk_start(&graph, entity); while ((entity = media_entity_graph_walk_next(&graph))) { + DECLARE_BITMAP(active, entity->num_pads); + DECLARE_BITMAP(has_no_links, entity->num_pads); unsigned int i; entity->stream_count++; @@ -248,21 +250,46 @@ __must_check int media_entity_pipeline_start(struct media_entity *entity, if (!entity->ops || !entity->ops->link_validate) continue; + bitmap_zero(active, entity->num_pads); + bitmap_fill(has_no_links, entity->num_pads); + for (i = 0; i < entity->num_links; i++) { struct media_link *link = &entity->links[i]; - - /* Is this pad part of an enabled link? */ - if (!(link->flags & MEDIA_LNK_FL_ENABLED)) - continue; - - /* Are we the sink or not? */ - if (link->sink->entity != entity) + struct media_pad *pad = link->sink->entity == entity + ? link->sink : link->source; + + /* Mark that a pad is connected by a link. */ + bitmap_clear(has_no_links, pad->index, 1); + + /* + * Pads that either do not need to connect or + * are connected through an enabled link are + * fine. + */ + if (!(pad->flags & MEDIA_PAD_FL_MUST_CONNECT) || + link->flags & MEDIA_LNK_FL_ENABLED) + bitmap_set(active, pad->index, 1); + + /* + * Link validation will only take place for + * sink ends of the link that are enabled. + */ + if (link->sink != pad || + !(link->flags & MEDIA_LNK_FL_ENABLED)) continue; ret = entity->ops->link_validate(link); if (ret < 0 && ret != -ENOIOCTLCMD) goto error; } + + /* Either no links or validated links are fine. */ + bitmap_or(active, active, has_no_links, entity->num_pads); + + if (!bitmap_full(active, entity->num_pads)) { + ret = -EPIPE; + goto error; + } } mutex_unlock(&mdev->graph_mutex); diff --git a/drivers/media/pci/bt8xx/bttv-driver.c b/drivers/media/pci/bt8xx/bttv-driver.c index 92a06fd85865..afcd53bfcf8e 100644 --- a/drivers/media/pci/bt8xx/bttv-driver.c +++ b/drivers/media/pci/bt8xx/bttv-driver.c @@ -1126,9 +1126,9 @@ bttv_crop_calc_limits(struct bttv_crop *c) c->min_scaled_height = 32; } else { c->min_scaled_width = - (max(48, c->rect.width >> 4) + 3) & ~3; + (max_t(unsigned int, 48, c->rect.width >> 4) + 3) & ~3; c->min_scaled_height = - max(32, c->rect.height >> 4); + max_t(unsigned int, 32, c->rect.height >> 4); } c->max_scaled_width = c->rect.width & ~3; @@ -2024,7 +2024,7 @@ limit_scaled_size_lock (struct bttv_fh * fh, /* We cannot scale up. When the scaled image is larger than crop.rect we adjust the crop.rect as required by the V4L2 spec, hence cropcap.bounds are our limit. */ - max_width = min(b->width, (__s32) MAX_HACTIVE); + max_width = min_t(unsigned int, b->width, MAX_HACTIVE); max_height = b->height; /* We cannot capture the same line as video and VBI data. @@ -3266,7 +3266,9 @@ static ssize_t radio_read(struct file *file, char __user *data, struct bttv_fh *fh = file->private_data; struct bttv *btv = fh->btv; struct saa6588_command cmd; - cmd.block_count = count/3; + + cmd.block_count = count / 3; + cmd.nonblocking = file->f_flags & O_NONBLOCK; cmd.buffer = data; cmd.instance = file; cmd.result = -ENODEV; diff --git a/drivers/media/pci/cx18/cx18-driver.c b/drivers/media/pci/cx18/cx18-driver.c index c1f8cc6f14b2..716bdc57fac6 100644 --- a/drivers/media/pci/cx18/cx18-driver.c +++ b/drivers/media/pci/cx18/cx18-driver.c @@ -327,13 +327,16 @@ void cx18_read_eeprom(struct cx18 *cx, struct tveeprom *tv) struct i2c_client *c; u8 eedata[256]; + memset(tv, 0, sizeof(*tv)); + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + return; strlcpy(c->name, "cx18 tveeprom tmp", sizeof(c->name)); c->adapter = &cx->i2c_adap[0]; c->addr = 0xa0 >> 1; - memset(tv, 0, sizeof(*tv)); if (tveeprom_read(c, eedata, sizeof(eedata))) goto ret; diff --git a/drivers/media/pci/cx25821/cx25821-alsa.c b/drivers/media/pci/cx25821/cx25821-alsa.c index 6e91e84d6bf9..b1e08c3e55cd 100644 --- a/drivers/media/pci/cx25821/cx25821-alsa.c +++ b/drivers/media/pci/cx25821/cx25821-alsa.c @@ -618,7 +618,7 @@ static int snd_cx25821_pcm(struct cx25821_audio_dev *chip, int device, * Only boards with eeprom and byte 1 at eeprom=1 have it */ -static DEFINE_PCI_DEVICE_TABLE(cx25821_audio_pci_tbl) = { +static const struct pci_device_id cx25821_audio_pci_tbl[] = { {0x14f1, 0x0920, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, {0,} }; diff --git a/drivers/media/pci/cx25821/cx25821-core.c b/drivers/media/pci/cx25821/cx25821-core.c index b762c5b2ca10..e81173c41e5a 100644 --- a/drivers/media/pci/cx25821/cx25821-core.c +++ b/drivers/media/pci/cx25821/cx25821-core.c @@ -1361,7 +1361,7 @@ static void cx25821_finidev(struct pci_dev *pci_dev) kfree(dev); } -static DEFINE_PCI_DEVICE_TABLE(cx25821_pci_tbl) = { +static const struct pci_device_id cx25821_pci_tbl[] = { { /* CX25821 Athena */ .vendor = 0x14f1, diff --git a/drivers/media/pci/cx88/cx88-alsa.c b/drivers/media/pci/cx88/cx88-alsa.c index 400eb1c42d3f..d014206e7176 100644 --- a/drivers/media/pci/cx88/cx88-alsa.c +++ b/drivers/media/pci/cx88/cx88-alsa.c @@ -931,9 +931,9 @@ error: */ static void cx88_audio_finidev(struct pci_dev *pci) { - struct cx88_audio_dev *card = pci_get_drvdata(pci); + struct snd_card *card = pci_get_drvdata(pci); - snd_card_free((void *)card); + snd_card_free(card); devno--; } diff --git a/drivers/media/pci/saa7134/Kconfig b/drivers/media/pci/saa7134/Kconfig index 15b90d6e9130..7883393571e5 100644 --- a/drivers/media/pci/saa7134/Kconfig +++ b/drivers/media/pci/saa7134/Kconfig @@ -6,6 +6,7 @@ config VIDEO_SAA7134 select VIDEO_TVEEPROM select CRC32 select VIDEO_SAA6588 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_SAA6752HS if MEDIA_SUBDRV_AUTOSELECT ---help--- This is a video4linux driver for Philips SAA713x based TV cards. diff --git a/drivers/media/pci/saa7134/Makefile b/drivers/media/pci/saa7134/Makefile index 35375480ed4d..58de9b085689 100644 --- a/drivers/media/pci/saa7134/Makefile +++ b/drivers/media/pci/saa7134/Makefile @@ -4,7 +4,7 @@ saa7134-y += saa7134-ts.o saa7134-tvaudio.o saa7134-vbi.o saa7134-y += saa7134-video.o saa7134-$(CONFIG_VIDEO_SAA7134_RC) += saa7134-input.o -obj-$(CONFIG_VIDEO_SAA7134) += saa6752hs.o saa7134.o saa7134-empress.o +obj-$(CONFIG_VIDEO_SAA7134) += saa7134.o saa7134-empress.o obj-$(CONFIG_VIDEO_SAA7134_ALSA) += saa7134-alsa.o diff --git a/drivers/media/pci/saa7134/saa7134-core.c b/drivers/media/pci/saa7134/saa7134-core.c index 27d7ee709c58..1362b4aab473 100644 --- a/drivers/media/pci/saa7134/saa7134-core.c +++ b/drivers/media/pci/saa7134/saa7134-core.c @@ -751,6 +751,7 @@ static int saa7134_hwfini(struct saa7134_dev *dev) saa7134_input_fini(dev); saa7134_vbi_fini(dev); saa7134_tvaudio_fini(dev); + saa7134_video_fini(dev); return 0; } @@ -802,7 +803,6 @@ static struct video_device *vdev_init(struct saa7134_dev *dev, *vfd = *template; vfd->v4l2_dev = &dev->v4l2_dev; vfd->release = video_device_release; - vfd->debug = video_debug; snprintf(vfd->name, sizeof(vfd->name), "%s %s (%s)", dev->name, type, saa7134_boards[dev->board].name); set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags); @@ -1008,13 +1008,13 @@ static int saa7134_initdev(struct pci_dev *pci_dev, /* load i2c helpers */ if (card_is_empress(dev)) { - struct v4l2_subdev *sd = + dev->empress_sd = v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap, "saa6752hs", saa7134_boards[dev->board].empress_addr, NULL); - if (sd) - sd->grp_id = GRP_EMPRESS; + if (dev->empress_sd) + dev->empress_sd->grp_id = GRP_EMPRESS; } if (saa7134_boards[dev->board].rds_addr) { @@ -1046,6 +1046,7 @@ static int saa7134_initdev(struct pci_dev *pci_dev, printk(KERN_INFO "%s: Overlay support disabled.\n", dev->name); dev->video_dev = vdev_init(dev,&saa7134_video_template,"video"); + dev->video_dev->ctrl_handler = &dev->ctrl_handler; err = video_register_device(dev->video_dev,VFL_TYPE_GRABBER, video_nr[dev->nr]); if (err < 0) { @@ -1057,6 +1058,7 @@ static int saa7134_initdev(struct pci_dev *pci_dev, dev->name, video_device_node_name(dev->video_dev)); dev->vbi_dev = vdev_init(dev, &saa7134_video_template, "vbi"); + dev->vbi_dev->ctrl_handler = &dev->ctrl_handler; err = video_register_device(dev->vbi_dev,VFL_TYPE_VBI, vbi_nr[dev->nr]); @@ -1067,6 +1069,7 @@ static int saa7134_initdev(struct pci_dev *pci_dev, if (card_has_radio(dev)) { dev->radio_dev = vdev_init(dev,&saa7134_radio_template,"radio"); + dev->radio_dev->ctrl_handler = &dev->radio_ctrl_handler; err = video_register_device(dev->radio_dev,VFL_TYPE_RADIO, radio_nr[dev->nr]); if (err < 0) diff --git a/drivers/media/pci/saa7134/saa7134-empress.c b/drivers/media/pci/saa7134/saa7134-empress.c index 3022eb2a7925..0a9047e754b9 100644 --- a/drivers/media/pci/saa7134/saa7134-empress.c +++ b/drivers/media/pci/saa7134/saa7134-empress.c @@ -23,12 +23,12 @@ #include <linux/kernel.h> #include <linux/delay.h> +#include <media/v4l2-common.h> +#include <media/v4l2-event.h> + #include "saa7134-reg.h" #include "saa7134.h" -#include <media/saa6752hs.h> -#include <media/v4l2-common.h> - /* ------------------------------------------------------------------ */ MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); @@ -85,52 +85,54 @@ static int ts_open(struct file *file) { struct video_device *vdev = video_devdata(file); struct saa7134_dev *dev = video_drvdata(file); - int err; + struct saa7134_fh *fh; - dprintk("open dev=%s\n", video_device_node_name(vdev)); - err = -EBUSY; - if (!mutex_trylock(&dev->empress_tsq.vb_lock)) - return err; - if (atomic_read(&dev->empress_users)) - goto done; + /* allocate + initialize per filehandle data */ + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (NULL == fh) + return -ENOMEM; + + v4l2_fh_init(&fh->fh, vdev); + file->private_data = fh; + fh->is_empress = true; + v4l2_fh_add(&fh->fh); /* Unmute audio */ saa_writeb(SAA7134_AUDIO_MUTE_CTRL, saa_readb(SAA7134_AUDIO_MUTE_CTRL) & ~(1 << 6)); - atomic_inc(&dev->empress_users); - file->private_data = dev; - err = 0; - -done: - mutex_unlock(&dev->empress_tsq.vb_lock); - return err; + return 0; } static int ts_release(struct file *file) { - struct saa7134_dev *dev = file->private_data; + struct saa7134_dev *dev = video_drvdata(file); + struct saa7134_fh *fh = file->private_data; - videobuf_stop(&dev->empress_tsq); - videobuf_mmap_free(&dev->empress_tsq); + if (res_check(fh, RESOURCE_EMPRESS)) { + videobuf_stop(&dev->empress_tsq); + videobuf_mmap_free(&dev->empress_tsq); - /* stop the encoder */ - ts_reset_encoder(dev); + /* stop the encoder */ + ts_reset_encoder(dev); - /* Mute audio */ - saa_writeb(SAA7134_AUDIO_MUTE_CTRL, - saa_readb(SAA7134_AUDIO_MUTE_CTRL) | (1 << 6)); - - atomic_dec(&dev->empress_users); + /* Mute audio */ + saa_writeb(SAA7134_AUDIO_MUTE_CTRL, + saa_readb(SAA7134_AUDIO_MUTE_CTRL) | (1 << 6)); + } + v4l2_fh_del(&fh->fh); + v4l2_fh_exit(&fh->fh); return 0; } static ssize_t ts_read(struct file *file, char __user *data, size_t count, loff_t *ppos) { - struct saa7134_dev *dev = file->private_data; + struct saa7134_dev *dev = video_drvdata(file); + if (res_locked(dev, RESOURCE_EMPRESS)) + return -EBUSY; if (!dev->empress_started) ts_init_encoder(dev); @@ -142,68 +144,27 @@ ts_read(struct file *file, char __user *data, size_t count, loff_t *ppos) static unsigned int ts_poll(struct file *file, struct poll_table_struct *wait) { - struct saa7134_dev *dev = file->private_data; + unsigned long req_events = poll_requested_events(wait); + struct saa7134_dev *dev = video_drvdata(file); + struct saa7134_fh *fh = file->private_data; + unsigned int rc = 0; - return videobuf_poll_stream(file, &dev->empress_tsq, wait); + if (v4l2_event_pending(&fh->fh)) + rc = POLLPRI; + else if (req_events & POLLPRI) + poll_wait(file, &fh->fh.wait, wait); + return rc | videobuf_poll_stream(file, &dev->empress_tsq, wait); } static int ts_mmap(struct file *file, struct vm_area_struct * vma) { - struct saa7134_dev *dev = file->private_data; + struct saa7134_dev *dev = video_drvdata(file); return videobuf_mmap_mapper(&dev->empress_tsq, vma); } -/* - * This function is _not_ called directly, but from - * video_generic_ioctl (and maybe others). userspace - * copying is done already, arg is a kernel pointer. - */ - -static int empress_querycap(struct file *file, void *priv, - struct v4l2_capability *cap) -{ - struct saa7134_dev *dev = file->private_data; - - strcpy(cap->driver, "saa7134"); - strlcpy(cap->card, saa7134_boards[dev->board].name, - sizeof(cap->card)); - sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci)); - cap->capabilities = - V4L2_CAP_VIDEO_CAPTURE | - V4L2_CAP_READWRITE | - V4L2_CAP_STREAMING; - return 0; -} - -static int empress_enum_input(struct file *file, void *priv, - struct v4l2_input *i) -{ - if (i->index != 0) - return -EINVAL; - - i->type = V4L2_INPUT_TYPE_CAMERA; - strcpy(i->name, "CCIR656"); - - return 0; -} - -static int empress_g_input(struct file *file, void *priv, unsigned int *i) -{ - *i = 0; - return 0; -} - -static int empress_s_input(struct file *file, void *priv, unsigned int i) -{ - if (i != 0) - return -EINVAL; - - return 0; -} - static int empress_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f) { @@ -219,7 +180,7 @@ static int empress_enum_fmt_vid_cap(struct file *file, void *priv, static int empress_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { - struct saa7134_dev *dev = file->private_data; + struct saa7134_dev *dev = video_drvdata(file); struct v4l2_mbus_framefmt mbus_fmt; saa_call_all(dev, video, g_mbus_fmt, &mbus_fmt); @@ -236,7 +197,7 @@ static int empress_g_fmt_vid_cap(struct file *file, void *priv, static int empress_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { - struct saa7134_dev *dev = file->private_data; + struct saa7134_dev *dev = video_drvdata(file); struct v4l2_mbus_framefmt mbus_fmt; v4l2_fill_mbus_format(&mbus_fmt, &f->fmt.pix, V4L2_MBUS_FMT_FIXED); @@ -254,7 +215,7 @@ static int empress_s_fmt_vid_cap(struct file *file, void *priv, static int empress_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { - struct saa7134_dev *dev = file->private_data; + struct saa7134_dev *dev = video_drvdata(file); struct v4l2_mbus_framefmt mbus_fmt; v4l2_fill_mbus_format(&mbus_fmt, &f->fmt.pix, V4L2_MBUS_FMT_FIXED); @@ -269,175 +230,6 @@ static int empress_try_fmt_vid_cap(struct file *file, void *priv, return 0; } -static int empress_reqbufs(struct file *file, void *priv, - struct v4l2_requestbuffers *p) -{ - struct saa7134_dev *dev = file->private_data; - - return videobuf_reqbufs(&dev->empress_tsq, p); -} - -static int empress_querybuf(struct file *file, void *priv, - struct v4l2_buffer *b) -{ - struct saa7134_dev *dev = file->private_data; - - return videobuf_querybuf(&dev->empress_tsq, b); -} - -static int empress_qbuf(struct file *file, void *priv, struct v4l2_buffer *b) -{ - struct saa7134_dev *dev = file->private_data; - - return videobuf_qbuf(&dev->empress_tsq, b); -} - -static int empress_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b) -{ - struct saa7134_dev *dev = file->private_data; - - return videobuf_dqbuf(&dev->empress_tsq, b, - file->f_flags & O_NONBLOCK); -} - -static int empress_streamon(struct file *file, void *priv, - enum v4l2_buf_type type) -{ - struct saa7134_dev *dev = file->private_data; - - return videobuf_streamon(&dev->empress_tsq); -} - -static int empress_streamoff(struct file *file, void *priv, - enum v4l2_buf_type type) -{ - struct saa7134_dev *dev = file->private_data; - - return videobuf_streamoff(&dev->empress_tsq); -} - -static int empress_s_ext_ctrls(struct file *file, void *priv, - struct v4l2_ext_controls *ctrls) -{ - struct saa7134_dev *dev = file->private_data; - int err; - - /* count == 0 is abused in saa6752hs.c, so that special - case is handled here explicitly. */ - if (ctrls->count == 0) - return 0; - - if (ctrls->ctrl_class != V4L2_CTRL_CLASS_MPEG) - return -EINVAL; - - err = saa_call_empress(dev, core, s_ext_ctrls, ctrls); - ts_init_encoder(dev); - - return err; -} - -static int empress_g_ext_ctrls(struct file *file, void *priv, - struct v4l2_ext_controls *ctrls) -{ - struct saa7134_dev *dev = file->private_data; - - if (ctrls->ctrl_class != V4L2_CTRL_CLASS_MPEG) - return -EINVAL; - return saa_call_empress(dev, core, g_ext_ctrls, ctrls); -} - -static int empress_g_ctrl(struct file *file, void *priv, - struct v4l2_control *c) -{ - struct saa7134_dev *dev = file->private_data; - - return saa7134_g_ctrl_internal(dev, NULL, c); -} - -static int empress_s_ctrl(struct file *file, void *priv, - struct v4l2_control *c) -{ - struct saa7134_dev *dev = file->private_data; - - return saa7134_s_ctrl_internal(dev, NULL, c); -} - -static int empress_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *c) -{ - /* Must be sorted from low to high control ID! */ - static const u32 user_ctrls[] = { - V4L2_CID_USER_CLASS, - V4L2_CID_BRIGHTNESS, - V4L2_CID_CONTRAST, - V4L2_CID_SATURATION, - V4L2_CID_HUE, - V4L2_CID_AUDIO_VOLUME, - V4L2_CID_AUDIO_MUTE, - V4L2_CID_HFLIP, - 0 - }; - - /* Must be sorted from low to high control ID! */ - static const u32 mpeg_ctrls[] = { - V4L2_CID_MPEG_CLASS, - V4L2_CID_MPEG_STREAM_TYPE, - V4L2_CID_MPEG_STREAM_PID_PMT, - V4L2_CID_MPEG_STREAM_PID_AUDIO, - V4L2_CID_MPEG_STREAM_PID_VIDEO, - V4L2_CID_MPEG_STREAM_PID_PCR, - V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ, - V4L2_CID_MPEG_AUDIO_ENCODING, - V4L2_CID_MPEG_AUDIO_L2_BITRATE, - V4L2_CID_MPEG_VIDEO_ENCODING, - V4L2_CID_MPEG_VIDEO_ASPECT, - V4L2_CID_MPEG_VIDEO_BITRATE_MODE, - V4L2_CID_MPEG_VIDEO_BITRATE, - V4L2_CID_MPEG_VIDEO_BITRATE_PEAK, - 0 - }; - static const u32 *ctrl_classes[] = { - user_ctrls, - mpeg_ctrls, - NULL - }; - struct saa7134_dev *dev = file->private_data; - - c->id = v4l2_ctrl_next(ctrl_classes, c->id); - if (c->id == 0) - return -EINVAL; - if (c->id == V4L2_CID_USER_CLASS || c->id == V4L2_CID_MPEG_CLASS) - return v4l2_ctrl_query_fill(c, 0, 0, 0, 0); - if (V4L2_CTRL_ID2CLASS(c->id) != V4L2_CTRL_CLASS_MPEG) - return saa7134_queryctrl(file, priv, c); - return saa_call_empress(dev, core, queryctrl, c); -} - -static int empress_querymenu(struct file *file, void *priv, - struct v4l2_querymenu *c) -{ - struct saa7134_dev *dev = file->private_data; - - if (V4L2_CTRL_ID2CLASS(c->id) != V4L2_CTRL_CLASS_MPEG) - return -EINVAL; - return saa_call_empress(dev, core, querymenu, c); -} - -static int empress_s_std(struct file *file, void *priv, v4l2_std_id id) -{ - struct saa7134_dev *dev = file->private_data; - - return saa7134_s_std_internal(dev, NULL, id); -} - -static int empress_g_std(struct file *file, void *priv, v4l2_std_id *id) -{ - struct saa7134_dev *dev = file->private_data; - - *id = dev->tvnorm->id; - return 0; -} - static const struct v4l2_file_operations ts_fops = { .owner = THIS_MODULE, @@ -450,28 +242,29 @@ static const struct v4l2_file_operations ts_fops = }; static const struct v4l2_ioctl_ops ts_ioctl_ops = { - .vidioc_querycap = empress_querycap, + .vidioc_querycap = saa7134_querycap, .vidioc_enum_fmt_vid_cap = empress_enum_fmt_vid_cap, .vidioc_try_fmt_vid_cap = empress_try_fmt_vid_cap, .vidioc_s_fmt_vid_cap = empress_s_fmt_vid_cap, .vidioc_g_fmt_vid_cap = empress_g_fmt_vid_cap, - .vidioc_reqbufs = empress_reqbufs, - .vidioc_querybuf = empress_querybuf, - .vidioc_qbuf = empress_qbuf, - .vidioc_dqbuf = empress_dqbuf, - .vidioc_streamon = empress_streamon, - .vidioc_streamoff = empress_streamoff, - .vidioc_s_ext_ctrls = empress_s_ext_ctrls, - .vidioc_g_ext_ctrls = empress_g_ext_ctrls, - .vidioc_enum_input = empress_enum_input, - .vidioc_g_input = empress_g_input, - .vidioc_s_input = empress_s_input, - .vidioc_queryctrl = empress_queryctrl, - .vidioc_querymenu = empress_querymenu, - .vidioc_g_ctrl = empress_g_ctrl, - .vidioc_s_ctrl = empress_s_ctrl, - .vidioc_s_std = empress_s_std, - .vidioc_g_std = empress_g_std, + .vidioc_reqbufs = saa7134_reqbufs, + .vidioc_querybuf = saa7134_querybuf, + .vidioc_qbuf = saa7134_qbuf, + .vidioc_dqbuf = saa7134_dqbuf, + .vidioc_streamon = saa7134_streamon, + .vidioc_streamoff = saa7134_streamoff, + .vidioc_g_frequency = saa7134_g_frequency, + .vidioc_s_frequency = saa7134_s_frequency, + .vidioc_g_tuner = saa7134_g_tuner, + .vidioc_s_tuner = saa7134_s_tuner, + .vidioc_enum_input = saa7134_enum_input, + .vidioc_g_input = saa7134_g_input, + .vidioc_s_input = saa7134_s_input, + .vidioc_s_std = saa7134_s_std, + .vidioc_g_std = saa7134_g_std, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; /* ----------------------------------------------------------- */ @@ -501,9 +294,26 @@ static void empress_signal_change(struct saa7134_dev *dev) schedule_work(&dev->empress_workqueue); } +static bool empress_ctrl_filter(const struct v4l2_ctrl *ctrl) +{ + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + case V4L2_CID_HUE: + case V4L2_CID_CONTRAST: + case V4L2_CID_SATURATION: + case V4L2_CID_AUDIO_MUTE: + case V4L2_CID_AUDIO_VOLUME: + case V4L2_CID_PRIVATE_INVERT: + case V4L2_CID_PRIVATE_AUTOMUTE: + return true; + default: + return false; + } +} static int empress_init(struct saa7134_dev *dev) { + struct v4l2_ctrl_handler *hdl = &dev->empress_ctrl_handler; int err; dprintk("%s: %s\n",dev->name,__func__); @@ -516,6 +326,16 @@ static int empress_init(struct saa7134_dev *dev) snprintf(dev->empress_dev->name, sizeof(dev->empress_dev->name), "%s empress (%s)", dev->name, saa7134_boards[dev->board].name); + set_bit(V4L2_FL_USE_FH_PRIO, &dev->empress_dev->flags); + v4l2_ctrl_handler_init(hdl, 21); + v4l2_ctrl_add_handler(hdl, &dev->ctrl_handler, empress_ctrl_filter); + if (dev->empress_sd) + v4l2_ctrl_add_handler(hdl, dev->empress_sd->ctrl_handler, NULL); + if (hdl->error) { + video_device_release(dev->empress_dev); + return hdl->error; + } + dev->empress_dev->ctrl_handler = hdl; INIT_WORK(&dev->empress_workqueue, empress_signal_update); @@ -551,6 +371,7 @@ static int empress_fini(struct saa7134_dev *dev) return 0; flush_work(&dev->empress_workqueue); video_unregister_device(dev->empress_dev); + v4l2_ctrl_handler_free(&dev->empress_ctrl_handler); dev->empress_dev = NULL; return 0; } diff --git a/drivers/media/pci/saa7134/saa7134-vbi.c b/drivers/media/pci/saa7134/saa7134-vbi.c index e9aa94b807f1..d4da18d049f3 100644 --- a/drivers/media/pci/saa7134/saa7134-vbi.c +++ b/drivers/media/pci/saa7134/saa7134-vbi.c @@ -117,8 +117,7 @@ static int buffer_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, enum v4l2_field field) { - struct saa7134_fh *fh = q->priv_data; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = q->priv_data; struct saa7134_buf *buf = container_of(vb,struct saa7134_buf,vb); struct saa7134_tvnorm *norm = dev->tvnorm; unsigned int lines, llength, size; @@ -141,7 +140,7 @@ static int buffer_prepare(struct videobuf_queue *q, buf->vb.width = llength; buf->vb.height = lines; buf->vb.size = size; - buf->pt = &fh->pt_vbi; + buf->pt = &dev->pt_vbi; err = videobuf_iolock(q,&buf->vb,NULL); if (err) @@ -166,8 +165,7 @@ static int buffer_prepare(struct videobuf_queue *q, static int buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) { - struct saa7134_fh *fh = q->priv_data; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = q->priv_data; int llength,lines; lines = dev->tvnorm->vbi_v_stop_0 - dev->tvnorm->vbi_v_start_0 +1; @@ -181,8 +179,7 @@ buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) static void buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) { - struct saa7134_fh *fh = q->priv_data; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = q->priv_data; struct saa7134_buf *buf = container_of(vb,struct saa7134_buf,vb); saa7134_buffer_queue(dev,&dev->vbi_q,buf); diff --git a/drivers/media/pci/saa7134/saa7134-video.c b/drivers/media/pci/saa7134/saa7134-video.c index fb60da85bc2c..eb472b5b26a0 100644 --- a/drivers/media/pci/saa7134/saa7134-video.c +++ b/drivers/media/pci/saa7134/saa7134-video.c @@ -27,11 +27,13 @@ #include <linux/slab.h> #include <linux/sort.h> -#include "saa7134-reg.h" -#include "saa7134.h" #include <media/v4l2-common.h> +#include <media/v4l2-event.h> #include <media/saa6588.h> +#include "saa7134-reg.h" +#include "saa7134.h" + /* ------------------------------------------------------------------ */ unsigned int video_debug; @@ -369,117 +371,6 @@ static struct saa7134_tvnorm tvnorms[] = { }; #define TVNORMS ARRAY_SIZE(tvnorms) -#define V4L2_CID_PRIVATE_INVERT (V4L2_CID_PRIVATE_BASE + 0) -#define V4L2_CID_PRIVATE_Y_ODD (V4L2_CID_PRIVATE_BASE + 1) -#define V4L2_CID_PRIVATE_Y_EVEN (V4L2_CID_PRIVATE_BASE + 2) -#define V4L2_CID_PRIVATE_AUTOMUTE (V4L2_CID_PRIVATE_BASE + 3) -#define V4L2_CID_PRIVATE_LASTP1 (V4L2_CID_PRIVATE_BASE + 4) - -static const struct v4l2_queryctrl no_ctrl = { - .name = "42", - .flags = V4L2_CTRL_FLAG_DISABLED, -}; -static const struct v4l2_queryctrl video_ctrls[] = { - /* --- video --- */ - { - .id = V4L2_CID_BRIGHTNESS, - .name = "Brightness", - .minimum = 0, - .maximum = 255, - .step = 1, - .default_value = 128, - .type = V4L2_CTRL_TYPE_INTEGER, - },{ - .id = V4L2_CID_CONTRAST, - .name = "Contrast", - .minimum = 0, - .maximum = 127, - .step = 1, - .default_value = 68, - .type = V4L2_CTRL_TYPE_INTEGER, - },{ - .id = V4L2_CID_SATURATION, - .name = "Saturation", - .minimum = 0, - .maximum = 127, - .step = 1, - .default_value = 64, - .type = V4L2_CTRL_TYPE_INTEGER, - },{ - .id = V4L2_CID_HUE, - .name = "Hue", - .minimum = -128, - .maximum = 127, - .step = 1, - .default_value = 0, - .type = V4L2_CTRL_TYPE_INTEGER, - },{ - .id = V4L2_CID_HFLIP, - .name = "Mirror", - .minimum = 0, - .maximum = 1, - .type = V4L2_CTRL_TYPE_BOOLEAN, - }, - /* --- audio --- */ - { - .id = V4L2_CID_AUDIO_MUTE, - .name = "Mute", - .minimum = 0, - .maximum = 1, - .type = V4L2_CTRL_TYPE_BOOLEAN, - },{ - .id = V4L2_CID_AUDIO_VOLUME, - .name = "Volume", - .minimum = -15, - .maximum = 15, - .step = 1, - .default_value = 0, - .type = V4L2_CTRL_TYPE_INTEGER, - }, - /* --- private --- */ - { - .id = V4L2_CID_PRIVATE_INVERT, - .name = "Invert", - .minimum = 0, - .maximum = 1, - .type = V4L2_CTRL_TYPE_BOOLEAN, - },{ - .id = V4L2_CID_PRIVATE_Y_ODD, - .name = "y offset odd field", - .minimum = 0, - .maximum = 128, - .step = 1, - .default_value = 0, - .type = V4L2_CTRL_TYPE_INTEGER, - },{ - .id = V4L2_CID_PRIVATE_Y_EVEN, - .name = "y offset even field", - .minimum = 0, - .maximum = 128, - .step = 1, - .default_value = 0, - .type = V4L2_CTRL_TYPE_INTEGER, - },{ - .id = V4L2_CID_PRIVATE_AUTOMUTE, - .name = "automute", - .minimum = 0, - .maximum = 1, - .default_value = 1, - .type = V4L2_CTRL_TYPE_BOOLEAN, - } -}; -static const unsigned int CTRLS = ARRAY_SIZE(video_ctrls); - -static const struct v4l2_queryctrl* ctrl_by_id(unsigned int id) -{ - unsigned int i; - - for (i = 0; i < CTRLS; i++) - if (video_ctrls[i].id == id) - return video_ctrls+i; - return NULL; -} - static struct saa7134_format* format_by_fourcc(unsigned int fourcc) { unsigned int i; @@ -514,16 +405,6 @@ static int res_get(struct saa7134_dev *dev, struct saa7134_fh *fh, unsigned int return 1; } -static int res_check(struct saa7134_fh *fh, unsigned int bit) -{ - return (fh->resources & bit); -} - -static int res_locked(struct saa7134_dev *dev, unsigned int bit) -{ - return (dev->resources & bit); -} - static void res_free(struct saa7134_dev *dev, struct saa7134_fh *fh, unsigned int bits) { @@ -868,7 +749,7 @@ static int verify_preview(struct saa7134_dev *dev, struct v4l2_window *win, bool return 0; } -static int start_preview(struct saa7134_dev *dev, struct saa7134_fh *fh) +static int start_preview(struct saa7134_dev *dev) { unsigned long base,control,bpl; int err; @@ -923,7 +804,7 @@ static int start_preview(struct saa7134_dev *dev, struct saa7134_fh *fh) return 0; } -static int stop_preview(struct saa7134_dev *dev, struct saa7134_fh *fh) +static int stop_preview(struct saa7134_dev *dev) { dev->ovenable = 0; saa7134_set_dmabits(dev); @@ -1018,8 +899,7 @@ static int buffer_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, enum v4l2_field field) { - struct saa7134_fh *fh = q->priv_data; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = q->priv_data; struct saa7134_buf *buf = container_of(vb,struct saa7134_buf,vb); unsigned int size; int err; @@ -1057,7 +937,7 @@ static int buffer_prepare(struct videobuf_queue *q, buf->vb.size = size; buf->vb.field = field; buf->fmt = dev->fmt; - buf->pt = &fh->pt_cap; + buf->pt = &dev->pt_cap; dev->video_q.curr = NULL; err = videobuf_iolock(q,&buf->vb,&dev->ovbuf); @@ -1082,8 +962,7 @@ static int buffer_prepare(struct videobuf_queue *q, static int buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) { - struct saa7134_fh *fh = q->priv_data; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = q->priv_data; *size = dev->fmt->depth * dev->width * dev->height >> 3; if (0 == *count) @@ -1094,10 +973,10 @@ buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) static void buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) { - struct saa7134_fh *fh = q->priv_data; + struct saa7134_dev *dev = q->priv_data; struct saa7134_buf *buf = container_of(vb,struct saa7134_buf,vb); - saa7134_buffer_queue(fh->dev,&fh->dev->video_q,buf); + saa7134_buffer_queue(dev, &dev->video_q, buf); } static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) @@ -1116,133 +995,56 @@ static struct videobuf_queue_ops video_qops = { /* ------------------------------------------------------------------ */ -int saa7134_g_ctrl_internal(struct saa7134_dev *dev, struct saa7134_fh *fh, struct v4l2_control *c) +static int saa7134_s_ctrl(struct v4l2_ctrl *ctrl) { - const struct v4l2_queryctrl* ctrl; - - ctrl = ctrl_by_id(c->id); - if (NULL == ctrl) - return -EINVAL; - switch (c->id) { - case V4L2_CID_BRIGHTNESS: - c->value = dev->ctl_bright; - break; - case V4L2_CID_HUE: - c->value = dev->ctl_hue; - break; - case V4L2_CID_CONTRAST: - c->value = dev->ctl_contrast; - break; - case V4L2_CID_SATURATION: - c->value = dev->ctl_saturation; - break; - case V4L2_CID_AUDIO_MUTE: - c->value = dev->ctl_mute; - break; - case V4L2_CID_AUDIO_VOLUME: - c->value = dev->ctl_volume; - break; - case V4L2_CID_PRIVATE_INVERT: - c->value = dev->ctl_invert; - break; - case V4L2_CID_HFLIP: - c->value = dev->ctl_mirror; - break; - case V4L2_CID_PRIVATE_Y_EVEN: - c->value = dev->ctl_y_even; - break; - case V4L2_CID_PRIVATE_Y_ODD: - c->value = dev->ctl_y_odd; - break; - case V4L2_CID_PRIVATE_AUTOMUTE: - c->value = dev->ctl_automute; - break; - default: - return -EINVAL; - } - return 0; -} -EXPORT_SYMBOL_GPL(saa7134_g_ctrl_internal); - -static int saa7134_g_ctrl(struct file *file, void *priv, struct v4l2_control *c) -{ - struct saa7134_fh *fh = priv; - - return saa7134_g_ctrl_internal(fh->dev, fh, c); -} - -int saa7134_s_ctrl_internal(struct saa7134_dev *dev, struct saa7134_fh *fh, struct v4l2_control *c) -{ - const struct v4l2_queryctrl* ctrl; + struct saa7134_dev *dev = container_of(ctrl->handler, struct saa7134_dev, ctrl_handler); unsigned long flags; int restart_overlay = 0; - int err; - err = -EINVAL; - - mutex_lock(&dev->lock); - - ctrl = ctrl_by_id(c->id); - if (NULL == ctrl) - goto error; - - dprintk("set_control name=%s val=%d\n",ctrl->name,c->value); - switch (ctrl->type) { - case V4L2_CTRL_TYPE_BOOLEAN: - case V4L2_CTRL_TYPE_MENU: - case V4L2_CTRL_TYPE_INTEGER: - if (c->value < ctrl->minimum) - c->value = ctrl->minimum; - if (c->value > ctrl->maximum) - c->value = ctrl->maximum; - break; - default: - /* nothing */; - } - switch (c->id) { + switch (ctrl->id) { case V4L2_CID_BRIGHTNESS: - dev->ctl_bright = c->value; - saa_writeb(SAA7134_DEC_LUMA_BRIGHT, dev->ctl_bright); + dev->ctl_bright = ctrl->val; + saa_writeb(SAA7134_DEC_LUMA_BRIGHT, ctrl->val); break; case V4L2_CID_HUE: - dev->ctl_hue = c->value; - saa_writeb(SAA7134_DEC_CHROMA_HUE, dev->ctl_hue); + dev->ctl_hue = ctrl->val; + saa_writeb(SAA7134_DEC_CHROMA_HUE, ctrl->val); break; case V4L2_CID_CONTRAST: - dev->ctl_contrast = c->value; + dev->ctl_contrast = ctrl->val; saa_writeb(SAA7134_DEC_LUMA_CONTRAST, dev->ctl_invert ? -dev->ctl_contrast : dev->ctl_contrast); break; case V4L2_CID_SATURATION: - dev->ctl_saturation = c->value; + dev->ctl_saturation = ctrl->val; saa_writeb(SAA7134_DEC_CHROMA_SATURATION, dev->ctl_invert ? -dev->ctl_saturation : dev->ctl_saturation); break; case V4L2_CID_AUDIO_MUTE: - dev->ctl_mute = c->value; + dev->ctl_mute = ctrl->val; saa7134_tvaudio_setmute(dev); break; case V4L2_CID_AUDIO_VOLUME: - dev->ctl_volume = c->value; + dev->ctl_volume = ctrl->val; saa7134_tvaudio_setvolume(dev,dev->ctl_volume); break; case V4L2_CID_PRIVATE_INVERT: - dev->ctl_invert = c->value; + dev->ctl_invert = ctrl->val; saa_writeb(SAA7134_DEC_LUMA_CONTRAST, dev->ctl_invert ? -dev->ctl_contrast : dev->ctl_contrast); saa_writeb(SAA7134_DEC_CHROMA_SATURATION, dev->ctl_invert ? -dev->ctl_saturation : dev->ctl_saturation); break; case V4L2_CID_HFLIP: - dev->ctl_mirror = c->value; + dev->ctl_mirror = ctrl->val; restart_overlay = 1; break; case V4L2_CID_PRIVATE_Y_EVEN: - dev->ctl_y_even = c->value; + dev->ctl_y_even = ctrl->val; restart_overlay = 1; break; case V4L2_CID_PRIVATE_Y_ODD: - dev->ctl_y_odd = c->value; + dev->ctl_y_odd = ctrl->val; restart_overlay = 1; break; case V4L2_CID_PRIVATE_AUTOMUTE: @@ -1252,7 +1054,7 @@ int saa7134_s_ctrl_internal(struct saa7134_dev *dev, struct saa7134_fh *fh, str tda9887_cfg.tuner = TUNER_TDA9887; tda9887_cfg.priv = &dev->tda9887_conf; - dev->ctl_automute = c->value; + dev->ctl_automute = ctrl->val; if (dev->tda9887_conf) { if (dev->ctl_automute) dev->tda9887_conf |= TDA9887_AUTOMUTE; @@ -1264,27 +1066,15 @@ int saa7134_s_ctrl_internal(struct saa7134_dev *dev, struct saa7134_fh *fh, str break; } default: - goto error; + return -EINVAL; } - if (restart_overlay && fh && res_check(fh, RESOURCE_OVERLAY)) { - spin_lock_irqsave(&dev->slock,flags); - stop_preview(dev,fh); - start_preview(dev,fh); - spin_unlock_irqrestore(&dev->slock,flags); + if (restart_overlay && res_locked(dev, RESOURCE_OVERLAY)) { + spin_lock_irqsave(&dev->slock, flags); + stop_preview(dev); + start_preview(dev); + spin_unlock_irqrestore(&dev->slock, flags); } - err = 0; - -error: - mutex_unlock(&dev->lock); - return err; -} -EXPORT_SYMBOL_GPL(saa7134_s_ctrl_internal); - -static int saa7134_s_ctrl(struct file *file, void *f, struct v4l2_control *c) -{ - struct saa7134_fh *fh = f; - - return saa7134_s_ctrl_internal(fh->dev, fh, c); + return 0; } /* ------------------------------------------------------------------ */ @@ -1292,15 +1082,16 @@ static int saa7134_s_ctrl(struct file *file, void *f, struct v4l2_control *c) static struct videobuf_queue *saa7134_queue(struct file *file) { struct video_device *vdev = video_devdata(file); + struct saa7134_dev *dev = video_drvdata(file); struct saa7134_fh *fh = file->private_data; struct videobuf_queue *q = NULL; switch (vdev->vfl_type) { case VFL_TYPE_GRABBER: - q = &fh->cap; + q = fh->is_empress ? &dev->empress_tsq : &dev->cap; break; case VFL_TYPE_VBI: - q = &fh->vbi; + q = &dev->vbi; break; default: BUG(); @@ -1311,9 +1102,10 @@ static struct videobuf_queue *saa7134_queue(struct file *file) static int saa7134_resource(struct file *file) { struct video_device *vdev = video_devdata(file); + struct saa7134_fh *fh = file->private_data; if (vdev->vfl_type == VFL_TYPE_GRABBER) - return RESOURCE_VIDEO; + return fh->is_empress ? RESOURCE_EMPRESS : RESOURCE_VIDEO; if (vdev->vfl_type == VFL_TYPE_VBI) return RESOURCE_VBI; @@ -1335,22 +1127,6 @@ static int video_open(struct file *file) v4l2_fh_init(&fh->fh, vdev); file->private_data = fh; - fh->dev = dev; - - videobuf_queue_sg_init(&fh->cap, &video_qops, - &dev->pci->dev, &dev->slock, - V4L2_BUF_TYPE_VIDEO_CAPTURE, - V4L2_FIELD_INTERLACED, - sizeof(struct saa7134_buf), - fh, NULL); - videobuf_queue_sg_init(&fh->vbi, &saa7134_vbi_qops, - &dev->pci->dev, &dev->slock, - V4L2_BUF_TYPE_VBI_CAPTURE, - V4L2_FIELD_SEQ_TB, - sizeof(struct saa7134_buf), - fh, NULL); - saa7134_pgtable_alloc(dev->pci,&fh->pt_cap); - saa7134_pgtable_alloc(dev->pci,&fh->pt_vbi); if (vdev->vfl_type == VFL_TYPE_RADIO) { /* switch to radio mode */ @@ -1369,17 +1145,18 @@ static ssize_t video_read(struct file *file, char __user *data, size_t count, loff_t *ppos) { struct video_device *vdev = video_devdata(file); + struct saa7134_dev *dev = video_drvdata(file); struct saa7134_fh *fh = file->private_data; switch (vdev->vfl_type) { case VFL_TYPE_GRABBER: - if (res_locked(fh->dev,RESOURCE_VIDEO)) + if (res_locked(dev, RESOURCE_VIDEO)) return -EBUSY; return videobuf_read_one(saa7134_queue(file), data, count, ppos, file->f_flags & O_NONBLOCK); case VFL_TYPE_VBI: - if (!res_get(fh->dev,fh,RESOURCE_VBI)) + if (!res_get(dev, fh, RESOURCE_VBI)) return -EBUSY; return videobuf_read_stream(saa7134_queue(file), data, count, ppos, 1, @@ -1394,52 +1171,59 @@ video_read(struct file *file, char __user *data, size_t count, loff_t *ppos) static unsigned int video_poll(struct file *file, struct poll_table_struct *wait) { + unsigned long req_events = poll_requested_events(wait); struct video_device *vdev = video_devdata(file); + struct saa7134_dev *dev = video_drvdata(file); struct saa7134_fh *fh = file->private_data; struct videobuf_buffer *buf = NULL; unsigned int rc = 0; + if (v4l2_event_pending(&fh->fh)) + rc = POLLPRI; + else if (req_events & POLLPRI) + poll_wait(file, &fh->fh.wait, wait); + if (vdev->vfl_type == VFL_TYPE_VBI) - return videobuf_poll_stream(file, &fh->vbi, wait); + return rc | videobuf_poll_stream(file, &dev->vbi, wait); - if (res_check(fh,RESOURCE_VIDEO)) { - mutex_lock(&fh->cap.vb_lock); - if (!list_empty(&fh->cap.stream)) - buf = list_entry(fh->cap.stream.next, struct videobuf_buffer, stream); + if (res_check(fh, RESOURCE_VIDEO)) { + mutex_lock(&dev->cap.vb_lock); + if (!list_empty(&dev->cap.stream)) + buf = list_entry(dev->cap.stream.next, struct videobuf_buffer, stream); } else { - mutex_lock(&fh->cap.vb_lock); - if (UNSET == fh->cap.read_off) { + mutex_lock(&dev->cap.vb_lock); + if (UNSET == dev->cap.read_off) { /* need to capture a new frame */ - if (res_locked(fh->dev,RESOURCE_VIDEO)) + if (res_locked(dev, RESOURCE_VIDEO)) goto err; - if (0 != fh->cap.ops->buf_prepare(&fh->cap,fh->cap.read_buf,fh->cap.field)) + if (0 != dev->cap.ops->buf_prepare(&dev->cap, + dev->cap.read_buf, dev->cap.field)) goto err; - fh->cap.ops->buf_queue(&fh->cap,fh->cap.read_buf); - fh->cap.read_off = 0; + dev->cap.ops->buf_queue(&dev->cap, dev->cap.read_buf); + dev->cap.read_off = 0; } - buf = fh->cap.read_buf; + buf = dev->cap.read_buf; } if (!buf) goto err; poll_wait(file, &buf->done, wait); - if (buf->state == VIDEOBUF_DONE || - buf->state == VIDEOBUF_ERROR) - rc = POLLIN|POLLRDNORM; - mutex_unlock(&fh->cap.vb_lock); + if (buf->state == VIDEOBUF_DONE || buf->state == VIDEOBUF_ERROR) + rc |= POLLIN | POLLRDNORM; + mutex_unlock(&dev->cap.vb_lock); return rc; err: - mutex_unlock(&fh->cap.vb_lock); - return POLLERR; + mutex_unlock(&dev->cap.vb_lock); + return rc | POLLERR; } static int video_release(struct file *file) { struct video_device *vdev = video_devdata(file); - struct saa7134_fh *fh = file->private_data; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); + struct saa7134_fh *fh = file->private_data; struct saa6588_command cmd; unsigned long flags; @@ -1448,26 +1232,28 @@ static int video_release(struct file *file) /* turn off overlay */ if (res_check(fh, RESOURCE_OVERLAY)) { spin_lock_irqsave(&dev->slock,flags); - stop_preview(dev,fh); + stop_preview(dev); spin_unlock_irqrestore(&dev->slock,flags); - res_free(dev,fh,RESOURCE_OVERLAY); + res_free(dev, fh, RESOURCE_OVERLAY); } /* stop video capture */ if (res_check(fh, RESOURCE_VIDEO)) { pm_qos_remove_request(&dev->qos_request); - videobuf_streamoff(&fh->cap); - res_free(dev,fh,RESOURCE_VIDEO); + videobuf_streamoff(&dev->cap); + res_free(dev, fh, RESOURCE_VIDEO); + videobuf_mmap_free(&dev->cap); } - if (fh->cap.read_buf) { - buffer_release(&fh->cap,fh->cap.read_buf); - kfree(fh->cap.read_buf); + if (dev->cap.read_buf) { + buffer_release(&dev->cap, dev->cap.read_buf); + kfree(dev->cap.read_buf); } /* stop vbi capture */ if (res_check(fh, RESOURCE_VBI)) { - videobuf_stop(&fh->vbi); - res_free(dev,fh,RESOURCE_VBI); + videobuf_stop(&dev->vbi); + res_free(dev, fh, RESOURCE_VBI); + videobuf_mmap_free(&dev->vbi); } /* ts-capture will not work in planar mode, so turn it off Hac: 04.05*/ @@ -1480,12 +1266,6 @@ static int video_release(struct file *file) if (vdev->vfl_type == VFL_TYPE_RADIO) saa_call_all(dev, core, ioctl, SAA6588_CMD_CLOSE, &cmd); - /* free stuff */ - videobuf_mmap_free(&fh->cap); - videobuf_mmap_free(&fh->vbi); - saa7134_pgtable_free(dev->pci,&fh->pt_cap); - saa7134_pgtable_free(dev->pci,&fh->pt_vbi); - v4l2_fh_del(&fh->fh); v4l2_fh_exit(&fh->fh); file->private_data = NULL; @@ -1501,11 +1281,11 @@ static int video_mmap(struct file *file, struct vm_area_struct * vma) static ssize_t radio_read(struct file *file, char __user *data, size_t count, loff_t *ppos) { - struct saa7134_fh *fh = file->private_data; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); struct saa6588_command cmd; cmd.block_count = count/3; + cmd.nonblocking = file->f_flags & O_NONBLOCK; cmd.buffer = data; cmd.instance = file; cmd.result = -ENODEV; @@ -1517,16 +1297,16 @@ static ssize_t radio_read(struct file *file, char __user *data, static unsigned int radio_poll(struct file *file, poll_table *wait) { - struct saa7134_fh *fh = file->private_data; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); struct saa6588_command cmd; + unsigned int rc = v4l2_ctrl_poll(file, wait); cmd.instance = file; cmd.event_list = wait; - cmd.result = -ENODEV; + cmd.result = 0; saa_call_all(dev, core, ioctl, SAA6588_CMD_POLL, &cmd); - return cmd.result; + return rc | cmd.result; } /* ------------------------------------------------------------------ */ @@ -1534,8 +1314,7 @@ static unsigned int radio_poll(struct file *file, poll_table *wait) static int saa7134_try_get_set_fmt_vbi_cap(struct file *file, void *priv, struct v4l2_format *f) { - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); struct saa7134_tvnorm *norm = dev->tvnorm; memset(&f->fmt.vbi.reserved, 0, sizeof(f->fmt.vbi.reserved)); @@ -1555,12 +1334,11 @@ static int saa7134_try_get_set_fmt_vbi_cap(struct file *file, void *priv, static int saa7134_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); f->fmt.pix.width = dev->width; f->fmt.pix.height = dev->height; - f->fmt.pix.field = fh->cap.field; + f->fmt.pix.field = dev->cap.field; f->fmt.pix.pixelformat = dev->fmt->fourcc; f->fmt.pix.bytesperline = (f->fmt.pix.width * dev->fmt->depth) >> 3; @@ -1574,8 +1352,7 @@ static int saa7134_g_fmt_vid_cap(struct file *file, void *priv, static int saa7134_g_fmt_vid_overlay(struct file *file, void *priv, struct v4l2_format *f) { - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); struct v4l2_clip __user *clips = f->fmt.win.clips; u32 clipcount = f->fmt.win.clipcount; int err = 0; @@ -1607,8 +1384,7 @@ static int saa7134_g_fmt_vid_overlay(struct file *file, void *priv, static int saa7134_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); struct saa7134_format *fmt; enum v4l2_field field; unsigned int maxw, maxh; @@ -1659,8 +1435,7 @@ static int saa7134_try_fmt_vid_cap(struct file *file, void *priv, static int saa7134_try_fmt_vid_overlay(struct file *file, void *priv, struct v4l2_format *f) { - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); if (saa7134_no_overlay > 0) { printk(KERN_ERR "V4L2_BUF_TYPE_VIDEO_OVERLAY: no_overlay\n"); @@ -1675,8 +1450,7 @@ static int saa7134_try_fmt_vid_overlay(struct file *file, void *priv, static int saa7134_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); int err; err = saa7134_try_fmt_vid_cap(file, priv, f); @@ -1686,15 +1460,14 @@ static int saa7134_s_fmt_vid_cap(struct file *file, void *priv, dev->fmt = format_by_fourcc(f->fmt.pix.pixelformat); dev->width = f->fmt.pix.width; dev->height = f->fmt.pix.height; - fh->cap.field = f->fmt.pix.field; + dev->cap.field = f->fmt.pix.field; return 0; } static int saa7134_s_fmt_vid_overlay(struct file *file, void *priv, struct v4l2_format *f) { - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); int err; unsigned long flags; @@ -1719,10 +1492,10 @@ static int saa7134_s_fmt_vid_overlay(struct file *file, void *priv, return -EFAULT; } - if (res_check(fh, RESOURCE_OVERLAY)) { + if (res_check(priv, RESOURCE_OVERLAY)) { spin_lock_irqsave(&dev->slock, flags); - stop_preview(dev, fh); - start_preview(dev, fh); + stop_preview(dev); + start_preview(dev); spin_unlock_irqrestore(&dev->slock, flags); } @@ -1730,26 +1503,9 @@ static int saa7134_s_fmt_vid_overlay(struct file *file, void *priv, return 0; } -int saa7134_queryctrl(struct file *file, void *priv, struct v4l2_queryctrl *c) -{ - const struct v4l2_queryctrl *ctrl; - - if ((c->id < V4L2_CID_BASE || - c->id >= V4L2_CID_LASTP1) && - (c->id < V4L2_CID_PRIVATE_BASE || - c->id >= V4L2_CID_PRIVATE_LASTP1)) - return -EINVAL; - ctrl = ctrl_by_id(c->id); - *c = (NULL != ctrl) ? *ctrl : no_ctrl; - return 0; -} -EXPORT_SYMBOL_GPL(saa7134_queryctrl); - -static int saa7134_enum_input(struct file *file, void *priv, - struct v4l2_input *i) +int saa7134_enum_input(struct file *file, void *priv, struct v4l2_input *i) { - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); unsigned int n; n = i->index; @@ -1769,27 +1525,27 @@ static int saa7134_enum_input(struct file *file, void *priv, if (0 != (v1 & 0x40)) i->status |= V4L2_IN_ST_NO_H_LOCK; if (0 != (v2 & 0x40)) - i->status |= V4L2_IN_ST_NO_SYNC; + i->status |= V4L2_IN_ST_NO_SIGNAL; if (0 != (v2 & 0x0e)) i->status |= V4L2_IN_ST_MACROVISION; } i->std = SAA7134_NORMS; return 0; } +EXPORT_SYMBOL_GPL(saa7134_enum_input); -static int saa7134_g_input(struct file *file, void *priv, unsigned int *i) +int saa7134_g_input(struct file *file, void *priv, unsigned int *i) { - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); *i = dev->ctl_input; return 0; } +EXPORT_SYMBOL_GPL(saa7134_g_input); -static int saa7134_s_input(struct file *file, void *priv, unsigned int i) +int saa7134_s_input(struct file *file, void *priv, unsigned int i) { - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); if (i >= SAA7134_INPUT_MAX) return -EINVAL; @@ -1800,13 +1556,14 @@ static int saa7134_s_input(struct file *file, void *priv, unsigned int i) mutex_unlock(&dev->lock); return 0; } +EXPORT_SYMBOL_GPL(saa7134_s_input); -static int saa7134_querycap(struct file *file, void *priv, +int saa7134_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); struct video_device *vdev = video_devdata(file); + struct saa7134_fh *fh = priv; u32 radio_caps, video_caps, vbi_caps; unsigned int tuner_type = dev->tuner_type; @@ -1825,7 +1582,7 @@ static int saa7134_querycap(struct file *file, void *priv, radio_caps |= V4L2_CAP_RDS_CAPTURE; video_caps = V4L2_CAP_VIDEO_CAPTURE; - if (saa7134_no_overlay <= 0) + if (saa7134_no_overlay <= 0 && !fh->is_empress) video_caps |= V4L2_CAP_VIDEO_OVERLAY; vbi_caps = V4L2_CAP_VBI_CAPTURE; @@ -1851,14 +1608,17 @@ static int saa7134_querycap(struct file *file, void *priv, return 0; } +EXPORT_SYMBOL_GPL(saa7134_querycap); -int saa7134_s_std_internal(struct saa7134_dev *dev, struct saa7134_fh *fh, v4l2_std_id id) +int saa7134_s_std(struct file *file, void *priv, v4l2_std_id id) { + struct saa7134_dev *dev = video_drvdata(file); + struct saa7134_fh *fh = priv; unsigned long flags; unsigned int i; v4l2_std_id fixup; - if (!fh && res_locked(dev, RESOURCE_OVERLAY)) { + if (fh->is_empress && res_locked(dev, RESOURCE_OVERLAY)) { /* Don't change the std from the mpeg device if overlay is active. */ return -EBUSY; @@ -1898,15 +1658,15 @@ int saa7134_s_std_internal(struct saa7134_dev *dev, struct saa7134_fh *fh, v4l2_ id = tvnorms[i].id; mutex_lock(&dev->lock); - if (fh && res_check(fh, RESOURCE_OVERLAY)) { + if (!fh->is_empress && res_check(fh, RESOURCE_OVERLAY)) { spin_lock_irqsave(&dev->slock, flags); - stop_preview(dev, fh); + stop_preview(dev); spin_unlock_irqrestore(&dev->slock, flags); set_tvnorm(dev, &tvnorms[i]); spin_lock_irqsave(&dev->slock, flags); - start_preview(dev, fh); + start_preview(dev); spin_unlock_irqrestore(&dev->slock, flags); } else set_tvnorm(dev, &tvnorms[i]); @@ -1915,29 +1675,21 @@ int saa7134_s_std_internal(struct saa7134_dev *dev, struct saa7134_fh *fh, v4l2_ mutex_unlock(&dev->lock); return 0; } -EXPORT_SYMBOL_GPL(saa7134_s_std_internal); +EXPORT_SYMBOL_GPL(saa7134_s_std); -static int saa7134_s_std(struct file *file, void *priv, v4l2_std_id id) +int saa7134_g_std(struct file *file, void *priv, v4l2_std_id *id) { - struct saa7134_fh *fh = priv; - - return saa7134_s_std_internal(fh->dev, fh, id); -} - -static int saa7134_g_std(struct file *file, void *priv, v4l2_std_id *id) -{ - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); *id = dev->tvnorm->id; return 0; } +EXPORT_SYMBOL_GPL(saa7134_g_std); static int saa7134_cropcap(struct file *file, void *priv, struct v4l2_cropcap *cap) { - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); if (cap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && cap->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) @@ -1959,8 +1711,7 @@ static int saa7134_cropcap(struct file *file, void *priv, static int saa7134_g_crop(struct file *file, void *f, struct v4l2_crop *crop) { - struct saa7134_fh *fh = f; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) @@ -1971,22 +1722,17 @@ static int saa7134_g_crop(struct file *file, void *f, struct v4l2_crop *crop) static int saa7134_s_crop(struct file *file, void *f, const struct v4l2_crop *crop) { - struct saa7134_fh *fh = f; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); struct v4l2_rect *b = &dev->crop_bounds; struct v4l2_rect *c = &dev->crop_current; if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) return -EINVAL; - if (crop->c.height < 0) - return -EINVAL; - if (crop->c.width < 0) - return -EINVAL; - if (res_locked(fh->dev, RESOURCE_OVERLAY)) + if (res_locked(dev, RESOURCE_OVERLAY)) return -EBUSY; - if (res_locked(fh->dev, RESOURCE_VIDEO)) + if (res_locked(dev, RESOURCE_VIDEO)) return -EBUSY; *c = crop->c; @@ -2006,11 +1752,10 @@ static int saa7134_s_crop(struct file *file, void *f, const struct v4l2_crop *cr return 0; } -static int saa7134_g_tuner(struct file *file, void *priv, +int saa7134_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t) { - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); int n; if (0 != t->index) @@ -2037,12 +1782,12 @@ static int saa7134_g_tuner(struct file *file, void *priv, t->signal = 0xffff; return 0; } +EXPORT_SYMBOL_GPL(saa7134_g_tuner); -static int saa7134_s_tuner(struct file *file, void *priv, +int saa7134_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *t) { - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); int rx, mode; if (0 != t->index) @@ -2058,12 +1803,12 @@ static int saa7134_s_tuner(struct file *file, void *priv, return 0; } +EXPORT_SYMBOL_GPL(saa7134_s_tuner); -static int saa7134_g_frequency(struct file *file, void *priv, +int saa7134_g_frequency(struct file *file, void *priv, struct v4l2_frequency *f) { - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); if (0 != f->tuner) return -EINVAL; @@ -2072,12 +1817,12 @@ static int saa7134_g_frequency(struct file *file, void *priv, return 0; } +EXPORT_SYMBOL_GPL(saa7134_g_frequency); -static int saa7134_s_frequency(struct file *file, void *priv, +int saa7134_s_frequency(struct file *file, void *priv, const struct v4l2_frequency *f) { - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); if (0 != f->tuner) return -EINVAL; @@ -2089,6 +1834,7 @@ static int saa7134_s_frequency(struct file *file, void *priv, mutex_unlock(&dev->lock); return 0; } +EXPORT_SYMBOL_GPL(saa7134_s_frequency); static int saa7134_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f) @@ -2126,8 +1872,7 @@ static int saa7134_enum_fmt_vid_overlay(struct file *file, void *priv, static int saa7134_g_fbuf(struct file *file, void *f, struct v4l2_framebuffer *fb) { - struct saa7134_fh *fh = f; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); *fb = dev->ovbuf; fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING; @@ -2138,8 +1883,7 @@ static int saa7134_g_fbuf(struct file *file, void *f, static int saa7134_s_fbuf(struct file *file, void *f, const struct v4l2_framebuffer *fb) { - struct saa7134_fh *fh = f; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); struct saa7134_format *fmt; if (!capable(CAP_SYS_ADMIN) && @@ -2160,10 +1904,9 @@ static int saa7134_s_fbuf(struct file *file, void *f, return 0; } -static int saa7134_overlay(struct file *file, void *f, unsigned int on) +static int saa7134_overlay(struct file *file, void *priv, unsigned int on) { - struct saa7134_fh *fh = f; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); unsigned long flags; if (on) { @@ -2172,54 +1915,57 @@ static int saa7134_overlay(struct file *file, void *f, unsigned int on) return -EINVAL; } - if (!res_get(dev, fh, RESOURCE_OVERLAY)) + if (!res_get(dev, priv, RESOURCE_OVERLAY)) return -EBUSY; spin_lock_irqsave(&dev->slock, flags); - start_preview(dev, fh); + start_preview(dev); spin_unlock_irqrestore(&dev->slock, flags); } if (!on) { - if (!res_check(fh, RESOURCE_OVERLAY)) + if (!res_check(priv, RESOURCE_OVERLAY)) return -EINVAL; spin_lock_irqsave(&dev->slock, flags); - stop_preview(dev, fh); + stop_preview(dev); spin_unlock_irqrestore(&dev->slock, flags); - res_free(dev, fh, RESOURCE_OVERLAY); + res_free(dev, priv, RESOURCE_OVERLAY); } return 0; } -static int saa7134_reqbufs(struct file *file, void *priv, +int saa7134_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *p) { return videobuf_reqbufs(saa7134_queue(file), p); } +EXPORT_SYMBOL_GPL(saa7134_reqbufs); -static int saa7134_querybuf(struct file *file, void *priv, +int saa7134_querybuf(struct file *file, void *priv, struct v4l2_buffer *b) { return videobuf_querybuf(saa7134_queue(file), b); } +EXPORT_SYMBOL_GPL(saa7134_querybuf); -static int saa7134_qbuf(struct file *file, void *priv, struct v4l2_buffer *b) +int saa7134_qbuf(struct file *file, void *priv, struct v4l2_buffer *b) { return videobuf_qbuf(saa7134_queue(file), b); } +EXPORT_SYMBOL_GPL(saa7134_qbuf); -static int saa7134_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b) +int saa7134_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b) { return videobuf_dqbuf(saa7134_queue(file), b, file->f_flags & O_NONBLOCK); } +EXPORT_SYMBOL_GPL(saa7134_dqbuf); -static int saa7134_streamon(struct file *file, void *priv, +int saa7134_streamon(struct file *file, void *priv, enum v4l2_buf_type type) { - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); int res = saa7134_resource(file); - if (!res_get(dev, fh, res)) + if (!res_get(dev, priv, res)) return -EBUSY; /* The SAA7134 has a 1K FIFO; the datasheet suggests that when @@ -2229,36 +1975,37 @@ static int saa7134_streamon(struct file *file, void *priv, * Unfortunately, I lack register-level documentation to check the * Linux FIFO setup and confirm the perfect value. */ - pm_qos_add_request(&dev->qos_request, - PM_QOS_CPU_DMA_LATENCY, - 20); + if (res != RESOURCE_EMPRESS) + pm_qos_add_request(&dev->qos_request, + PM_QOS_CPU_DMA_LATENCY, 20); return videobuf_streamon(saa7134_queue(file)); } +EXPORT_SYMBOL_GPL(saa7134_streamon); -static int saa7134_streamoff(struct file *file, void *priv, +int saa7134_streamoff(struct file *file, void *priv, enum v4l2_buf_type type) { + struct saa7134_dev *dev = video_drvdata(file); int err; - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; int res = saa7134_resource(file); - pm_qos_remove_request(&dev->qos_request); + if (res != RESOURCE_EMPRESS) + pm_qos_remove_request(&dev->qos_request); err = videobuf_streamoff(saa7134_queue(file)); if (err < 0) return err; - res_free(dev, fh, res); + res_free(dev, priv, res); return 0; } +EXPORT_SYMBOL_GPL(saa7134_streamoff); #ifdef CONFIG_VIDEO_ADV_DEBUG static int vidioc_g_register (struct file *file, void *priv, struct v4l2_dbg_register *reg) { - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); reg->val = saa_readb(reg->reg & 0xffffff); reg->size = 1; @@ -2268,8 +2015,7 @@ static int vidioc_g_register (struct file *file, void *priv, static int vidioc_s_register (struct file *file, void *priv, const struct v4l2_dbg_register *reg) { - struct saa7134_fh *fh = priv; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); saa_writeb(reg->reg & 0xffffff, reg->val); return 0; @@ -2279,8 +2025,7 @@ static int vidioc_s_register (struct file *file, void *priv, static int radio_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t) { - struct saa7134_fh *fh = file->private_data; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); if (0 != t->index) return -EINVAL; @@ -2299,8 +2044,7 @@ static int radio_g_tuner(struct file *file, void *priv, static int radio_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *t) { - struct saa7134_fh *fh = file->private_data; - struct saa7134_dev *dev = fh->dev; + struct saa7134_dev *dev = video_drvdata(file); if (0 != t->index) return -EINVAL; @@ -2309,50 +2053,6 @@ static int radio_s_tuner(struct file *file, void *priv, return 0; } -static int radio_enum_input(struct file *file, void *priv, - struct v4l2_input *i) -{ - if (i->index != 0) - return -EINVAL; - - strcpy(i->name, "Radio"); - i->type = V4L2_INPUT_TYPE_TUNER; - - return 0; -} - -static int radio_g_input(struct file *filp, void *priv, unsigned int *i) -{ - *i = 0; - return 0; -} - -static int radio_s_input(struct file *filp, void *priv, unsigned int i) -{ - return 0; -} - -static int radio_s_std(struct file *file, void *fh, v4l2_std_id norm) -{ - return 0; -} - -static int radio_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *c) -{ - const struct v4l2_queryctrl *ctrl; - - if (c->id < V4L2_CID_BASE || - c->id >= V4L2_CID_LASTP1) - return -EINVAL; - if (c->id == V4L2_CID_AUDIO_MUTE) { - ctrl = ctrl_by_id(c->id); - *c = *ctrl; - } else - *c = no_ctrl; - return 0; -} - static const struct v4l2_file_operations video_fops = { .owner = THIS_MODULE, @@ -2387,9 +2087,6 @@ static const struct v4l2_ioctl_ops video_ioctl_ops = { .vidioc_enum_input = saa7134_enum_input, .vidioc_g_input = saa7134_g_input, .vidioc_s_input = saa7134_s_input, - .vidioc_queryctrl = saa7134_queryctrl, - .vidioc_g_ctrl = saa7134_g_ctrl, - .vidioc_s_ctrl = saa7134_s_ctrl, .vidioc_streamon = saa7134_streamon, .vidioc_streamoff = saa7134_streamoff, .vidioc_g_tuner = saa7134_g_tuner, @@ -2405,6 +2102,9 @@ static const struct v4l2_ioctl_ops video_ioctl_ops = { .vidioc_g_register = vidioc_g_register, .vidioc_s_register = vidioc_s_register, #endif + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; static const struct v4l2_file_operations radio_fops = { @@ -2419,16 +2119,11 @@ static const struct v4l2_file_operations radio_fops = { static const struct v4l2_ioctl_ops radio_ioctl_ops = { .vidioc_querycap = saa7134_querycap, .vidioc_g_tuner = radio_g_tuner, - .vidioc_enum_input = radio_enum_input, .vidioc_s_tuner = radio_s_tuner, - .vidioc_s_input = radio_s_input, - .vidioc_s_std = radio_s_std, - .vidioc_queryctrl = radio_queryctrl, - .vidioc_g_input = radio_g_input, - .vidioc_g_ctrl = saa7134_g_ctrl, - .vidioc_s_ctrl = saa7134_s_ctrl, .vidioc_g_frequency = saa7134_g_frequency, .vidioc_s_frequency = saa7134_s_frequency, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; /* ----------------------------------------------------------- */ @@ -2447,8 +2142,55 @@ struct video_device saa7134_radio_template = { .ioctl_ops = &radio_ioctl_ops, }; +static const struct v4l2_ctrl_ops saa7134_ctrl_ops = { + .s_ctrl = saa7134_s_ctrl, +}; + +static const struct v4l2_ctrl_config saa7134_ctrl_invert = { + .ops = &saa7134_ctrl_ops, + .id = V4L2_CID_PRIVATE_INVERT, + .name = "Invert", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, +}; + +static const struct v4l2_ctrl_config saa7134_ctrl_y_odd = { + .ops = &saa7134_ctrl_ops, + .id = V4L2_CID_PRIVATE_Y_ODD, + .name = "Y Offset Odd Field", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 128, + .step = 1, +}; + +static const struct v4l2_ctrl_config saa7134_ctrl_y_even = { + .ops = &saa7134_ctrl_ops, + .id = V4L2_CID_PRIVATE_Y_EVEN, + .name = "Y Offset Even Field", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 128, + .step = 1, +}; + +static const struct v4l2_ctrl_config saa7134_ctrl_automute = { + .ops = &saa7134_ctrl_ops, + .id = V4L2_CID_PRIVATE_AUTOMUTE, + .name = "Automute", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 1, +}; + int saa7134_video_init1(struct saa7134_dev *dev) { + struct v4l2_ctrl_handler *hdl = &dev->ctrl_handler; + /* sanitycheck insmod options */ if (gbuffers < 2 || gbuffers > VIDEO_MAX_FRAME) gbuffers = 2; @@ -2456,17 +2198,38 @@ int saa7134_video_init1(struct saa7134_dev *dev) gbufsize = gbufsize_max; gbufsize = (gbufsize + PAGE_SIZE - 1) & PAGE_MASK; - /* put some sensible defaults into the data structures ... */ - dev->ctl_bright = ctrl_by_id(V4L2_CID_BRIGHTNESS)->default_value; - dev->ctl_contrast = ctrl_by_id(V4L2_CID_CONTRAST)->default_value; - dev->ctl_hue = ctrl_by_id(V4L2_CID_HUE)->default_value; - dev->ctl_saturation = ctrl_by_id(V4L2_CID_SATURATION)->default_value; - dev->ctl_volume = ctrl_by_id(V4L2_CID_AUDIO_VOLUME)->default_value; - dev->ctl_mute = 1; // ctrl_by_id(V4L2_CID_AUDIO_MUTE)->default_value; - dev->ctl_invert = ctrl_by_id(V4L2_CID_PRIVATE_INVERT)->default_value; - dev->ctl_automute = ctrl_by_id(V4L2_CID_PRIVATE_AUTOMUTE)->default_value; - - if (dev->tda9887_conf && dev->ctl_automute) + v4l2_ctrl_handler_init(hdl, 11); + v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, + V4L2_CID_CONTRAST, 0, 127, 1, 68); + v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, + V4L2_CID_SATURATION, 0, 127, 1, 64); + v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); + v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, -15, 15, 1, 0); + v4l2_ctrl_new_custom(hdl, &saa7134_ctrl_invert, NULL); + v4l2_ctrl_new_custom(hdl, &saa7134_ctrl_y_odd, NULL); + v4l2_ctrl_new_custom(hdl, &saa7134_ctrl_y_even, NULL); + v4l2_ctrl_new_custom(hdl, &saa7134_ctrl_automute, NULL); + if (hdl->error) + return hdl->error; + if (card_has_radio(dev)) { + hdl = &dev->radio_ctrl_handler; + v4l2_ctrl_handler_init(hdl, 2); + v4l2_ctrl_add_handler(hdl, &dev->ctrl_handler, + v4l2_ctrl_radio_filter); + if (hdl->error) + return hdl->error; + } + dev->ctl_mute = 1; + + if (dev->tda9887_conf && saa7134_ctrl_automute.def) dev->tda9887_conf |= TDA9887_AUTOMUTE; dev->automute = 0; @@ -2489,9 +2252,34 @@ int saa7134_video_init1(struct saa7134_dev *dev) if (saa7134_boards[dev->board].video_out) saa7134_videoport_init(dev); + videobuf_queue_sg_init(&dev->cap, &video_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct saa7134_buf), + dev, NULL); + videobuf_queue_sg_init(&dev->vbi, &saa7134_vbi_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VBI_CAPTURE, + V4L2_FIELD_SEQ_TB, + sizeof(struct saa7134_buf), + dev, NULL); + saa7134_pgtable_alloc(dev->pci, &dev->pt_cap); + saa7134_pgtable_alloc(dev->pci, &dev->pt_vbi); + return 0; } +void saa7134_video_fini(struct saa7134_dev *dev) +{ + /* free stuff */ + saa7134_pgtable_free(dev->pci, &dev->pt_cap); + saa7134_pgtable_free(dev->pci, &dev->pt_vbi); + v4l2_ctrl_handler_free(&dev->ctrl_handler); + if (card_has_radio(dev)) + v4l2_ctrl_handler_free(&dev->radio_ctrl_handler); +} + int saa7134_videoport_init(struct saa7134_dev *dev) { /* enable video output */ @@ -2533,6 +2321,7 @@ int saa7134_video_init2(struct saa7134_dev *dev) /* init video hw */ set_tvnorm(dev,&tvnorms[0]); video_mux(dev,0); + v4l2_ctrl_handler_setup(&dev->ctrl_handler); saa7134_tvaudio_setmute(dev); saa7134_tvaudio_setvolume(dev,dev->ctl_volume); return 0; diff --git a/drivers/media/pci/saa7134/saa7134.h b/drivers/media/pci/saa7134/saa7134.h index 8d1453a48014..2474e848f2c0 100644 --- a/drivers/media/pci/saa7134/saa7134.h +++ b/drivers/media/pci/saa7134/saa7134.h @@ -37,6 +37,7 @@ #include <media/v4l2-ioctl.h> #include <media/v4l2-device.h> #include <media/v4l2-fh.h> +#include <media/v4l2-ctrls.h> #include <media/tuner.h> #include <media/rc-core.h> #include <media/ir-kbd-i2c.h> @@ -410,12 +411,18 @@ struct saa7134_board { #define card(dev) (saa7134_boards[dev->board]) #define card_in(dev,n) (saa7134_boards[dev->board].inputs[n]) +#define V4L2_CID_PRIVATE_INVERT (V4L2_CID_USER_SAA7134_BASE + 0) +#define V4L2_CID_PRIVATE_Y_ODD (V4L2_CID_USER_SAA7134_BASE + 1) +#define V4L2_CID_PRIVATE_Y_EVEN (V4L2_CID_USER_SAA7134_BASE + 2) +#define V4L2_CID_PRIVATE_AUTOMUTE (V4L2_CID_USER_SAA7134_BASE + 3) + /* ----------------------------------------------------------- */ /* device / file handle status */ #define RESOURCE_OVERLAY 1 #define RESOURCE_VIDEO 2 #define RESOURCE_VBI 4 +#define RESOURCE_EMPRESS 8 #define INTERLACE_AUTO 0 #define INTERLACE_ON 1 @@ -470,16 +477,8 @@ struct saa7134_dmaqueue { /* video filehandle status */ struct saa7134_fh { struct v4l2_fh fh; - struct saa7134_dev *dev; + bool is_empress; unsigned int resources; - - /* video capture */ - struct videobuf_queue cap; - struct saa7134_pgtable pt_cap; - - /* vbi capture */ - struct videobuf_queue vbi; - struct saa7134_pgtable pt_vbi; }; /* dmasound dsp status */ @@ -589,7 +588,11 @@ struct saa7134_dev { /* video+ts+vbi capture */ struct saa7134_dmaqueue video_q; + struct videobuf_queue cap; + struct saa7134_pgtable pt_cap; struct saa7134_dmaqueue vbi_q; + struct videobuf_queue vbi; + struct saa7134_pgtable pt_vbi; unsigned int video_fieldcount; unsigned int vbi_fieldcount; struct saa7134_format *fmt; @@ -599,6 +602,7 @@ struct saa7134_dev { /* various v4l controls */ struct saa7134_tvnorm *tvnorm; /* video */ struct saa7134_tvaudio *tvaudio; + struct v4l2_ctrl_handler ctrl_handler; unsigned int ctl_input; int ctl_bright; int ctl_contrast; @@ -626,6 +630,7 @@ struct saa7134_dev { int last_carrier; int nosignal; unsigned int insuspend; + struct v4l2_ctrl_handler radio_ctrl_handler; /* I2C keyboard data */ struct IR_i2c_init_data init_data; @@ -638,10 +643,11 @@ struct saa7134_dev { /* SAA7134_MPEG_EMPRESS only */ struct video_device *empress_dev; + struct v4l2_subdev *empress_sd; struct videobuf_queue empress_tsq; - atomic_t empress_users; struct work_struct empress_workqueue; int empress_started; + struct v4l2_ctrl_handler empress_ctrl_handler; #if IS_ENABLED(CONFIG_VIDEO_SAA7134_DVB) /* SAA7134_MPEG_DVB only */ @@ -699,6 +705,16 @@ struct saa7134_dev { _rc; \ }) +static inline int res_check(struct saa7134_fh *fh, unsigned int bit) +{ + return fh->resources & bit; +} + +static inline int res_locked(struct saa7134_dev *dev, unsigned int bit) +{ + return dev->resources & bit; +} + /* ----------------------------------------------------------- */ /* saa7134-core.c */ @@ -761,10 +777,31 @@ extern unsigned int video_debug; extern struct video_device saa7134_video_template; extern struct video_device saa7134_radio_template; -int saa7134_s_ctrl_internal(struct saa7134_dev *dev, struct saa7134_fh *fh, struct v4l2_control *c); -int saa7134_g_ctrl_internal(struct saa7134_dev *dev, struct saa7134_fh *fh, struct v4l2_control *c); -int saa7134_queryctrl(struct file *file, void *priv, struct v4l2_queryctrl *c); -int saa7134_s_std_internal(struct saa7134_dev *dev, struct saa7134_fh *fh, v4l2_std_id id); +int saa7134_s_std(struct file *file, void *priv, v4l2_std_id id); +int saa7134_g_std(struct file *file, void *priv, v4l2_std_id *id); +int saa7134_enum_input(struct file *file, void *priv, struct v4l2_input *i); +int saa7134_g_input(struct file *file, void *priv, unsigned int *i); +int saa7134_s_input(struct file *file, void *priv, unsigned int i); +int saa7134_querycap(struct file *file, void *priv, + struct v4l2_capability *cap); +int saa7134_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t); +int saa7134_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *t); +int saa7134_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f); +int saa7134_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f); +int saa7134_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *p); +int saa7134_querybuf(struct file *file, void *priv, + struct v4l2_buffer *b); +int saa7134_qbuf(struct file *file, void *priv, struct v4l2_buffer *b); +int saa7134_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b); +int saa7134_streamon(struct file *file, void *priv, + enum v4l2_buf_type type); +int saa7134_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type); int saa7134_videoport_init(struct saa7134_dev *dev); void saa7134_set_tvnorm_hw(struct saa7134_dev *dev); @@ -773,6 +810,7 @@ int saa7134_video_init1(struct saa7134_dev *dev); int saa7134_video_init2(struct saa7134_dev *dev); void saa7134_irq_video_signalchange(struct saa7134_dev *dev); void saa7134_irq_video_done(struct saa7134_dev *dev, unsigned long status); +void saa7134_video_fini(struct saa7134_dev *dev); /* ----------------------------------------------------------- */ diff --git a/drivers/media/pci/sta2x11/sta2x11_vip.c b/drivers/media/pci/sta2x11/sta2x11_vip.c index 77edc113e485..e5cfb6cfa18d 100644 --- a/drivers/media/pci/sta2x11/sta2x11_vip.c +++ b/drivers/media/pci/sta2x11/sta2x11_vip.c @@ -1303,7 +1303,7 @@ static int sta2x11_vip_resume(struct pci_dev *pdev) #endif -static DEFINE_PCI_DEVICE_TABLE(sta2x11_vip_pci_tbl) = { +static const struct pci_device_id sta2x11_vip_pci_tbl[] = { {PCI_DEVICE(PCI_VENDOR_ID_STMICRO, PCI_DEVICE_ID_STMICRO_VIP)}, {0,} }; diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index d7f0249e4050..b2a4403940c5 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -36,7 +36,8 @@ source "drivers/media/platform/blackfin/Kconfig" config VIDEO_SH_VOU tristate "SuperH VOU video output driver" depends on MEDIA_CAMERA_SUPPORT - depends on VIDEO_DEV && ARCH_SHMOBILE && I2C + depends on VIDEO_DEV && I2C + depends on ARCH_SHMOBILE || COMPILE_TEST select VIDEOBUF_DMA_CONTIG help Support for the Video Output Unit (VOU) on SuperH SoCs. @@ -90,13 +91,6 @@ config VIDEO_M32R_AR_M64278 To compile this driver as a module, choose M here: the module will be called arv. -config VIDEO_OMAP2 - tristate "OMAP2 Camera Capture Interface driver" - depends on VIDEO_DEV && ARCH_OMAP2 && VIDEO_V4L2_INT_DEVICE - select VIDEOBUF_DMA_SG - ---help--- - This is a v4l2 driver for the TI OMAP2 camera capture interface - config VIDEO_OMAP3 tristate "OMAP 3 Camera support" depends on OMAP_IOVMM && VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API && ARCH_OMAP3 diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 1348ba1faf92..e5269da91906 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -2,8 +2,6 @@ # Makefile for the video capture/playback device drivers. # -omap2cam-objs := omap24xxcam.o omap24xxcam-dma.o - obj-$(CONFIG_VIDEO_VINO) += indycam.o obj-$(CONFIG_VIDEO_VINO) += vino.o @@ -14,7 +12,6 @@ obj-$(CONFIG_VIDEO_VIA_CAMERA) += via-camera.o obj-$(CONFIG_VIDEO_CAFE_CCIC) += marvell-ccic/ obj-$(CONFIG_VIDEO_MMP_CAMERA) += marvell-ccic/ -obj-$(CONFIG_VIDEO_OMAP2) += omap2cam.o obj-$(CONFIG_VIDEO_OMAP3) += omap3isp/ obj-$(CONFIG_VIDEO_VIU) += fsl-viu.o diff --git a/drivers/media/platform/davinci/vpbe_display.c b/drivers/media/platform/davinci/vpbe_display.c index eac472b5ae83..b02aba488826 100644 --- a/drivers/media/platform/davinci/vpbe_display.c +++ b/drivers/media/platform/davinci/vpbe_display.c @@ -347,7 +347,7 @@ static int vpbe_start_streaming(struct vb2_queue *vq, unsigned int count) /* If buffer queue is empty, return error */ if (list_empty(&layer->dma_queue)) { v4l2_err(&vpbe_dev->v4l2_dev, "buffer queue is empty\n"); - return -EINVAL; + return -ENOBUFS; } /* Get the next frame from the buffer queue */ layer->next_frm = layer->cur_frm = list_entry(layer->dma_queue.next, diff --git a/drivers/media/platform/davinci/vpif_capture.c b/drivers/media/platform/davinci/vpif_capture.c index 52ac5e6c8625..735ec47601a9 100644 --- a/drivers/media/platform/davinci/vpif_capture.c +++ b/drivers/media/platform/davinci/vpif_capture.c @@ -277,7 +277,7 @@ static int vpif_start_streaming(struct vb2_queue *vq, unsigned int count) if (list_empty(&common->dma_queue)) { spin_unlock_irqrestore(&common->irqlock, flags); vpif_dbg(1, debug, "buffer queue is empty\n"); - return -EIO; + return -ENOBUFS; } /* Get the next frame from the buffer queue */ diff --git a/drivers/media/platform/davinci/vpif_display.c b/drivers/media/platform/davinci/vpif_display.c index c31bcf129a5d..9d115cdc6bdb 100644 --- a/drivers/media/platform/davinci/vpif_display.c +++ b/drivers/media/platform/davinci/vpif_display.c @@ -239,7 +239,7 @@ static int vpif_start_streaming(struct vb2_queue *vq, unsigned int count) if (list_empty(&common->dma_queue)) { spin_unlock_irqrestore(&common->irqlock, flags); vpif_err("buffer queue is empty\n"); - return -EIO; + return -ENOBUFS; } /* Get the next frame from the buffer queue */ diff --git a/drivers/media/platform/exynos4-is/Kconfig b/drivers/media/platform/exynos4-is/Kconfig index d2d3b4b61435..01ed1ecdff7e 100644 --- a/drivers/media/platform/exynos4-is/Kconfig +++ b/drivers/media/platform/exynos4-is/Kconfig @@ -1,7 +1,7 @@ config VIDEO_SAMSUNG_EXYNOS4_IS bool "Samsung S5P/EXYNOS4 SoC series Camera Subsystem driver" - depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && PM_RUNTIME + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API depends on (PLAT_S5P || ARCH_EXYNOS) help Say Y here to enable camera host interface devices for diff --git a/drivers/media/platform/exynos4-is/fimc-capture.c b/drivers/media/platform/exynos4-is/fimc-capture.c index fb27ff7e1e07..8a712ca91d11 100644 --- a/drivers/media/platform/exynos4-is/fimc-capture.c +++ b/drivers/media/platform/exynos4-is/fimc-capture.c @@ -549,7 +549,7 @@ static int fimc_capture_release(struct file *file) vc->streaming = false; } - ret = vb2_fop_release(file); + ret = _vb2_fop_release(file, NULL); if (close) { clear_bit(ST_CAPT_BUSY, &fimc->state); diff --git a/drivers/media/platform/exynos4-is/fimc-core.c b/drivers/media/platform/exynos4-is/fimc-core.c index f7915695c907..a7dfd07e8389 100644 --- a/drivers/media/platform/exynos4-is/fimc-core.c +++ b/drivers/media/platform/exynos4-is/fimc-core.c @@ -998,36 +998,39 @@ static int fimc_probe(struct platform_device *pdev) ret = devm_request_irq(dev, res->start, fimc_irq_handler, 0, dev_name(dev), fimc); - if (ret) { + if (ret < 0) { dev_err(dev, "failed to install irq (%d)\n", ret); - goto err_clk; + goto err_sclk; } ret = fimc_initialize_capture_subdev(fimc); - if (ret) - goto err_clk; + if (ret < 0) + goto err_sclk; platform_set_drvdata(pdev, fimc); pm_runtime_enable(dev); - ret = pm_runtime_get_sync(dev); - if (ret < 0) - goto err_sd; + + if (!pm_runtime_enabled(dev)) { + ret = clk_enable(fimc->clock[CLK_GATE]); + if (ret < 0) + goto err_sd; + } + /* Initialize contiguous memory allocator */ fimc->alloc_ctx = vb2_dma_contig_init_ctx(dev); if (IS_ERR(fimc->alloc_ctx)) { ret = PTR_ERR(fimc->alloc_ctx); - goto err_pm; + goto err_gclk; } dev_dbg(dev, "FIMC.%d registered successfully\n", fimc->id); - - pm_runtime_put(dev); return 0; -err_pm: - pm_runtime_put(dev); + +err_gclk: + clk_disable(fimc->clock[CLK_GATE]); err_sd: fimc_unregister_capture_subdev(fimc); -err_clk: +err_sclk: clk_disable(fimc->clock[CLK_BUS]); fimc_clk_put(fimc); return ret; diff --git a/drivers/media/platform/exynos4-is/fimc-core.h b/drivers/media/platform/exynos4-is/fimc-core.h index 3d376faec777..1790fb4e32ea 100644 --- a/drivers/media/platform/exynos4-is/fimc-core.h +++ b/drivers/media/platform/exynos4-is/fimc-core.h @@ -481,7 +481,6 @@ struct fimc_ctrls { * @flags: additional flags for image conversion * @state: flags to keep track of user configuration * @fimc_dev: the FIMC device this context applies to - * @m2m_ctx: memory-to-memory device context * @fh: v4l2 file handle * @ctrls: v4l2 controls structure */ @@ -502,7 +501,6 @@ struct fimc_ctx { u32 flags; u32 state; struct fimc_dev *fimc_dev; - struct v4l2_m2m_ctx *m2m_ctx; struct v4l2_fh fh; struct fimc_ctrls ctrls; }; diff --git a/drivers/media/platform/exynos4-is/fimc-is-regs.c b/drivers/media/platform/exynos4-is/fimc-is-regs.c index f758e2694fa3..2628733c4e10 100644 --- a/drivers/media/platform/exynos4-is/fimc-is-regs.c +++ b/drivers/media/platform/exynos4-is/fimc-is-regs.c @@ -33,47 +33,23 @@ void fimc_is_hw_set_intgr0_gd0(struct fimc_is *is) mcuctl_write(INTGR0_INTGD(0), is, MCUCTL_REG_INTGR0); } -int fimc_is_hw_wait_intsr0_intsd0(struct fimc_is *is) -{ - unsigned int timeout = 2000; - u32 cfg, status; - - cfg = mcuctl_read(is, MCUCTL_REG_INTSR0); - status = INTSR0_GET_INTSD(0, cfg); - - while (status) { - cfg = mcuctl_read(is, MCUCTL_REG_INTSR0); - status = INTSR0_GET_INTSD(0, cfg); - if (timeout == 0) { - dev_warn(&is->pdev->dev, "%s timeout\n", - __func__); - return -ETIME; - } - timeout--; - udelay(1); - } - return 0; -} - int fimc_is_hw_wait_intmsr0_intmsd0(struct fimc_is *is) { unsigned int timeout = 2000; u32 cfg, status; - cfg = mcuctl_read(is, MCUCTL_REG_INTMSR0); - status = INTMSR0_GET_INTMSD(0, cfg); - - while (status) { + do { cfg = mcuctl_read(is, MCUCTL_REG_INTMSR0); status = INTMSR0_GET_INTMSD(0, cfg); - if (timeout == 0) { + + if (--timeout == 0) { dev_warn(&is->pdev->dev, "%s timeout\n", __func__); - return -ETIME; + return -ETIMEDOUT; } - timeout--; udelay(1); - } + } while (status != 0); + return 0; } diff --git a/drivers/media/platform/exynos4-is/fimc-is-regs.h b/drivers/media/platform/exynos4-is/fimc-is-regs.h index 5fa2fda46742..1d9d4ffc6ad5 100644 --- a/drivers/media/platform/exynos4-is/fimc-is-regs.h +++ b/drivers/media/platform/exynos4-is/fimc-is-regs.h @@ -145,7 +145,6 @@ void fimc_is_fw_clear_irq2(struct fimc_is *is); int fimc_is_hw_get_params(struct fimc_is *is, unsigned int num); void fimc_is_hw_set_intgr0_gd0(struct fimc_is *is); -int fimc_is_hw_wait_intsr0_intsd0(struct fimc_is *is); int fimc_is_hw_wait_intmsr0_intmsd0(struct fimc_is *is); void fimc_is_hw_set_sensor_num(struct fimc_is *is); void fimc_is_hw_stream_on(struct fimc_is *is); diff --git a/drivers/media/platform/exynos4-is/fimc-is.c b/drivers/media/platform/exynos4-is/fimc-is.c index 9770fa98d6a1..13a4228952e3 100644 --- a/drivers/media/platform/exynos4-is/fimc-is.c +++ b/drivers/media/platform/exynos4-is/fimc-is.c @@ -781,6 +781,9 @@ static int fimc_is_debugfs_create(struct fimc_is *is) return is->debugfs_entry == NULL ? -EIO : 0; } +static int fimc_is_runtime_resume(struct device *dev); +static int fimc_is_runtime_suspend(struct device *dev); + static int fimc_is_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -835,14 +838,20 @@ static int fimc_is_probe(struct platform_device *pdev) } pm_runtime_enable(dev); + if (!pm_runtime_enabled(dev)) { + ret = fimc_is_runtime_resume(dev); + if (ret < 0) + goto err_irq; + } + ret = pm_runtime_get_sync(dev); if (ret < 0) - goto err_irq; + goto err_pm; is->alloc_ctx = vb2_dma_contig_init_ctx(dev); if (IS_ERR(is->alloc_ctx)) { ret = PTR_ERR(is->alloc_ctx); - goto err_irq; + goto err_pm; } /* * Register FIMC-IS V4L2 subdevs to this driver. The video nodes @@ -867,10 +876,13 @@ static int fimc_is_probe(struct platform_device *pdev) err_dfs: fimc_is_debugfs_remove(is); -err_vb: - vb2_dma_contig_cleanup_ctx(is->alloc_ctx); err_sd: fimc_is_unregister_subdevs(is); +err_vb: + vb2_dma_contig_cleanup_ctx(is->alloc_ctx); +err_pm: + if (!pm_runtime_enabled(dev)) + fimc_is_runtime_suspend(dev); err_irq: free_irq(is->irq, is); err_clk: @@ -919,10 +931,13 @@ static int fimc_is_suspend(struct device *dev) static int fimc_is_remove(struct platform_device *pdev) { - struct fimc_is *is = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + struct fimc_is *is = dev_get_drvdata(dev); - pm_runtime_disable(&pdev->dev); - pm_runtime_set_suspended(&pdev->dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + if (!pm_runtime_status_suspended(dev)) + fimc_is_runtime_suspend(dev); free_irq(is->irq, is); fimc_is_unregister_subdevs(is); vb2_dma_contig_cleanup_ctx(is->alloc_ctx); diff --git a/drivers/media/platform/exynos4-is/fimc-lite-reg.c b/drivers/media/platform/exynos4-is/fimc-lite-reg.c index 72a343e3b5e8..d0dc7ee04452 100644 --- a/drivers/media/platform/exynos4-is/fimc-lite-reg.c +++ b/drivers/media/platform/exynos4-is/fimc-lite-reg.c @@ -133,7 +133,7 @@ void flite_hw_set_source_format(struct fimc_lite *dev, struct flite_frame *f) int i = ARRAY_SIZE(src_pixfmt_map); u32 cfg; - while (--i >= 0) { + while (--i) { if (src_pixfmt_map[i][0] == pixelcode) break; } @@ -240,7 +240,7 @@ static void flite_hw_set_out_order(struct fimc_lite *dev, struct flite_frame *f) u32 cfg = readl(dev->regs + FLITE_REG_CIODMAFMT); int i = ARRAY_SIZE(pixcode); - while (--i >= 0) + while (--i) if (pixcode[i][0] == f->fmt->mbus_code) break; cfg &= ~FLITE_REG_CIODMAFMT_YCBCR_ORDER_MASK; diff --git a/drivers/media/platform/exynos4-is/fimc-lite.c b/drivers/media/platform/exynos4-is/fimc-lite.c index e5798f70d149..1234734bccf4 100644 --- a/drivers/media/platform/exynos4-is/fimc-lite.c +++ b/drivers/media/platform/exynos4-is/fimc-lite.c @@ -546,7 +546,7 @@ static int fimc_lite_release(struct file *file) mutex_unlock(&entity->parent->graph_mutex); } - vb2_fop_release(file); + _vb2_fop_release(file, NULL); pm_runtime_put(&fimc->pdev->dev); clear_bit(ST_FLITE_SUSPENDED, &fimc->state); @@ -1549,38 +1549,40 @@ static int fimc_lite_probe(struct platform_device *pdev) 0, dev_name(dev), fimc); if (ret) { dev_err(dev, "Failed to install irq (%d)\n", ret); - goto err_clk; + goto err_clk_put; } /* The video node will be created within the subdev's registered() op */ ret = fimc_lite_create_capture_subdev(fimc); if (ret) - goto err_clk; + goto err_clk_put; platform_set_drvdata(pdev, fimc); pm_runtime_enable(dev); - ret = pm_runtime_get_sync(dev); - if (ret < 0) - goto err_sd; + + if (!pm_runtime_enabled(dev)) { + ret = clk_enable(fimc->clock); + if (ret < 0) + goto err_clk_put; + } fimc->alloc_ctx = vb2_dma_contig_init_ctx(dev); if (IS_ERR(fimc->alloc_ctx)) { ret = PTR_ERR(fimc->alloc_ctx); - goto err_pm; + goto err_clk_dis; } - pm_runtime_put(dev); - fimc_lite_set_default_config(fimc); dev_dbg(dev, "FIMC-LITE.%d registered successfully\n", fimc->index); return 0; -err_pm: - pm_runtime_put(dev); + +err_clk_dis: + clk_disable(fimc->clock); err_sd: fimc_lite_unregister_capture_subdev(fimc); -err_clk: +err_clk_put: fimc_lite_clk_put(fimc); return ret; } diff --git a/drivers/media/platform/exynos4-is/fimc-m2m.c b/drivers/media/platform/exynos4-is/fimc-m2m.c index 8d33b68c76ba..9da95bd14820 100644 --- a/drivers/media/platform/exynos4-is/fimc-m2m.c +++ b/drivers/media/platform/exynos4-is/fimc-m2m.c @@ -44,17 +44,17 @@ void fimc_m2m_job_finish(struct fimc_ctx *ctx, int vb_state) { struct vb2_buffer *src_vb, *dst_vb; - if (!ctx || !ctx->m2m_ctx) + if (!ctx || !ctx->fh.m2m_ctx) return; - src_vb = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); - dst_vb = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); + src_vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); if (src_vb && dst_vb) { v4l2_m2m_buf_done(src_vb, vb_state); v4l2_m2m_buf_done(dst_vb, vb_state); v4l2_m2m_job_finish(ctx->fimc_dev->m2m.m2m_dev, - ctx->m2m_ctx); + ctx->fh.m2m_ctx); } } @@ -123,12 +123,12 @@ static void fimc_device_run(void *priv) fimc_prepare_dma_offset(ctx, df); } - src_vb = v4l2_m2m_next_src_buf(ctx->m2m_ctx); + src_vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); ret = fimc_prepare_addr(ctx, src_vb, sf, &sf->paddr); if (ret) goto dma_unlock; - dst_vb = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + dst_vb = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); ret = fimc_prepare_addr(ctx, dst_vb, df, &df->paddr); if (ret) goto dma_unlock; @@ -219,31 +219,15 @@ static int fimc_buf_prepare(struct vb2_buffer *vb) static void fimc_buf_queue(struct vb2_buffer *vb) { struct fimc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); - - dbg("ctx: %p, ctx->state: 0x%x", ctx, ctx->state); - - if (ctx->m2m_ctx) - v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); -} - -static void fimc_lock(struct vb2_queue *vq) -{ - struct fimc_ctx *ctx = vb2_get_drv_priv(vq); - mutex_lock(&ctx->fimc_dev->lock); -} - -static void fimc_unlock(struct vb2_queue *vq) -{ - struct fimc_ctx *ctx = vb2_get_drv_priv(vq); - mutex_unlock(&ctx->fimc_dev->lock); + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vb); } static struct vb2_ops fimc_qops = { .queue_setup = fimc_queue_setup, .buf_prepare = fimc_buf_prepare, .buf_queue = fimc_buf_queue, - .wait_prepare = fimc_unlock, - .wait_finish = fimc_lock, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, .stop_streaming = stop_streaming, .start_streaming = start_streaming, }; @@ -385,7 +369,7 @@ static int fimc_m2m_s_fmt_mplane(struct file *file, void *fh, if (ret) return ret; - vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); if (vb2_is_busy(vq)) { v4l2_err(&fimc->m2m.vfd, "queue (%d) busy\n", f->type); @@ -410,56 +394,6 @@ static int fimc_m2m_s_fmt_mplane(struct file *file, void *fh, return 0; } -static int fimc_m2m_reqbufs(struct file *file, void *fh, - struct v4l2_requestbuffers *reqbufs) -{ - struct fimc_ctx *ctx = fh_to_ctx(fh); - return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs); -} - -static int fimc_m2m_querybuf(struct file *file, void *fh, - struct v4l2_buffer *buf) -{ - struct fimc_ctx *ctx = fh_to_ctx(fh); - return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf); -} - -static int fimc_m2m_qbuf(struct file *file, void *fh, - struct v4l2_buffer *buf) -{ - struct fimc_ctx *ctx = fh_to_ctx(fh); - return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); -} - -static int fimc_m2m_dqbuf(struct file *file, void *fh, - struct v4l2_buffer *buf) -{ - struct fimc_ctx *ctx = fh_to_ctx(fh); - return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); -} - -static int fimc_m2m_expbuf(struct file *file, void *fh, - struct v4l2_exportbuffer *eb) -{ - struct fimc_ctx *ctx = fh_to_ctx(fh); - return v4l2_m2m_expbuf(file, ctx->m2m_ctx, eb); -} - - -static int fimc_m2m_streamon(struct file *file, void *fh, - enum v4l2_buf_type type) -{ - struct fimc_ctx *ctx = fh_to_ctx(fh); - return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); -} - -static int fimc_m2m_streamoff(struct file *file, void *fh, - enum v4l2_buf_type type) -{ - struct fimc_ctx *ctx = fh_to_ctx(fh); - return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); -} - static int fimc_m2m_cropcap(struct file *file, void *fh, struct v4l2_cropcap *cr) { @@ -598,13 +532,13 @@ static const struct v4l2_ioctl_ops fimc_m2m_ioctl_ops = { .vidioc_try_fmt_vid_out_mplane = fimc_m2m_try_fmt_mplane, .vidioc_s_fmt_vid_cap_mplane = fimc_m2m_s_fmt_mplane, .vidioc_s_fmt_vid_out_mplane = fimc_m2m_s_fmt_mplane, - .vidioc_reqbufs = fimc_m2m_reqbufs, - .vidioc_querybuf = fimc_m2m_querybuf, - .vidioc_qbuf = fimc_m2m_qbuf, - .vidioc_dqbuf = fimc_m2m_dqbuf, - .vidioc_expbuf = fimc_m2m_expbuf, - .vidioc_streamon = fimc_m2m_streamon, - .vidioc_streamoff = fimc_m2m_streamoff, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, .vidioc_g_crop = fimc_m2m_g_crop, .vidioc_s_crop = fimc_m2m_s_crop, .vidioc_cropcap = fimc_m2m_cropcap @@ -624,6 +558,7 @@ static int queue_init(void *priv, struct vb2_queue *src_vq, src_vq->mem_ops = &vb2_dma_contig_memops; src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); src_vq->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->fimc_dev->lock; ret = vb2_queue_init(src_vq); if (ret) @@ -636,6 +571,7 @@ static int queue_init(void *priv, struct vb2_queue *src_vq, dst_vq->mem_ops = &vb2_dma_contig_memops; dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); dst_vq->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->fimc_dev->lock; return vb2_queue_init(dst_vq); } @@ -708,9 +644,9 @@ static int fimc_m2m_open(struct file *file) ctx->out_path = FIMC_IO_DMA; ctx->scaler.enabled = 1; - ctx->m2m_ctx = v4l2_m2m_ctx_init(fimc->m2m.m2m_dev, ctx, queue_init); - if (IS_ERR(ctx->m2m_ctx)) { - ret = PTR_ERR(ctx->m2m_ctx); + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(fimc->m2m.m2m_dev, ctx, queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); goto error_c; } @@ -725,7 +661,7 @@ static int fimc_m2m_open(struct file *file) return 0; error_m2m_ctx: - v4l2_m2m_ctx_release(ctx->m2m_ctx); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); error_c: fimc_ctrls_delete(ctx); error_fh: @@ -747,7 +683,7 @@ static int fimc_m2m_release(struct file *file) mutex_lock(&fimc->lock); - v4l2_m2m_ctx_release(ctx->m2m_ctx); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); fimc_ctrls_delete(ctx); v4l2_fh_del(&ctx->fh); v4l2_fh_exit(&ctx->fh); @@ -760,45 +696,13 @@ static int fimc_m2m_release(struct file *file) return 0; } -static unsigned int fimc_m2m_poll(struct file *file, - struct poll_table_struct *wait) -{ - struct fimc_ctx *ctx = fh_to_ctx(file->private_data); - struct fimc_dev *fimc = ctx->fimc_dev; - int ret; - - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; - - ret = v4l2_m2m_poll(file, ctx->m2m_ctx, wait); - mutex_unlock(&fimc->lock); - - return ret; -} - - -static int fimc_m2m_mmap(struct file *file, struct vm_area_struct *vma) -{ - struct fimc_ctx *ctx = fh_to_ctx(file->private_data); - struct fimc_dev *fimc = ctx->fimc_dev; - int ret; - - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; - - ret = v4l2_m2m_mmap(file, ctx->m2m_ctx, vma); - mutex_unlock(&fimc->lock); - - return ret; -} - static const struct v4l2_file_operations fimc_m2m_fops = { .owner = THIS_MODULE, .open = fimc_m2m_open, .release = fimc_m2m_release, - .poll = fimc_m2m_poll, + .poll = v4l2_m2m_fop_poll, .unlocked_ioctl = video_ioctl2, - .mmap = fimc_m2m_mmap, + .mmap = v4l2_m2m_fop_mmap, }; static struct v4l2_m2m_ops m2m_ops = { diff --git a/drivers/media/platform/exynos4-is/mipi-csis.c b/drivers/media/platform/exynos4-is/mipi-csis.c index 9fc2af6a0446..f3c3591fdc5d 100644 --- a/drivers/media/platform/exynos4-is/mipi-csis.c +++ b/drivers/media/platform/exynos4-is/mipi-csis.c @@ -91,7 +91,7 @@ MODULE_PARM_DESC(debug, "Debug level (0-2)"); #define S5PCSIS_INTSRC_ODD_BEFORE (1 << 29) #define S5PCSIS_INTSRC_ODD_AFTER (1 << 28) #define S5PCSIS_INTSRC_ODD (0x3 << 28) -#define S5PCSIS_INTSRC_NON_IMAGE_DATA (0xff << 28) +#define S5PCSIS_INTSRC_NON_IMAGE_DATA (0xf << 28) #define S5PCSIS_INTSRC_FRAME_START (1 << 27) #define S5PCSIS_INTSRC_FRAME_END (1 << 26) #define S5PCSIS_INTSRC_ERR_SOT_HS (0xf << 12) @@ -790,6 +790,7 @@ static int s5pcsis_parse_dt(struct platform_device *pdev, #define s5pcsis_parse_dt(pdev, state) (-ENOSYS) #endif +static int s5pcsis_pm_resume(struct device *dev, bool runtime); static const struct of_device_id s5pcsis_of_match[]; static int s5pcsis_probe(struct platform_device *pdev) @@ -902,13 +903,21 @@ static int s5pcsis_probe(struct platform_device *pdev) /* .. and a pointer to the subdev. */ platform_set_drvdata(pdev, &state->sd); memcpy(state->events, s5pcsis_events, sizeof(state->events)); + pm_runtime_enable(dev); + if (!pm_runtime_enabled(dev)) { + ret = s5pcsis_pm_resume(dev, true); + if (ret < 0) + goto e_m_ent; + } dev_info(&pdev->dev, "lanes: %d, hs_settle: %d, wclk: %d, freq: %u\n", state->num_lanes, state->hs_settle, state->wclk_ext, state->clk_frequency); return 0; +e_m_ent: + media_entity_cleanup(&state->sd.entity); e_clkdis: clk_disable(state->clock[CSIS_CLK_MUX]); e_clkput: @@ -1014,7 +1023,7 @@ static int s5pcsis_remove(struct platform_device *pdev) struct csis_state *state = sd_to_csis_state(sd); pm_runtime_disable(&pdev->dev); - s5pcsis_pm_suspend(&pdev->dev, false); + s5pcsis_pm_suspend(&pdev->dev, true); clk_disable(state->clock[CSIS_CLK_MUX]); pm_runtime_set_suspended(&pdev->dev); s5pcsis_clk_put(state); diff --git a/drivers/media/platform/m2m-deinterlace.c b/drivers/media/platform/m2m-deinterlace.c index 65cab70fefcb..6bb86b581a34 100644 --- a/drivers/media/platform/m2m-deinterlace.c +++ b/drivers/media/platform/m2m-deinterlace.c @@ -918,7 +918,7 @@ static int deinterlace_open(struct file *file) return ret; } - ctx->xt = kzalloc(sizeof(struct dma_async_tx_descriptor) + + ctx->xt = kzalloc(sizeof(struct dma_interleaved_template) + sizeof(struct data_chunk), GFP_KERNEL); if (!ctx->xt) { kfree(ctx); diff --git a/drivers/media/platform/mem2mem_testdev.c b/drivers/media/platform/mem2mem_testdev.c index 8df5975b700a..08e24379b794 100644 --- a/drivers/media/platform/mem2mem_testdev.c +++ b/drivers/media/platform/mem2mem_testdev.c @@ -177,8 +177,6 @@ struct m2mtest_ctx { enum v4l2_colorspace colorspace; - struct v4l2_m2m_ctx *m2m_ctx; - /* Source and destination queue data */ struct m2mtest_q_data q_data[2]; }; @@ -342,8 +340,8 @@ static int job_ready(void *priv) { struct m2mtest_ctx *ctx = priv; - if (v4l2_m2m_num_src_bufs_ready(ctx->m2m_ctx) < ctx->translen - || v4l2_m2m_num_dst_bufs_ready(ctx->m2m_ctx) < ctx->translen) { + if (v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx) < ctx->translen + || v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx) < ctx->translen) { dprintk(ctx->dev, "Not enough buffers available\n"); return 0; } @@ -359,21 +357,6 @@ static void job_abort(void *priv) ctx->aborting = 1; } -static void m2mtest_lock(void *priv) -{ - struct m2mtest_ctx *ctx = priv; - struct m2mtest_dev *dev = ctx->dev; - mutex_lock(&dev->dev_mutex); -} - -static void m2mtest_unlock(void *priv) -{ - struct m2mtest_ctx *ctx = priv; - struct m2mtest_dev *dev = ctx->dev; - mutex_unlock(&dev->dev_mutex); -} - - /* device_run() - prepares and starts the device * * This simulates all the immediate preparations required before starting @@ -386,8 +369,8 @@ static void device_run(void *priv) struct m2mtest_dev *dev = ctx->dev; struct vb2_buffer *src_buf, *dst_buf; - src_buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); - dst_buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); device_process(ctx, src_buf, dst_buf); @@ -409,8 +392,8 @@ static void device_isr(unsigned long priv) return; } - src_vb = v4l2_m2m_src_buf_remove(curr_ctx->m2m_ctx); - dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->m2m_ctx); + src_vb = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx); curr_ctx->num_processed++; @@ -423,7 +406,7 @@ static void device_isr(unsigned long priv) || curr_ctx->aborting) { dprintk(curr_ctx->dev, "Finishing transaction\n"); curr_ctx->num_processed = 0; - v4l2_m2m_job_finish(m2mtest_dev->m2m_dev, curr_ctx->m2m_ctx); + v4l2_m2m_job_finish(m2mtest_dev->m2m_dev, curr_ctx->fh.m2m_ctx); } else { device_run(curr_ctx); } @@ -491,7 +474,7 @@ static int vidioc_g_fmt(struct m2mtest_ctx *ctx, struct v4l2_format *f) struct vb2_queue *vq; struct m2mtest_q_data *q_data; - vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); if (!vq) return -EINVAL; @@ -594,7 +577,7 @@ static int vidioc_s_fmt(struct m2mtest_ctx *ctx, struct v4l2_format *f) struct m2mtest_q_data *q_data; struct vb2_queue *vq; - vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); if (!vq) return -EINVAL; @@ -648,52 +631,6 @@ static int vidioc_s_fmt_vid_out(struct file *file, void *priv, return ret; } -static int vidioc_reqbufs(struct file *file, void *priv, - struct v4l2_requestbuffers *reqbufs) -{ - struct m2mtest_ctx *ctx = file2ctx(file); - - return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs); -} - -static int vidioc_querybuf(struct file *file, void *priv, - struct v4l2_buffer *buf) -{ - struct m2mtest_ctx *ctx = file2ctx(file); - - return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf); -} - -static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) -{ - struct m2mtest_ctx *ctx = file2ctx(file); - - return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); -} - -static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) -{ - struct m2mtest_ctx *ctx = file2ctx(file); - - return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); -} - -static int vidioc_streamon(struct file *file, void *priv, - enum v4l2_buf_type type) -{ - struct m2mtest_ctx *ctx = file2ctx(file); - - return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); -} - -static int vidioc_streamoff(struct file *file, void *priv, - enum v4l2_buf_type type) -{ - struct m2mtest_ctx *ctx = file2ctx(file); - - return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); -} - static int m2mtest_s_ctrl(struct v4l2_ctrl *ctrl) { struct m2mtest_ctx *ctx = @@ -748,14 +685,14 @@ static const struct v4l2_ioctl_ops m2mtest_ioctl_ops = { .vidioc_try_fmt_vid_out = vidioc_try_fmt_vid_out, .vidioc_s_fmt_vid_out = vidioc_s_fmt_vid_out, - .vidioc_reqbufs = vidioc_reqbufs, - .vidioc_querybuf = vidioc_querybuf, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, - .vidioc_qbuf = vidioc_qbuf, - .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, - .vidioc_streamon = vidioc_streamon, - .vidioc_streamoff = vidioc_streamoff, .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; @@ -818,27 +755,15 @@ static int m2mtest_buf_prepare(struct vb2_buffer *vb) static void m2mtest_buf_queue(struct vb2_buffer *vb) { struct m2mtest_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); - v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); -} - -static void m2mtest_wait_prepare(struct vb2_queue *q) -{ - struct m2mtest_ctx *ctx = vb2_get_drv_priv(q); - m2mtest_unlock(ctx); -} - -static void m2mtest_wait_finish(struct vb2_queue *q) -{ - struct m2mtest_ctx *ctx = vb2_get_drv_priv(q); - m2mtest_lock(ctx); + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vb); } static struct vb2_ops m2mtest_qops = { .queue_setup = m2mtest_queue_setup, .buf_prepare = m2mtest_buf_prepare, .buf_queue = m2mtest_buf_queue, - .wait_prepare = m2mtest_wait_prepare, - .wait_finish = m2mtest_wait_finish, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, }; static int queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq) @@ -853,6 +778,7 @@ static int queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *ds src_vq->ops = &m2mtest_qops; src_vq->mem_ops = &vb2_vmalloc_memops; src_vq->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->dev->dev_mutex; ret = vb2_queue_init(src_vq); if (ret) @@ -865,6 +791,7 @@ static int queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *ds dst_vq->ops = &m2mtest_qops; dst_vq->mem_ops = &vb2_vmalloc_memops; dst_vq->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->dev->dev_mutex; return vb2_queue_init(dst_vq); } @@ -936,10 +863,10 @@ static int m2mtest_open(struct file *file) ctx->q_data[V4L2_M2M_DST] = ctx->q_data[V4L2_M2M_SRC]; ctx->colorspace = V4L2_COLORSPACE_REC709; - ctx->m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &queue_init); + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &queue_init); - if (IS_ERR(ctx->m2m_ctx)) { - rc = PTR_ERR(ctx->m2m_ctx); + if (IS_ERR(ctx->fh.m2m_ctx)) { + rc = PTR_ERR(ctx->fh.m2m_ctx); v4l2_ctrl_handler_free(hdl); kfree(ctx); @@ -949,7 +876,8 @@ static int m2mtest_open(struct file *file) v4l2_fh_add(&ctx->fh); atomic_inc(&dev->num_inst); - dprintk(dev, "Created instance %p, m2m_ctx: %p\n", ctx, ctx->m2m_ctx); + dprintk(dev, "Created instance: %p, m2m_ctx: %p\n", + ctx, ctx->fh.m2m_ctx); open_unlock: mutex_unlock(&dev->dev_mutex); @@ -967,7 +895,7 @@ static int m2mtest_release(struct file *file) v4l2_fh_exit(&ctx->fh); v4l2_ctrl_handler_free(&ctx->hdl); mutex_lock(&dev->dev_mutex); - v4l2_m2m_ctx_release(ctx->m2m_ctx); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); mutex_unlock(&dev->dev_mutex); kfree(ctx); @@ -976,34 +904,13 @@ static int m2mtest_release(struct file *file) return 0; } -static unsigned int m2mtest_poll(struct file *file, - struct poll_table_struct *wait) -{ - struct m2mtest_ctx *ctx = file2ctx(file); - - return v4l2_m2m_poll(file, ctx->m2m_ctx, wait); -} - -static int m2mtest_mmap(struct file *file, struct vm_area_struct *vma) -{ - struct m2mtest_dev *dev = video_drvdata(file); - struct m2mtest_ctx *ctx = file2ctx(file); - int res; - - if (mutex_lock_interruptible(&dev->dev_mutex)) - return -ERESTARTSYS; - res = v4l2_m2m_mmap(file, ctx->m2m_ctx, vma); - mutex_unlock(&dev->dev_mutex); - return res; -} - static const struct v4l2_file_operations m2mtest_fops = { .owner = THIS_MODULE, .open = m2mtest_open, .release = m2mtest_release, - .poll = m2mtest_poll, + .poll = v4l2_m2m_fop_poll, .unlocked_ioctl = video_ioctl2, - .mmap = m2mtest_mmap, + .mmap = v4l2_m2m_fop_mmap, }; static struct video_device m2mtest_videodev = { @@ -1019,8 +926,6 @@ static struct v4l2_m2m_ops m2m_ops = { .device_run = device_run, .job_ready = job_ready, .job_abort = job_abort, - .lock = m2mtest_lock, - .unlock = m2mtest_unlock, }; static int m2mtest_probe(struct platform_device *pdev) @@ -1133,4 +1038,3 @@ static int __init m2mtest_init(void) module_init(m2mtest_init); module_exit(m2mtest_exit); - diff --git a/drivers/media/platform/omap3isp/isp.c b/drivers/media/platform/omap3isp/isp.c index fdbdeae3900d..5807185262fe 100644 --- a/drivers/media/platform/omap3isp/isp.c +++ b/drivers/media/platform/omap3isp/isp.c @@ -873,15 +873,12 @@ static int isp_pipeline_enable(struct isp_pipeline *pipe, unsigned long flags; int ret; - /* If the preview engine crashed it might not respond to read/write - * operations on the L4 bus. This would result in a bus fault and a - * kernel oops. Refuse to start streaming in that case. This check must - * be performed before the loop below to avoid starting entities if the - * pipeline won't start anyway (those entities would then likely fail to - * stop, making the problem worse). + /* Refuse to start streaming if an entity included in the pipeline has + * crashed. This check must be performed before the loop below to avoid + * starting entities if the pipeline won't start anyway (those entities + * would then likely fail to stop, making the problem worse). */ - if ((pipe->entities & isp->crashed) & - (1U << isp->isp_prev.subdev.entity.id)) + if (pipe->entities & isp->crashed) return -EIO; spin_lock_irqsave(&pipe->lock, flags); @@ -1014,13 +1011,23 @@ static int isp_pipeline_disable(struct isp_pipeline *pipe) else ret = 0; + /* Handle stop failures. An entity that fails to stop can + * usually just be restarted. Flag the stop failure nonetheless + * to trigger an ISP reset the next time the device is released, + * just in case. + * + * The preview engine is a special case. A failure to stop can + * mean a hardware crash. When that happens the preview engine + * won't respond to read/write operations on the L4 bus anymore, + * resulting in a bus fault and a kernel oops next time it gets + * accessed. Mark it as crashed to prevent pipelines including + * it from being started. + */ if (ret) { dev_info(isp->dev, "Unable to stop %s\n", subdev->name); - /* If the entity failed to stopped, assume it has - * crashed. Mark it as such, the ISP will be reset when - * applications will release it. - */ - isp->crashed |= 1U << subdev->entity.id; + isp->stop_failure = true; + if (subdev == &isp->isp_prev.subdev) + isp->crashed |= 1U << subdev->entity.id; failure = -ETIMEDOUT; } } @@ -1057,6 +1064,23 @@ int omap3isp_pipeline_set_stream(struct isp_pipeline *pipe, } /* + * omap3isp_pipeline_cancel_stream - Cancel stream on a pipeline + * @pipe: ISP pipeline + * + * Cancelling a stream mark all buffers on all video nodes in the pipeline as + * erroneous and makes sure no new buffer can be queued. This function is called + * when a fatal error that prevents any further operation on the pipeline + * occurs. + */ +void omap3isp_pipeline_cancel_stream(struct isp_pipeline *pipe) +{ + if (pipe->input) + omap3isp_video_cancel_stream(pipe->input); + if (pipe->output) + omap3isp_video_cancel_stream(pipe->output); +} + +/* * isp_pipeline_resume - Resume streaming on a pipeline * @pipe: ISP pipeline * @@ -1208,6 +1232,7 @@ static int isp_reset(struct isp_device *isp) udelay(1); } + isp->stop_failure = false; isp->crashed = 0; return 0; } @@ -1619,7 +1644,7 @@ void omap3isp_put(struct isp_device *isp) /* Reset the ISP if an entity has failed to stop. This is the * only way to recover from such conditions. */ - if (isp->crashed) + if (isp->crashed || isp->stop_failure) isp_reset(isp); isp_disable_clocks(isp); } @@ -2130,28 +2155,13 @@ static int isp_map_mem_resource(struct platform_device *pdev, /* request the mem region for the camera registers */ mem = platform_get_resource(pdev, IORESOURCE_MEM, res); - if (!mem) { - dev_err(isp->dev, "no mem resource?\n"); - return -ENODEV; - } - - if (!devm_request_mem_region(isp->dev, mem->start, resource_size(mem), - pdev->name)) { - dev_err(isp->dev, - "cannot reserve camera register I/O region\n"); - return -ENODEV; - } - isp->mmio_base_phys[res] = mem->start; - isp->mmio_size[res] = resource_size(mem); /* map the region */ - isp->mmio_base[res] = devm_ioremap_nocache(isp->dev, - isp->mmio_base_phys[res], - isp->mmio_size[res]); - if (!isp->mmio_base[res]) { - dev_err(isp->dev, "cannot map camera register I/O region\n"); - return -ENODEV; - } + isp->mmio_base[res] = devm_ioremap_resource(isp->dev, mem); + if (IS_ERR(isp->mmio_base[res])) + return PTR_ERR(isp->mmio_base[res]); + + isp->mmio_base_phys[res] = mem->start; return 0; } diff --git a/drivers/media/platform/omap3isp/isp.h b/drivers/media/platform/omap3isp/isp.h index d1e857e41731..081f5ec5a663 100644 --- a/drivers/media/platform/omap3isp/isp.h +++ b/drivers/media/platform/omap3isp/isp.h @@ -152,9 +152,9 @@ struct isp_xclk { * regions. * @mmio_base_phys: Array with physical L4 bus addresses for ISP register * regions. - * @mmio_size: Array with ISP register regions size in bytes. * @stat_lock: Spinlock for handling statistics * @isp_mutex: Mutex for serializing requests to ISP. + * @stop_failure: Indicates that an entity failed to stop. * @crashed: Bitmask of crashed entities (indexed by entity ID) * @has_context: Context has been saved at least once and can be restored. * @ref_count: Reference count for handling multiple ISP requests. @@ -188,11 +188,11 @@ struct isp_device { void __iomem *mmio_base[OMAP3_ISP_IOMEM_LAST]; unsigned long mmio_base_phys[OMAP3_ISP_IOMEM_LAST]; - resource_size_t mmio_size[OMAP3_ISP_IOMEM_LAST]; /* ISP Obj */ spinlock_t stat_lock; /* common lock for statistic drivers */ struct mutex isp_mutex; /* For handling ref_count field */ + bool stop_failure; u32 crashed; int has_context; int ref_count; @@ -238,6 +238,7 @@ int omap3isp_module_sync_is_stopping(wait_queue_head_t *wait, int omap3isp_pipeline_set_stream(struct isp_pipeline *pipe, enum isp_pipeline_stream_state state); +void omap3isp_pipeline_cancel_stream(struct isp_pipeline *pipe); void omap3isp_configure_bridge(struct isp_device *isp, enum ccdc_input_entity input, const struct isp_parallel_platform_data *pdata, diff --git a/drivers/media/platform/omap3isp/ispccdc.c b/drivers/media/platform/omap3isp/ispccdc.c index 907a205da5a5..5db2c88b9ad8 100644 --- a/drivers/media/platform/omap3isp/ispccdc.c +++ b/drivers/media/platform/omap3isp/ispccdc.c @@ -1516,6 +1516,8 @@ static int ccdc_isr_buffer(struct isp_ccdc_device *ccdc) if (ccdc_sbl_wait_idle(ccdc, 1000)) { dev_info(isp->dev, "CCDC won't become idle!\n"); + isp->crashed |= 1U << ccdc->subdev.entity.id; + omap3isp_pipeline_cancel_stream(pipe); goto done; } @@ -2484,7 +2486,8 @@ static int ccdc_init_entities(struct isp_ccdc_device *ccdc) v4l2_set_subdevdata(sd, ccdc); sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE; - pads[CCDC_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[CCDC_PAD_SINK].flags = MEDIA_PAD_FL_SINK + | MEDIA_PAD_FL_MUST_CONNECT; pads[CCDC_PAD_SOURCE_VP].flags = MEDIA_PAD_FL_SOURCE; pads[CCDC_PAD_SOURCE_OF].flags = MEDIA_PAD_FL_SOURCE; diff --git a/drivers/media/platform/omap3isp/ispccp2.c b/drivers/media/platform/omap3isp/ispccp2.c index e71651429dda..e84fe0543e47 100644 --- a/drivers/media/platform/omap3isp/ispccp2.c +++ b/drivers/media/platform/omap3isp/ispccp2.c @@ -1076,7 +1076,8 @@ static int ccp2_init_entities(struct isp_ccp2_device *ccp2) v4l2_set_subdevdata(sd, ccp2); sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; - pads[CCP2_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[CCP2_PAD_SINK].flags = MEDIA_PAD_FL_SINK + | MEDIA_PAD_FL_MUST_CONNECT; pads[CCP2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; me->ops = &ccp2_media_ops; diff --git a/drivers/media/platform/omap3isp/ispcsi2.c b/drivers/media/platform/omap3isp/ispcsi2.c index 6db245d84bbb..620560828a48 100644 --- a/drivers/media/platform/omap3isp/ispcsi2.c +++ b/drivers/media/platform/omap3isp/ispcsi2.c @@ -1245,7 +1245,8 @@ static int csi2_init_entities(struct isp_csi2_device *csi2) sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; pads[CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; - pads[CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK + | MEDIA_PAD_FL_MUST_CONNECT; me->ops = &csi2_media_ops; ret = media_entity_init(me, CSI2_PADS_NUM, pads, 0); diff --git a/drivers/media/platform/omap3isp/isppreview.c b/drivers/media/platform/omap3isp/isppreview.c index cd8831aebdeb..1c776c1186f1 100644 --- a/drivers/media/platform/omap3isp/isppreview.c +++ b/drivers/media/platform/omap3isp/isppreview.c @@ -2283,7 +2283,8 @@ static int preview_init_entities(struct isp_prev_device *prev) v4l2_ctrl_handler_setup(&prev->ctrls); sd->ctrl_handler = &prev->ctrls; - pads[PREV_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[PREV_PAD_SINK].flags = MEDIA_PAD_FL_SINK + | MEDIA_PAD_FL_MUST_CONNECT; pads[PREV_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; me->ops = &preview_media_ops; diff --git a/drivers/media/platform/omap3isp/ispqueue.c b/drivers/media/platform/omap3isp/ispqueue.c index e15f01342058..5f0f8fab1d17 100644 --- a/drivers/media/platform/omap3isp/ispqueue.c +++ b/drivers/media/platform/omap3isp/ispqueue.c @@ -553,8 +553,10 @@ static void isp_video_buffer_query(struct isp_video_buffer *buf, switch (buf->state) { case ISP_BUF_STATE_ERROR: vbuf->flags |= V4L2_BUF_FLAG_ERROR; + /* Fallthrough */ case ISP_BUF_STATE_DONE: vbuf->flags |= V4L2_BUF_FLAG_DONE; + break; case ISP_BUF_STATE_QUEUED: case ISP_BUF_STATE_ACTIVE: vbuf->flags |= V4L2_BUF_FLAG_QUEUED; diff --git a/drivers/media/platform/omap3isp/ispresizer.c b/drivers/media/platform/omap3isp/ispresizer.c index d11fb261d530..0d36b8bc9f98 100644 --- a/drivers/media/platform/omap3isp/ispresizer.c +++ b/drivers/media/platform/omap3isp/ispresizer.c @@ -1532,6 +1532,20 @@ static int resizer_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, return 0; } +static int resizer_link_validate(struct v4l2_subdev *sd, + struct media_link *link, + struct v4l2_subdev_format *source_fmt, + struct v4l2_subdev_format *sink_fmt) +{ + struct isp_res_device *res = v4l2_get_subdevdata(sd); + struct isp_pipeline *pipe = to_isp_pipeline(&sd->entity); + + omap3isp_resizer_max_rate(res, &pipe->max_rate); + + return v4l2_subdev_link_validate_default(sd, link, + source_fmt, sink_fmt); +} + /* * resizer_init_formats - Initialize formats on all pads * @sd: ISP resizer V4L2 subdevice @@ -1570,6 +1584,7 @@ static const struct v4l2_subdev_pad_ops resizer_v4l2_pad_ops = { .set_fmt = resizer_set_format, .get_selection = resizer_get_selection, .set_selection = resizer_set_selection, + .link_validate = resizer_link_validate, }; /* subdev operations */ @@ -1701,7 +1716,8 @@ static int resizer_init_entities(struct isp_res_device *res) v4l2_set_subdevdata(sd, res); sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; - pads[RESZ_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[RESZ_PAD_SINK].flags = MEDIA_PAD_FL_SINK + | MEDIA_PAD_FL_MUST_CONNECT; pads[RESZ_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; me->ops = &resizer_media_ops; diff --git a/drivers/media/platform/omap3isp/ispstat.c b/drivers/media/platform/omap3isp/ispstat.c index 61e17f9bd8b9..a75407c3a726 100644 --- a/drivers/media/platform/omap3isp/ispstat.c +++ b/drivers/media/platform/omap3isp/ispstat.c @@ -1067,7 +1067,7 @@ static int isp_stat_init_entities(struct ispstat *stat, const char *name, subdev->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE; v4l2_set_subdevdata(subdev, stat); - stat->pad.flags = MEDIA_PAD_FL_SINK; + stat->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; me->ops = NULL; return media_entity_init(me, 1, &stat->pad, 0); diff --git a/drivers/media/platform/omap3isp/ispvideo.c b/drivers/media/platform/omap3isp/ispvideo.c index f6304bb074f5..856fdf554035 100644 --- a/drivers/media/platform/omap3isp/ispvideo.c +++ b/drivers/media/platform/omap3isp/ispvideo.c @@ -278,55 +278,6 @@ static int isp_video_get_graph_data(struct isp_video *video, return 0; } -/* - * Validate a pipeline by checking both ends of all links for format - * discrepancies. - * - * Compute the minimum time per frame value as the maximum of time per frame - * limits reported by every block in the pipeline. - * - * Return 0 if all formats match, or -EPIPE if at least one link is found with - * different formats on its two ends or if the pipeline doesn't start with a - * video source (either a subdev with no input pad, or a non-subdev entity). - */ -static int isp_video_validate_pipeline(struct isp_pipeline *pipe) -{ - struct isp_device *isp = pipe->output->isp; - struct media_pad *pad; - struct v4l2_subdev *subdev; - - subdev = isp_video_remote_subdev(pipe->output, NULL); - if (subdev == NULL) - return -EPIPE; - - while (1) { - /* Retrieve the sink format */ - pad = &subdev->entity.pads[0]; - if (!(pad->flags & MEDIA_PAD_FL_SINK)) - break; - - /* Update the maximum frame rate */ - if (subdev == &isp->isp_res.subdev) - omap3isp_resizer_max_rate(&isp->isp_res, - &pipe->max_rate); - - /* Retrieve the source format. Return an error if no source - * entity can be found, and stop checking the pipeline if the - * source entity isn't a subdev. - */ - pad = media_entity_remote_pad(pad); - if (pad == NULL) - return -EPIPE; - - if (media_entity_type(pad->entity) != MEDIA_ENT_T_V4L2_SUBDEV) - break; - - subdev = media_entity_to_v4l2_subdev(pad->entity); - } - - return 0; -} - static int __isp_video_get_format(struct isp_video *video, struct v4l2_format *format) { @@ -460,6 +411,15 @@ static int isp_video_buffer_prepare(struct isp_video_buffer *buf) struct isp_video *video = vfh->video; unsigned long addr; + /* Refuse to prepare the buffer is the video node has registered an + * error. We don't need to take any lock here as the operation is + * inherently racy. The authoritative check will be performed in the + * queue handler, which can't return an error, this check is just a best + * effort to notify userspace as early as possible. + */ + if (unlikely(video->error)) + return -EIO; + addr = ispmmu_vmap(video->isp, buf->sglist, buf->sglen); if (IS_ERR_VALUE(addr)) return -EIO; @@ -496,6 +456,12 @@ static void isp_video_buffer_queue(struct isp_video_buffer *buf) unsigned int empty; unsigned int start; + if (unlikely(video->error)) { + buf->state = ISP_BUF_STATE_ERROR; + wake_up(&buf->wait); + return; + } + empty = list_empty(&video->dmaqueue); list_add_tail(&buffer->buffer.irqlist, &video->dmaqueue); @@ -618,6 +584,36 @@ struct isp_buffer *omap3isp_video_buffer_next(struct isp_video *video) } /* + * omap3isp_video_cancel_stream - Cancel stream on a video node + * @video: ISP video object + * + * Cancelling a stream mark all buffers on the video node as erroneous and makes + * sure no new buffer can be queued. + */ +void omap3isp_video_cancel_stream(struct isp_video *video) +{ + struct isp_video_queue *queue = video->queue; + unsigned long flags; + + spin_lock_irqsave(&queue->irqlock, flags); + + while (!list_empty(&video->dmaqueue)) { + struct isp_video_buffer *buf; + + buf = list_first_entry(&video->dmaqueue, + struct isp_video_buffer, irqlist); + list_del(&buf->irqlist); + + buf->state = ISP_BUF_STATE_ERROR; + wake_up(&buf->wait); + } + + video->error = true; + + spin_unlock_irqrestore(&queue->irqlock, flags); +} + +/* * omap3isp_video_resume - Perform resume operation on the buffers * @video: ISP video object * @continuous: Pipeline is in single shot mode if 0 or continuous mode otherwise @@ -1051,11 +1047,6 @@ isp_video_streamon(struct file *file, void *fh, enum v4l2_buf_type type) if (ret < 0) goto err_check_format; - /* Validate the pipeline and update its state. */ - ret = isp_video_validate_pipeline(pipe); - if (ret < 0) - goto err_check_format; - pipe->error = false; spin_lock_irqsave(&pipe->lock, flags); @@ -1159,6 +1150,7 @@ isp_video_streamoff(struct file *file, void *fh, enum v4l2_buf_type type) omap3isp_video_queue_streamoff(&vfh->queue); video->queue = NULL; video->streaming = 0; + video->error = false; if (video->isp->pdata->set_constraints) video->isp->pdata->set_constraints(video->isp, false); @@ -1332,11 +1324,13 @@ int omap3isp_video_init(struct isp_video *video, const char *name) switch (video->type) { case V4L2_BUF_TYPE_VIDEO_CAPTURE: direction = "output"; - video->pad.flags = MEDIA_PAD_FL_SINK; + video->pad.flags = MEDIA_PAD_FL_SINK + | MEDIA_PAD_FL_MUST_CONNECT; break; case V4L2_BUF_TYPE_VIDEO_OUTPUT: direction = "input"; - video->pad.flags = MEDIA_PAD_FL_SOURCE; + video->pad.flags = MEDIA_PAD_FL_SOURCE + | MEDIA_PAD_FL_MUST_CONNECT; video->video.vfl_dir = VFL_DIR_TX; break; diff --git a/drivers/media/platform/omap3isp/ispvideo.h b/drivers/media/platform/omap3isp/ispvideo.h index 1ad470ec2b9d..4e194076cc60 100644 --- a/drivers/media/platform/omap3isp/ispvideo.h +++ b/drivers/media/platform/omap3isp/ispvideo.h @@ -178,6 +178,7 @@ struct isp_video { /* Pipeline state */ struct isp_pipeline pipe; struct mutex stream_lock; /* pipeline and stream states */ + bool error; /* Video buffers queue */ struct isp_video_queue *queue; @@ -207,6 +208,7 @@ int omap3isp_video_register(struct isp_video *video, struct v4l2_device *vdev); void omap3isp_video_unregister(struct isp_video *video); struct isp_buffer *omap3isp_video_buffer_next(struct isp_video *video); +void omap3isp_video_cancel_stream(struct isp_video *video); void omap3isp_video_resume(struct isp_video *video, int continuous); struct media_pad *omap3isp_video_remote_pad(struct isp_video *video); diff --git a/drivers/media/platform/s5p-g2d/g2d.c b/drivers/media/platform/s5p-g2d/g2d.c index 0b2948376aee..0fcf7d75e841 100644 --- a/drivers/media/platform/s5p-g2d/g2d.c +++ b/drivers/media/platform/s5p-g2d/g2d.c @@ -136,10 +136,9 @@ static int g2d_buf_prepare(struct vb2_buffer *vb) static void g2d_buf_queue(struct vb2_buffer *vb) { struct g2d_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); - v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vb); } - static struct vb2_ops g2d_qops = { .queue_setup = g2d_queue_setup, .buf_prepare = g2d_buf_prepare, @@ -159,6 +158,7 @@ static int queue_init(void *priv, struct vb2_queue *src_vq, src_vq->mem_ops = &vb2_dma_contig_memops; src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); src_vq->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->dev->mutex; ret = vb2_queue_init(src_vq); if (ret) @@ -171,6 +171,7 @@ static int queue_init(void *priv, struct vb2_queue *src_vq, dst_vq->mem_ops = &vb2_dma_contig_memops; dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); dst_vq->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->dev->mutex; return vb2_queue_init(dst_vq); } @@ -253,9 +254,9 @@ static int g2d_open(struct file *file) kfree(ctx); return -ERESTARTSYS; } - ctx->m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &queue_init); - if (IS_ERR(ctx->m2m_ctx)) { - ret = PTR_ERR(ctx->m2m_ctx); + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); mutex_unlock(&dev->mutex); kfree(ctx); return ret; @@ -324,7 +325,7 @@ static int vidioc_g_fmt(struct file *file, void *prv, struct v4l2_format *f) struct vb2_queue *vq; struct g2d_frame *frm; - vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); if (!vq) return -EINVAL; frm = get_frame(ctx, f->type); @@ -384,7 +385,7 @@ static int vidioc_s_fmt(struct file *file, void *prv, struct v4l2_format *f) ret = vidioc_try_fmt(file, prv, f); if (ret) return ret; - vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); if (vb2_is_busy(vq)) { v4l2_err(&dev->v4l2_dev, "queue (%d) bust\n", f->type); return -EBUSY; @@ -410,72 +411,6 @@ static int vidioc_s_fmt(struct file *file, void *prv, struct v4l2_format *f) return 0; } -static unsigned int g2d_poll(struct file *file, struct poll_table_struct *wait) -{ - struct g2d_ctx *ctx = fh2ctx(file->private_data); - struct g2d_dev *dev = ctx->dev; - unsigned int res; - - mutex_lock(&dev->mutex); - res = v4l2_m2m_poll(file, ctx->m2m_ctx, wait); - mutex_unlock(&dev->mutex); - return res; -} - -static int g2d_mmap(struct file *file, struct vm_area_struct *vma) -{ - struct g2d_ctx *ctx = fh2ctx(file->private_data); - struct g2d_dev *dev = ctx->dev; - int ret; - - if (mutex_lock_interruptible(&dev->mutex)) - return -ERESTARTSYS; - ret = v4l2_m2m_mmap(file, ctx->m2m_ctx, vma); - mutex_unlock(&dev->mutex); - return ret; -} - -static int vidioc_reqbufs(struct file *file, void *priv, - struct v4l2_requestbuffers *reqbufs) -{ - struct g2d_ctx *ctx = priv; - return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs); -} - -static int vidioc_querybuf(struct file *file, void *priv, - struct v4l2_buffer *buf) -{ - struct g2d_ctx *ctx = priv; - return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf); -} - -static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) -{ - struct g2d_ctx *ctx = priv; - return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); -} - -static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) -{ - struct g2d_ctx *ctx = priv; - return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); -} - - -static int vidioc_streamon(struct file *file, void *priv, - enum v4l2_buf_type type) -{ - struct g2d_ctx *ctx = priv; - return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); -} - -static int vidioc_streamoff(struct file *file, void *priv, - enum v4l2_buf_type type) -{ - struct g2d_ctx *ctx = priv; - return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); -} - static int vidioc_cropcap(struct file *file, void *priv, struct v4l2_cropcap *cr) { @@ -551,20 +486,6 @@ static int vidioc_s_crop(struct file *file, void *prv, const struct v4l2_crop *c return 0; } -static void g2d_lock(void *prv) -{ - struct g2d_ctx *ctx = prv; - struct g2d_dev *dev = ctx->dev; - mutex_lock(&dev->mutex); -} - -static void g2d_unlock(void *prv) -{ - struct g2d_ctx *ctx = prv; - struct g2d_dev *dev = ctx->dev; - mutex_unlock(&dev->mutex); -} - static void job_abort(void *prv) { struct g2d_ctx *ctx = prv; @@ -589,8 +510,8 @@ static void device_run(void *prv) dev->curr = ctx; - src = v4l2_m2m_next_src_buf(ctx->m2m_ctx); - dst = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); clk_enable(dev->gate); g2d_reset(dev); @@ -631,8 +552,8 @@ static irqreturn_t g2d_isr(int irq, void *prv) BUG_ON(ctx == NULL); - src = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); - dst = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); + src = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); BUG_ON(src == NULL); BUG_ON(dst == NULL); @@ -642,7 +563,7 @@ static irqreturn_t g2d_isr(int irq, void *prv) v4l2_m2m_buf_done(src, VB2_BUF_STATE_DONE); v4l2_m2m_buf_done(dst, VB2_BUF_STATE_DONE); - v4l2_m2m_job_finish(dev->m2m_dev, ctx->m2m_ctx); + v4l2_m2m_job_finish(dev->m2m_dev, ctx->fh.m2m_ctx); dev->curr = NULL; wake_up(&dev->irq_queue); @@ -653,9 +574,9 @@ static const struct v4l2_file_operations g2d_fops = { .owner = THIS_MODULE, .open = g2d_open, .release = g2d_release, - .poll = g2d_poll, + .poll = v4l2_m2m_fop_poll, .unlocked_ioctl = video_ioctl2, - .mmap = g2d_mmap, + .mmap = v4l2_m2m_fop_mmap, }; static const struct v4l2_ioctl_ops g2d_ioctl_ops = { @@ -671,14 +592,13 @@ static const struct v4l2_ioctl_ops g2d_ioctl_ops = { .vidioc_try_fmt_vid_out = vidioc_try_fmt, .vidioc_s_fmt_vid_out = vidioc_s_fmt, - .vidioc_reqbufs = vidioc_reqbufs, - .vidioc_querybuf = vidioc_querybuf, - - .vidioc_qbuf = vidioc_qbuf, - .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, - .vidioc_streamon = vidioc_streamon, - .vidioc_streamoff = vidioc_streamoff, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, .vidioc_g_crop = vidioc_g_crop, .vidioc_s_crop = vidioc_s_crop, @@ -697,8 +617,6 @@ static struct video_device g2d_videodev = { static struct v4l2_m2m_ops g2d_m2m_ops = { .device_run = device_run, .job_abort = job_abort, - .lock = g2d_lock, - .unlock = g2d_unlock, }; static const struct of_device_id exynos_g2d_match[]; diff --git a/drivers/media/platform/s5p-g2d/g2d.h b/drivers/media/platform/s5p-g2d/g2d.h index 300ca05ba404..b0e52ab7ecdb 100644 --- a/drivers/media/platform/s5p-g2d/g2d.h +++ b/drivers/media/platform/s5p-g2d/g2d.h @@ -57,7 +57,6 @@ struct g2d_frame { struct g2d_ctx { struct v4l2_fh fh; struct g2d_dev *dev; - struct v4l2_m2m_ctx *m2m_ctx; struct g2d_frame in; struct g2d_frame out; struct v4l2_ctrl *ctrl_hflip; diff --git a/drivers/media/platform/s5p-jpeg/Makefile b/drivers/media/platform/s5p-jpeg/Makefile index d18cb5edd2d5..a1a9169254c3 100644 --- a/drivers/media/platform/s5p-jpeg/Makefile +++ b/drivers/media/platform/s5p-jpeg/Makefile @@ -1,2 +1,2 @@ -s5p-jpeg-objs := jpeg-core.o +s5p-jpeg-objs := jpeg-core.o jpeg-hw-exynos4.o jpeg-hw-s5p.o obj-$(CONFIG_VIDEO_SAMSUNG_S5P_JPEG) += s5p-jpeg.o diff --git a/drivers/media/platform/s5p-jpeg/jpeg-core.c b/drivers/media/platform/s5p-jpeg/jpeg-core.c index 9b88a4601007..a1c78c870b68 100644 --- a/drivers/media/platform/s5p-jpeg/jpeg-core.c +++ b/drivers/media/platform/s5p-jpeg/jpeg-core.c @@ -1,9 +1,10 @@ /* linux/drivers/media/platform/s5p-jpeg/jpeg-core.c * - * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * Copyright (c) 2011-2013 Samsung Electronics Co., Ltd. * http://www.samsung.com * * Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com> + * Author: Jacek Anaszewski <j.anaszewski@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 @@ -17,6 +18,7 @@ #include <linux/io.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/slab.h> @@ -28,70 +30,234 @@ #include <media/videobuf2-dma-contig.h> #include "jpeg-core.h" -#include "jpeg-hw.h" +#include "jpeg-hw-s5p.h" +#include "jpeg-hw-exynos4.h" +#include "jpeg-regs.h" -static struct s5p_jpeg_fmt formats_enc[] = { +static struct s5p_jpeg_fmt sjpeg_formats[] = { { .name = "JPEG JFIF", .fourcc = V4L2_PIX_FMT_JPEG, + .flags = SJPEG_FMT_FLAG_ENC_CAPTURE | + SJPEG_FMT_FLAG_DEC_OUTPUT | + SJPEG_FMT_FLAG_S5P | + SJPEG_FMT_FLAG_EXYNOS4, + }, + { + .name = "YUV 4:2:2 packed, YCbYCr", + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, .colplanes = 1, - .types = MEM2MEM_CAPTURE, + .h_align = 4, + .v_align = 3, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_S5P | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, }, { .name = "YUV 4:2:2 packed, YCbYCr", .fourcc = V4L2_PIX_FMT_YUYV, .depth = 16, .colplanes = 1, - .types = MEM2MEM_OUTPUT, + .h_align = 1, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, + }, + { + .name = "YUV 4:2:2 packed, YCrYCb", + .fourcc = V4L2_PIX_FMT_YVYU, + .depth = 16, + .colplanes = 1, + .h_align = 1, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, }, { .name = "RGB565", .fourcc = V4L2_PIX_FMT_RGB565, .depth = 16, .colplanes = 1, - .types = MEM2MEM_OUTPUT, + .h_align = 0, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, }, -}; -#define NUM_FORMATS_ENC ARRAY_SIZE(formats_enc) - -static struct s5p_jpeg_fmt formats_dec[] = { { - .name = "YUV 4:2:0 planar, YCbCr", - .fourcc = V4L2_PIX_FMT_YUV420, - .depth = 12, - .colplanes = 3, - .h_align = 4, - .v_align = 4, - .types = MEM2MEM_CAPTURE, + .name = "RGB565", + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + .colplanes = 1, + .h_align = 0, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_S5P | + SJPEG_FMT_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, }, { - .name = "YUV 4:2:2 packed, YCbYCr", - .fourcc = V4L2_PIX_FMT_YUYV, - .depth = 16, + .name = "ARGB8888, 32 bpp", + .fourcc = V4L2_PIX_FMT_RGB32, + .depth = 32, .colplanes = 1, + .h_align = 0, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + }, + { + .name = "YUV 4:4:4 planar, Y/CbCr", + .fourcc = V4L2_PIX_FMT_NV24, + .depth = 24, + .colplanes = 2, + .h_align = 0, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + }, + { + .name = "YUV 4:4:4 planar, Y/CrCb", + .fourcc = V4L2_PIX_FMT_NV42, + .depth = 24, + .colplanes = 2, + .h_align = 0, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + }, + { + .name = "YUV 4:2:2 planar, Y/CrCb", + .fourcc = V4L2_PIX_FMT_NV61, + .depth = 16, + .colplanes = 2, + .h_align = 1, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, + }, + { + .name = "YUV 4:2:2 planar, Y/CbCr", + .fourcc = V4L2_PIX_FMT_NV16, + .depth = 16, + .colplanes = 2, + .h_align = 1, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, + }, + { + .name = "YUV 4:2:0 planar, Y/CbCr", + .fourcc = V4L2_PIX_FMT_NV12, + .depth = 16, + .colplanes = 2, + .h_align = 1, + .v_align = 1, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, + }, + { + .name = "YUV 4:2:0 planar, Y/CbCr", + .fourcc = V4L2_PIX_FMT_NV12, + .depth = 16, + .colplanes = 4, .h_align = 4, - .v_align = 3, - .types = MEM2MEM_CAPTURE, + .v_align = 1, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_S5P | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, }, { - .name = "JPEG JFIF", - .fourcc = V4L2_PIX_FMT_JPEG, + .name = "YUV 4:2:0 planar, Y/CrCb", + .fourcc = V4L2_PIX_FMT_NV21, + .depth = 12, + .colplanes = 2, + .h_align = 1, + .v_align = 1, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, + }, + { + .name = "YUV 4:2:0 contiguous 3-planar, Y/Cb/Cr", + .fourcc = V4L2_PIX_FMT_YUV420, + .depth = 12, + .colplanes = 3, + .h_align = 1, + .v_align = 1, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, + }, + { + .name = "Gray", + .fourcc = V4L2_PIX_FMT_GREY, + .depth = 8, .colplanes = 1, - .types = MEM2MEM_OUTPUT, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY, }, }; -#define NUM_FORMATS_DEC ARRAY_SIZE(formats_dec) +#define SJPEG_NUM_FORMATS ARRAY_SIZE(sjpeg_formats) static const unsigned char qtbl_luminance[4][64] = { - {/* level 1 - high quality */ - 8, 6, 6, 8, 12, 14, 16, 17, - 6, 6, 6, 8, 10, 13, 12, 15, - 6, 6, 7, 8, 13, 14, 18, 24, - 8, 8, 8, 14, 13, 19, 24, 35, - 12, 10, 13, 13, 20, 26, 34, 39, - 14, 13, 14, 19, 26, 34, 39, 39, - 16, 12, 18, 24, 34, 39, 39, 39, - 17, 15, 24, 35, 39, 39, 39, 39 + {/*level 0 - high compression quality */ + 20, 16, 25, 39, 50, 46, 62, 68, + 16, 18, 23, 38, 38, 53, 65, 68, + 25, 23, 31, 38, 53, 65, 68, 68, + 39, 38, 38, 53, 65, 68, 68, 68, + 50, 38, 53, 65, 68, 68, 68, 68, + 46, 53, 65, 68, 68, 68, 68, 68, + 62, 65, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68 + }, + {/* level 1 */ + 16, 11, 11, 16, 23, 27, 31, 30, + 11, 12, 12, 15, 20, 23, 23, 30, + 11, 12, 13, 16, 23, 26, 35, 47, + 16, 15, 16, 23, 26, 37, 47, 64, + 23, 20, 23, 26, 39, 51, 64, 64, + 27, 23, 26, 37, 51, 64, 64, 64, + 31, 23, 35, 47, 64, 64, 64, 64, + 30, 30, 47, 64, 64, 64, 64, 64 }, {/* level 2 */ 12, 8, 8, 12, 17, 21, 24, 23, @@ -103,38 +269,38 @@ static const unsigned char qtbl_luminance[4][64] = { 24, 18, 27, 36, 51, 59, 59, 59, 23, 23, 36, 53, 59, 59, 59, 59 }, - {/* level 3 */ - 16, 11, 11, 16, 23, 27, 31, 30, - 11, 12, 12, 15, 20, 23, 23, 30, - 11, 12, 13, 16, 23, 26, 35, 47, - 16, 15, 16, 23, 26, 37, 47, 64, - 23, 20, 23, 26, 39, 51, 64, 64, - 27, 23, 26, 37, 51, 64, 64, 64, - 31, 23, 35, 47, 64, 64, 64, 64, - 30, 30, 47, 64, 64, 64, 64, 64 - }, - {/*level 4 - low quality */ - 20, 16, 25, 39, 50, 46, 62, 68, - 16, 18, 23, 38, 38, 53, 65, 68, - 25, 23, 31, 38, 53, 65, 68, 68, - 39, 38, 38, 53, 65, 68, 68, 68, - 50, 38, 53, 65, 68, 68, 68, 68, - 46, 53, 65, 68, 68, 68, 68, 68, - 62, 65, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68 + {/* level 3 - low compression quality */ + 8, 6, 6, 8, 12, 14, 16, 17, + 6, 6, 6, 8, 10, 13, 12, 15, + 6, 6, 7, 8, 13, 14, 18, 24, + 8, 8, 8, 14, 13, 19, 24, 35, + 12, 10, 13, 13, 20, 26, 34, 39, + 14, 13, 14, 19, 26, 34, 39, 39, + 16, 12, 18, 24, 34, 39, 39, 39, + 17, 15, 24, 35, 39, 39, 39, 39 } }; static const unsigned char qtbl_chrominance[4][64] = { - {/* level 1 - high quality */ - 9, 8, 9, 11, 14, 17, 19, 24, - 8, 10, 9, 11, 14, 13, 17, 22, - 9, 9, 13, 14, 13, 15, 23, 26, - 11, 11, 14, 14, 15, 20, 26, 33, - 14, 14, 13, 15, 20, 24, 33, 39, - 17, 13, 15, 20, 24, 32, 39, 39, - 19, 17, 23, 26, 33, 39, 39, 39, - 24, 22, 26, 33, 39, 39, 39, 39 + {/*level 0 - high compression quality */ + 21, 25, 32, 38, 54, 68, 68, 68, + 25, 28, 24, 38, 54, 68, 68, 68, + 32, 24, 32, 43, 66, 68, 68, 68, + 38, 38, 43, 53, 68, 68, 68, 68, + 54, 54, 66, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68 + }, + {/* level 1 */ + 17, 15, 17, 21, 20, 26, 38, 48, + 15, 19, 18, 17, 20, 26, 35, 43, + 17, 18, 20, 22, 26, 30, 46, 53, + 21, 17, 22, 28, 30, 39, 53, 64, + 20, 20, 26, 30, 39, 48, 64, 64, + 26, 26, 30, 39, 48, 63, 64, 64, + 38, 35, 46, 53, 64, 64, 64, 64, + 48, 43, 53, 64, 64, 64, 64, 64 }, {/* level 2 */ 13, 11, 13, 16, 20, 20, 29, 37, @@ -146,25 +312,15 @@ static const unsigned char qtbl_chrominance[4][64] = { 29, 26, 35, 40, 50, 59, 59, 59, 37, 32, 40, 50, 59, 59, 59, 59 }, - {/* level 3 */ - 17, 15, 17, 21, 20, 26, 38, 48, - 15, 19, 18, 17, 20, 26, 35, 43, - 17, 18, 20, 22, 26, 30, 46, 53, - 21, 17, 22, 28, 30, 39, 53, 64, - 20, 20, 26, 30, 39, 48, 64, 64, - 26, 26, 30, 39, 48, 63, 64, 64, - 38, 35, 46, 53, 64, 64, 64, 64, - 48, 43, 53, 64, 64, 64, 64, 64 - }, - {/*level 4 - low quality */ - 21, 25, 32, 38, 54, 68, 68, 68, - 25, 28, 24, 38, 54, 68, 68, 68, - 32, 24, 32, 43, 66, 68, 68, 68, - 38, 38, 43, 53, 68, 68, 68, 68, - 54, 54, 66, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68 + {/* level 3 - low compression quality */ + 9, 8, 9, 11, 14, 17, 19, 24, + 8, 10, 9, 11, 14, 13, 17, 22, + 9, 9, 13, 14, 13, 15, 23, 26, + 11, 11, 14, 14, 15, 20, 26, 33, + 14, 14, 13, 15, 20, 24, 33, 39, + 17, 13, 15, 20, 24, 32, 39, 39, + 19, 17, 23, 26, 33, 39, 39, 39, + 24, 22, 26, 33, 39, 39, 39, 39 } }; @@ -202,6 +358,106 @@ static const unsigned char hactblg0[162] = { 0xf9, 0xfa }; +/* + * Fourcc downgrade schema lookup tables for 422 and 420 + * chroma subsampling - fourcc on each position maps on the + * fourcc from the table fourcc_to_dwngrd_schema_id which allows + * to get the most suitable fourcc counterpart for the given + * downgraded subsampling property. + */ +static const u32 subs422_fourcc_dwngrd_schema[] = { + V4L2_PIX_FMT_NV16, + V4L2_PIX_FMT_NV61, +}; + +static const u32 subs420_fourcc_dwngrd_schema[] = { + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_NV21, + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_NV21, + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_NV21, + V4L2_PIX_FMT_GREY, + V4L2_PIX_FMT_GREY, + V4L2_PIX_FMT_GREY, + V4L2_PIX_FMT_GREY, +}; + +/* + * Lookup table for translation of a fourcc to the position + * of its downgraded counterpart in the *fourcc_dwngrd_schema + * tables. + */ +static const u32 fourcc_to_dwngrd_schema_id[] = { + V4L2_PIX_FMT_NV24, + V4L2_PIX_FMT_NV42, + V4L2_PIX_FMT_NV16, + V4L2_PIX_FMT_NV61, + V4L2_PIX_FMT_YUYV, + V4L2_PIX_FMT_YVYU, + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_NV21, + V4L2_PIX_FMT_YUV420, + V4L2_PIX_FMT_GREY, +}; + +static int s5p_jpeg_get_dwngrd_sch_id_by_fourcc(u32 fourcc) +{ + int i; + for (i = 0; i < ARRAY_SIZE(fourcc_to_dwngrd_schema_id); ++i) { + if (fourcc_to_dwngrd_schema_id[i] == fourcc) + return i; + } + + return -EINVAL; +} + +static int s5p_jpeg_adjust_fourcc_to_subsampling( + enum v4l2_jpeg_chroma_subsampling subs, + u32 in_fourcc, + u32 *out_fourcc, + struct s5p_jpeg_ctx *ctx) +{ + int dwngrd_sch_id; + + if (ctx->subsampling != V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY) { + dwngrd_sch_id = + s5p_jpeg_get_dwngrd_sch_id_by_fourcc(in_fourcc); + if (dwngrd_sch_id < 0) + return -EINVAL; + } + + switch (ctx->subsampling) { + case V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY: + *out_fourcc = V4L2_PIX_FMT_GREY; + break; + case V4L2_JPEG_CHROMA_SUBSAMPLING_420: + if (dwngrd_sch_id > + ARRAY_SIZE(subs420_fourcc_dwngrd_schema) - 1) + return -EINVAL; + *out_fourcc = subs420_fourcc_dwngrd_schema[dwngrd_sch_id]; + break; + case V4L2_JPEG_CHROMA_SUBSAMPLING_422: + if (dwngrd_sch_id > + ARRAY_SIZE(subs422_fourcc_dwngrd_schema) - 1) + return -EINVAL; + *out_fourcc = subs422_fourcc_dwngrd_schema[dwngrd_sch_id]; + break; + default: + *out_fourcc = V4L2_PIX_FMT_GREY; + break; + } + + return 0; +} + +static int exynos4x12_decoded_subsampling[] = { + V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY, + V4L2_JPEG_CHROMA_SUBSAMPLING_444, + V4L2_JPEG_CHROMA_SUBSAMPLING_422, + V4L2_JPEG_CHROMA_SUBSAMPLING_420, +}; + static inline struct s5p_jpeg_ctx *ctrl_to_ctx(struct v4l2_ctrl *c) { return container_of(c->handler, struct s5p_jpeg_ctx, ctrl_handler); @@ -212,8 +468,24 @@ static inline struct s5p_jpeg_ctx *fh_to_ctx(struct v4l2_fh *fh) return container_of(fh, struct s5p_jpeg_ctx, fh); } -static inline void jpeg_set_qtbl(void __iomem *regs, const unsigned char *qtbl, - unsigned long tab, int len) +static int s5p_jpeg_to_user_subsampling(struct s5p_jpeg_ctx *ctx) +{ + WARN_ON(ctx->subsampling > 3); + + if (ctx->jpeg->variant->version == SJPEG_S5P) { + if (ctx->subsampling > 2) + return V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY; + return ctx->subsampling; + } else { + if (ctx->subsampling > 2) + return V4L2_JPEG_CHROMA_SUBSAMPLING_420; + return exynos4x12_decoded_subsampling[ctx->subsampling]; + } +} + +static inline void s5p_jpeg_set_qtbl(void __iomem *regs, + const unsigned char *qtbl, + unsigned long tab, int len) { int i; @@ -221,22 +493,25 @@ static inline void jpeg_set_qtbl(void __iomem *regs, const unsigned char *qtbl, writel((unsigned int)qtbl[i], regs + tab + (i * 0x04)); } -static inline void jpeg_set_qtbl_lum(void __iomem *regs, int quality) +static inline void s5p_jpeg_set_qtbl_lum(void __iomem *regs, int quality) { /* this driver fills quantisation table 0 with data for luma */ - jpeg_set_qtbl(regs, qtbl_luminance[quality], S5P_JPG_QTBL_CONTENT(0), - ARRAY_SIZE(qtbl_luminance[quality])); + s5p_jpeg_set_qtbl(regs, qtbl_luminance[quality], + S5P_JPG_QTBL_CONTENT(0), + ARRAY_SIZE(qtbl_luminance[quality])); } -static inline void jpeg_set_qtbl_chr(void __iomem *regs, int quality) +static inline void s5p_jpeg_set_qtbl_chr(void __iomem *regs, int quality) { /* this driver fills quantisation table 1 with data for chroma */ - jpeg_set_qtbl(regs, qtbl_chrominance[quality], S5P_JPG_QTBL_CONTENT(1), - ARRAY_SIZE(qtbl_chrominance[quality])); + s5p_jpeg_set_qtbl(regs, qtbl_chrominance[quality], + S5P_JPG_QTBL_CONTENT(1), + ARRAY_SIZE(qtbl_chrominance[quality])); } -static inline void jpeg_set_htbl(void __iomem *regs, const unsigned char *htbl, - unsigned long tab, int len) +static inline void s5p_jpeg_set_htbl(void __iomem *regs, + const unsigned char *htbl, + unsigned long tab, int len) { int i; @@ -244,28 +519,84 @@ static inline void jpeg_set_htbl(void __iomem *regs, const unsigned char *htbl, writel((unsigned int)htbl[i], regs + tab + (i * 0x04)); } -static inline void jpeg_set_hdctbl(void __iomem *regs) +static inline void s5p_jpeg_set_hdctbl(void __iomem *regs) { /* this driver fills table 0 for this component */ - jpeg_set_htbl(regs, hdctbl0, S5P_JPG_HDCTBL(0), ARRAY_SIZE(hdctbl0)); + s5p_jpeg_set_htbl(regs, hdctbl0, S5P_JPG_HDCTBL(0), + ARRAY_SIZE(hdctbl0)); } -static inline void jpeg_set_hdctblg(void __iomem *regs) +static inline void s5p_jpeg_set_hdctblg(void __iomem *regs) { /* this driver fills table 0 for this component */ - jpeg_set_htbl(regs, hdctblg0, S5P_JPG_HDCTBLG(0), ARRAY_SIZE(hdctblg0)); + s5p_jpeg_set_htbl(regs, hdctblg0, S5P_JPG_HDCTBLG(0), + ARRAY_SIZE(hdctblg0)); } -static inline void jpeg_set_hactbl(void __iomem *regs) +static inline void s5p_jpeg_set_hactbl(void __iomem *regs) { /* this driver fills table 0 for this component */ - jpeg_set_htbl(regs, hactbl0, S5P_JPG_HACTBL(0), ARRAY_SIZE(hactbl0)); + s5p_jpeg_set_htbl(regs, hactbl0, S5P_JPG_HACTBL(0), + ARRAY_SIZE(hactbl0)); } -static inline void jpeg_set_hactblg(void __iomem *regs) +static inline void s5p_jpeg_set_hactblg(void __iomem *regs) { /* this driver fills table 0 for this component */ - jpeg_set_htbl(regs, hactblg0, S5P_JPG_HACTBLG(0), ARRAY_SIZE(hactblg0)); + s5p_jpeg_set_htbl(regs, hactblg0, S5P_JPG_HACTBLG(0), + ARRAY_SIZE(hactblg0)); +} + +static inline void exynos4_jpeg_set_tbl(void __iomem *regs, + const unsigned char *tbl, + unsigned long tab, int len) +{ + int i; + unsigned int dword; + + for (i = 0; i < len; i += 4) { + dword = tbl[i] | + (tbl[i + 1] << 8) | + (tbl[i + 2] << 16) | + (tbl[i + 3] << 24); + writel(dword, regs + tab + i); + } +} + +static inline void exynos4_jpeg_set_qtbl_lum(void __iomem *regs, int quality) +{ + /* this driver fills quantisation table 0 with data for luma */ + exynos4_jpeg_set_tbl(regs, qtbl_luminance[quality], + EXYNOS4_QTBL_CONTENT(0), + ARRAY_SIZE(qtbl_luminance[quality])); +} + +static inline void exynos4_jpeg_set_qtbl_chr(void __iomem *regs, int quality) +{ + /* this driver fills quantisation table 1 with data for chroma */ + exynos4_jpeg_set_tbl(regs, qtbl_chrominance[quality], + EXYNOS4_QTBL_CONTENT(1), + ARRAY_SIZE(qtbl_chrominance[quality])); +} + +void exynos4_jpeg_set_huff_tbl(void __iomem *base) +{ + exynos4_jpeg_set_tbl(base, hdctbl0, EXYNOS4_HUFF_TBL_HDCLL, + ARRAY_SIZE(hdctbl0)); + exynos4_jpeg_set_tbl(base, hdctbl0, EXYNOS4_HUFF_TBL_HDCCL, + ARRAY_SIZE(hdctbl0)); + exynos4_jpeg_set_tbl(base, hdctblg0, EXYNOS4_HUFF_TBL_HDCLV, + ARRAY_SIZE(hdctblg0)); + exynos4_jpeg_set_tbl(base, hdctblg0, EXYNOS4_HUFF_TBL_HDCCV, + ARRAY_SIZE(hdctblg0)); + exynos4_jpeg_set_tbl(base, hactbl0, EXYNOS4_HUFF_TBL_HACLL, + ARRAY_SIZE(hactbl0)); + exynos4_jpeg_set_tbl(base, hactbl0, EXYNOS4_HUFF_TBL_HACCL, + ARRAY_SIZE(hactbl0)); + exynos4_jpeg_set_tbl(base, hactblg0, EXYNOS4_HUFF_TBL_HACLV, + ARRAY_SIZE(hactblg0)); + exynos4_jpeg_set_tbl(base, hactblg0, EXYNOS4_HUFF_TBL_HACCV, + ARRAY_SIZE(hactblg0)); } /* @@ -276,8 +607,8 @@ static inline void jpeg_set_hactblg(void __iomem *regs) static int queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq); -static struct s5p_jpeg_fmt *s5p_jpeg_find_format(unsigned int mode, - __u32 pixelformat); +static struct s5p_jpeg_fmt *s5p_jpeg_find_format(struct s5p_jpeg_ctx *ctx, + __u32 pixelformat, unsigned int fmt_type); static int s5p_jpeg_controls_create(struct s5p_jpeg_ctx *ctx); static int s5p_jpeg_open(struct file *file) @@ -285,7 +616,7 @@ static int s5p_jpeg_open(struct file *file) struct s5p_jpeg *jpeg = video_drvdata(file); struct video_device *vfd = video_devdata(file); struct s5p_jpeg_ctx *ctx; - struct s5p_jpeg_fmt *out_fmt; + struct s5p_jpeg_fmt *out_fmt, *cap_fmt; int ret = 0; ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); @@ -306,24 +637,31 @@ static int s5p_jpeg_open(struct file *file) ctx->jpeg = jpeg; if (vfd == jpeg->vfd_encoder) { ctx->mode = S5P_JPEG_ENCODE; - out_fmt = s5p_jpeg_find_format(ctx->mode, V4L2_PIX_FMT_RGB565); + out_fmt = s5p_jpeg_find_format(ctx, V4L2_PIX_FMT_RGB565, + FMT_TYPE_OUTPUT); + cap_fmt = s5p_jpeg_find_format(ctx, V4L2_PIX_FMT_JPEG, + FMT_TYPE_CAPTURE); } else { ctx->mode = S5P_JPEG_DECODE; - out_fmt = s5p_jpeg_find_format(ctx->mode, V4L2_PIX_FMT_JPEG); + out_fmt = s5p_jpeg_find_format(ctx, V4L2_PIX_FMT_JPEG, + FMT_TYPE_OUTPUT); + cap_fmt = s5p_jpeg_find_format(ctx, V4L2_PIX_FMT_YUYV, + FMT_TYPE_CAPTURE); } - ret = s5p_jpeg_controls_create(ctx); - if (ret < 0) - goto error; - - ctx->m2m_ctx = v4l2_m2m_ctx_init(jpeg->m2m_dev, ctx, queue_init); - if (IS_ERR(ctx->m2m_ctx)) { - ret = PTR_ERR(ctx->m2m_ctx); + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(jpeg->m2m_dev, ctx, queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); goto error; } ctx->out_q.fmt = out_fmt; - ctx->cap_q.fmt = s5p_jpeg_find_format(ctx->mode, V4L2_PIX_FMT_YUYV); + ctx->cap_q.fmt = cap_fmt; + + ret = s5p_jpeg_controls_create(ctx); + if (ret < 0) + goto error; + mutex_unlock(&jpeg->lock); return 0; @@ -342,49 +680,23 @@ static int s5p_jpeg_release(struct file *file) struct s5p_jpeg_ctx *ctx = fh_to_ctx(file->private_data); mutex_lock(&jpeg->lock); - v4l2_m2m_ctx_release(ctx->m2m_ctx); - mutex_unlock(&jpeg->lock); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); v4l2_ctrl_handler_free(&ctx->ctrl_handler); v4l2_fh_del(&ctx->fh); v4l2_fh_exit(&ctx->fh); kfree(ctx); - - return 0; -} - -static unsigned int s5p_jpeg_poll(struct file *file, - struct poll_table_struct *wait) -{ - struct s5p_jpeg *jpeg = video_drvdata(file); - struct s5p_jpeg_ctx *ctx = fh_to_ctx(file->private_data); - unsigned int res; - - mutex_lock(&jpeg->lock); - res = v4l2_m2m_poll(file, ctx->m2m_ctx, wait); mutex_unlock(&jpeg->lock); - return res; -} - -static int s5p_jpeg_mmap(struct file *file, struct vm_area_struct *vma) -{ - struct s5p_jpeg *jpeg = video_drvdata(file); - struct s5p_jpeg_ctx *ctx = fh_to_ctx(file->private_data); - int ret; - if (mutex_lock_interruptible(&jpeg->lock)) - return -ERESTARTSYS; - ret = v4l2_m2m_mmap(file, ctx->m2m_ctx, vma); - mutex_unlock(&jpeg->lock); - return ret; + return 0; } static const struct v4l2_file_operations s5p_jpeg_fops = { .owner = THIS_MODULE, .open = s5p_jpeg_open, .release = s5p_jpeg_release, - .poll = s5p_jpeg_poll, + .poll = v4l2_m2m_fop_poll, .unlocked_ioctl = video_ioctl2, - .mmap = s5p_jpeg_mmap, + .mmap = v4l2_m2m_fop_mmap, }; /* @@ -427,10 +739,11 @@ static void skip(struct s5p_jpeg_buffer *buf, long len) } static bool s5p_jpeg_parse_hdr(struct s5p_jpeg_q_data *result, - unsigned long buffer, unsigned long size) + unsigned long buffer, unsigned long size, + struct s5p_jpeg_ctx *ctx) { int c, components, notfound; - unsigned int height, width, word; + unsigned int height, width, word, subsampling = 0; long length; struct s5p_jpeg_buffer jpeg_buffer; @@ -469,7 +782,15 @@ static bool s5p_jpeg_parse_hdr(struct s5p_jpeg_q_data *result, break; notfound = 0; - skip(&jpeg_buffer, components * 3); + if (components == 1) { + subsampling = 0x33; + } else { + skip(&jpeg_buffer, 1); + subsampling = get_byte(&jpeg_buffer); + skip(&jpeg_buffer, 1); + } + + skip(&jpeg_buffer, components * 2); break; /* skip payload-less markers */ @@ -491,6 +812,24 @@ static bool s5p_jpeg_parse_hdr(struct s5p_jpeg_q_data *result, result->w = width; result->h = height; result->size = components; + + switch (subsampling) { + case 0x11: + ctx->subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444; + break; + case 0x21: + ctx->subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422; + break; + case 0x22: + ctx->subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420; + break; + case 0x33: + ctx->subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY; + break; + default: + return false; + } + return !notfound; } @@ -521,13 +860,13 @@ static int s5p_jpeg_querycap(struct file *file, void *priv, return 0; } -static int enum_fmt(struct s5p_jpeg_fmt *formats, int n, +static int enum_fmt(struct s5p_jpeg_fmt *sjpeg_formats, int n, struct v4l2_fmtdesc *f, u32 type) { int i, num = 0; for (i = 0; i < n; ++i) { - if (formats[i].types & type) { + if (sjpeg_formats[i].flags & type) { /* index-th format of type type found ? */ if (num == f->index) break; @@ -541,8 +880,8 @@ static int enum_fmt(struct s5p_jpeg_fmt *formats, int n, if (i >= n) return -EINVAL; - strlcpy(f->description, formats[i].name, sizeof(f->description)); - f->pixelformat = formats[i].fourcc; + strlcpy(f->description, sjpeg_formats[i].name, sizeof(f->description)); + f->pixelformat = sjpeg_formats[i].fourcc; return 0; } @@ -553,10 +892,11 @@ static int s5p_jpeg_enum_fmt_vid_cap(struct file *file, void *priv, struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); if (ctx->mode == S5P_JPEG_ENCODE) - return enum_fmt(formats_enc, NUM_FORMATS_ENC, f, - MEM2MEM_CAPTURE); + return enum_fmt(sjpeg_formats, SJPEG_NUM_FORMATS, f, + SJPEG_FMT_FLAG_ENC_CAPTURE); - return enum_fmt(formats_dec, NUM_FORMATS_DEC, f, MEM2MEM_CAPTURE); + return enum_fmt(sjpeg_formats, SJPEG_NUM_FORMATS, f, + SJPEG_FMT_FLAG_DEC_CAPTURE); } static int s5p_jpeg_enum_fmt_vid_out(struct file *file, void *priv, @@ -565,10 +905,11 @@ static int s5p_jpeg_enum_fmt_vid_out(struct file *file, void *priv, struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); if (ctx->mode == S5P_JPEG_ENCODE) - return enum_fmt(formats_enc, NUM_FORMATS_ENC, f, - MEM2MEM_OUTPUT); + return enum_fmt(sjpeg_formats, SJPEG_NUM_FORMATS, f, + SJPEG_FMT_FLAG_ENC_OUTPUT); - return enum_fmt(formats_dec, NUM_FORMATS_DEC, f, MEM2MEM_OUTPUT); + return enum_fmt(sjpeg_formats, SJPEG_NUM_FORMATS, f, + SJPEG_FMT_FLAG_DEC_OUTPUT); } static struct s5p_jpeg_q_data *get_q_data(struct s5p_jpeg_ctx *ctx, @@ -589,7 +930,7 @@ static int s5p_jpeg_g_fmt(struct file *file, void *priv, struct v4l2_format *f) struct v4l2_pix_format *pix = &f->fmt.pix; struct s5p_jpeg_ctx *ct = fh_to_ctx(priv); - vq = v4l2_m2m_get_vq(ct->m2m_ctx, f->type); + vq = v4l2_m2m_get_vq(ct->fh.m2m_ctx, f->type); if (!vq) return -EINVAL; @@ -615,29 +956,35 @@ static int s5p_jpeg_g_fmt(struct file *file, void *priv, struct v4l2_format *f) return 0; } -static struct s5p_jpeg_fmt *s5p_jpeg_find_format(unsigned int mode, - u32 pixelformat) +static struct s5p_jpeg_fmt *s5p_jpeg_find_format(struct s5p_jpeg_ctx *ctx, + u32 pixelformat, unsigned int fmt_type) { - unsigned int k; - struct s5p_jpeg_fmt *formats; - int n; + unsigned int k, fmt_flag, ver_flag; - if (mode == S5P_JPEG_ENCODE) { - formats = formats_enc; - n = NUM_FORMATS_ENC; - } else { - formats = formats_dec; - n = NUM_FORMATS_DEC; - } + if (ctx->mode == S5P_JPEG_ENCODE) + fmt_flag = (fmt_type == FMT_TYPE_OUTPUT) ? + SJPEG_FMT_FLAG_ENC_OUTPUT : + SJPEG_FMT_FLAG_ENC_CAPTURE; + else + fmt_flag = (fmt_type == FMT_TYPE_OUTPUT) ? + SJPEG_FMT_FLAG_DEC_OUTPUT : + SJPEG_FMT_FLAG_DEC_CAPTURE; + + if (ctx->jpeg->variant->version == SJPEG_S5P) + ver_flag = SJPEG_FMT_FLAG_S5P; + else + ver_flag = SJPEG_FMT_FLAG_EXYNOS4; - for (k = 0; k < n; k++) { - struct s5p_jpeg_fmt *fmt = &formats[k]; - if (fmt->fourcc == pixelformat) + for (k = 0; k < ARRAY_SIZE(sjpeg_formats); k++) { + struct s5p_jpeg_fmt *fmt = &sjpeg_formats[k]; + if (fmt->fourcc == pixelformat && + fmt->flags & fmt_flag && + fmt->flags & ver_flag) { return fmt; + } } return NULL; - } static void jpeg_bound_align_image(u32 *w, unsigned int wmin, unsigned int wmax, @@ -673,7 +1020,7 @@ static int vidioc_try_fmt(struct v4l2_format *f, struct s5p_jpeg_fmt *fmt, /* V4L2 specification suggests the driver corrects the format struct * if any of the dimensions is unsupported */ - if (q_type == MEM2MEM_OUTPUT) + if (q_type == FMT_TYPE_OUTPUT) jpeg_bound_align_image(&pix->width, S5P_JPEG_MIN_WIDTH, S5P_JPEG_MAX_WIDTH, 0, &pix->height, S5P_JPEG_MIN_HEIGHT, @@ -695,7 +1042,7 @@ static int vidioc_try_fmt(struct v4l2_format *f, struct s5p_jpeg_fmt *fmt, bpl = pix->width; /* planar */ if (fmt->colplanes == 1 && /* packed */ - (bpl << 3) * fmt->depth < pix->width) + (bpl << 3) / fmt->depth < pix->width) bpl = (pix->width * fmt->depth) >> 3; pix->bytesperline = bpl; @@ -709,17 +1056,41 @@ static int s5p_jpeg_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); + struct v4l2_pix_format *pix = &f->fmt.pix; struct s5p_jpeg_fmt *fmt; + int ret; - fmt = s5p_jpeg_find_format(ctx->mode, f->fmt.pix.pixelformat); - if (!fmt || !(fmt->types & MEM2MEM_CAPTURE)) { + fmt = s5p_jpeg_find_format(ctx, f->fmt.pix.pixelformat, + FMT_TYPE_CAPTURE); + if (!fmt) { v4l2_err(&ctx->jpeg->v4l2_dev, "Fourcc format (0x%08x) invalid.\n", f->fmt.pix.pixelformat); return -EINVAL; } - return vidioc_try_fmt(f, fmt, ctx, MEM2MEM_CAPTURE); + /* + * The exynos4x12 device requires resulting YUV image + * subsampling not to be lower than the input jpeg subsampling. + * If this requirement is not met then downgrade the requested + * capture format to the one with subsampling equal to the input jpeg. + */ + if ((ctx->jpeg->variant->version != SJPEG_S5P) && + (ctx->mode == S5P_JPEG_DECODE) && + (fmt->flags & SJPEG_FMT_NON_RGB) && + (fmt->subsampling < ctx->subsampling)) { + ret = s5p_jpeg_adjust_fourcc_to_subsampling(ctx->subsampling, + fmt->fourcc, + &pix->pixelformat, + ctx); + if (ret < 0) + pix->pixelformat = V4L2_PIX_FMT_GREY; + + fmt = s5p_jpeg_find_format(ctx, pix->pixelformat, + FMT_TYPE_CAPTURE); + } + + return vidioc_try_fmt(f, fmt, ctx, FMT_TYPE_CAPTURE); } static int s5p_jpeg_try_fmt_vid_out(struct file *file, void *priv, @@ -728,15 +1099,16 @@ static int s5p_jpeg_try_fmt_vid_out(struct file *file, void *priv, struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); struct s5p_jpeg_fmt *fmt; - fmt = s5p_jpeg_find_format(ctx->mode, f->fmt.pix.pixelformat); - if (!fmt || !(fmt->types & MEM2MEM_OUTPUT)) { + fmt = s5p_jpeg_find_format(ctx, f->fmt.pix.pixelformat, + FMT_TYPE_OUTPUT); + if (!fmt) { v4l2_err(&ctx->jpeg->v4l2_dev, "Fourcc format (0x%08x) invalid.\n", f->fmt.pix.pixelformat); return -EINVAL; } - return vidioc_try_fmt(f, fmt, ctx, MEM2MEM_OUTPUT); + return vidioc_try_fmt(f, fmt, ctx, FMT_TYPE_OUTPUT); } static int s5p_jpeg_s_fmt(struct s5p_jpeg_ctx *ct, struct v4l2_format *f) @@ -744,8 +1116,10 @@ static int s5p_jpeg_s_fmt(struct s5p_jpeg_ctx *ct, struct v4l2_format *f) struct vb2_queue *vq; struct s5p_jpeg_q_data *q_data = NULL; struct v4l2_pix_format *pix = &f->fmt.pix; + struct v4l2_ctrl *ctrl_subs; + unsigned int f_type; - vq = v4l2_m2m_get_vq(ct->m2m_ctx, f->type); + vq = v4l2_m2m_get_vq(ct->fh.m2m_ctx, f->type); if (!vq) return -EINVAL; @@ -757,7 +1131,10 @@ static int s5p_jpeg_s_fmt(struct s5p_jpeg_ctx *ct, struct v4l2_format *f) return -EBUSY; } - q_data->fmt = s5p_jpeg_find_format(ct->mode, pix->pixelformat); + f_type = V4L2_TYPE_IS_OUTPUT(f->type) ? + FMT_TYPE_OUTPUT : FMT_TYPE_CAPTURE; + + q_data->fmt = s5p_jpeg_find_format(ct, pix->pixelformat, f_type); q_data->w = pix->width; q_data->h = pix->height; if (q_data->fmt->fourcc != V4L2_PIX_FMT_JPEG) @@ -765,6 +1142,13 @@ static int s5p_jpeg_s_fmt(struct s5p_jpeg_ctx *ct, struct v4l2_format *f) else q_data->size = pix->sizeimage; + if (f_type == FMT_TYPE_OUTPUT) { + ctrl_subs = v4l2_ctrl_find(&ct->ctrl_handler, + V4L2_CID_JPEG_CHROMA_SUBSAMPLING); + if (ctrl_subs) + v4l2_ctrl_s_ctrl(ctrl_subs, q_data->fmt->subsampling); + } + return 0; } @@ -792,60 +1176,14 @@ static int s5p_jpeg_s_fmt_vid_out(struct file *file, void *priv, return s5p_jpeg_s_fmt(fh_to_ctx(priv), f); } -static int s5p_jpeg_reqbufs(struct file *file, void *priv, - struct v4l2_requestbuffers *reqbufs) -{ - struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); - - return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs); -} - -static int s5p_jpeg_querybuf(struct file *file, void *priv, - struct v4l2_buffer *buf) -{ - struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); - - return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf); -} - -static int s5p_jpeg_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) -{ - struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); - - return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); -} - -static int s5p_jpeg_dqbuf(struct file *file, void *priv, - struct v4l2_buffer *buf) -{ - struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); - - return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); -} - -static int s5p_jpeg_streamon(struct file *file, void *priv, - enum v4l2_buf_type type) -{ - struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); - - return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); -} - -static int s5p_jpeg_streamoff(struct file *file, void *priv, - enum v4l2_buf_type type) -{ - struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); - - return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); -} - static int s5p_jpeg_g_selection(struct file *file, void *priv, struct v4l2_selection *s) { struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && - s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + ctx->jpeg->variant->version != SJPEG_S5P) return -EINVAL; /* For JPEG blob active == default == bounds */ @@ -884,12 +1222,7 @@ static int s5p_jpeg_g_volatile_ctrl(struct v4l2_ctrl *ctrl) switch (ctrl->id) { case V4L2_CID_JPEG_CHROMA_SUBSAMPLING: spin_lock_irqsave(&jpeg->slock, flags); - - WARN_ON(ctx->subsampling > S5P_SUBSAMPLING_MODE_GRAY); - if (ctx->subsampling > 2) - ctrl->val = V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY; - else - ctrl->val = ctx->subsampling; + ctrl->val = s5p_jpeg_to_user_subsampling(ctx); spin_unlock_irqrestore(&jpeg->slock, flags); break; } @@ -897,6 +1230,40 @@ static int s5p_jpeg_g_volatile_ctrl(struct v4l2_ctrl *ctrl) return 0; } +static int s5p_jpeg_try_ctrl(struct v4l2_ctrl *ctrl) +{ + struct s5p_jpeg_ctx *ctx = ctrl_to_ctx(ctrl); + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&ctx->jpeg->slock, flags); + + if (ctrl->id == V4L2_CID_JPEG_CHROMA_SUBSAMPLING) { + if (ctx->jpeg->variant->version == SJPEG_S5P) + goto error_free; + /* + * The exynos4x12 device requires input raw image fourcc + * to be V4L2_PIX_FMT_GREY if gray jpeg format + * is to be set. + */ + if (ctx->out_q.fmt->fourcc != V4L2_PIX_FMT_GREY && + ctrl->val == V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY) { + ret = -EINVAL; + goto error_free; + } + /* + * The exynos4x12 device requires resulting jpeg subsampling + * not to be lower than the input raw image subsampling. + */ + if (ctx->out_q.fmt->subsampling > ctrl->val) + ctrl->val = ctx->out_q.fmt->subsampling; + } + +error_free: + spin_unlock_irqrestore(&ctx->jpeg->slock, flags); + return ret; +} + static int s5p_jpeg_s_ctrl(struct v4l2_ctrl *ctrl) { struct s5p_jpeg_ctx *ctx = ctrl_to_ctx(ctrl); @@ -906,7 +1273,7 @@ static int s5p_jpeg_s_ctrl(struct v4l2_ctrl *ctrl) switch (ctrl->id) { case V4L2_CID_JPEG_COMPRESSION_QUALITY: - ctx->compr_quality = S5P_JPEG_COMPR_QUAL_WORST - ctrl->val; + ctx->compr_quality = ctrl->val; break; case V4L2_CID_JPEG_RESTART_INTERVAL: ctx->restart_interval = ctrl->val; @@ -922,6 +1289,7 @@ static int s5p_jpeg_s_ctrl(struct v4l2_ctrl *ctrl) static const struct v4l2_ctrl_ops s5p_jpeg_ctrl_ops = { .g_volatile_ctrl = s5p_jpeg_g_volatile_ctrl, + .try_ctrl = s5p_jpeg_try_ctrl, .s_ctrl = s5p_jpeg_s_ctrl, }; @@ -929,18 +1297,20 @@ static int s5p_jpeg_controls_create(struct s5p_jpeg_ctx *ctx) { unsigned int mask = ~0x27; /* 444, 422, 420, GRAY */ struct v4l2_ctrl *ctrl; + int ret; v4l2_ctrl_handler_init(&ctx->ctrl_handler, 3); if (ctx->mode == S5P_JPEG_ENCODE) { v4l2_ctrl_new_std(&ctx->ctrl_handler, &s5p_jpeg_ctrl_ops, V4L2_CID_JPEG_COMPRESSION_QUALITY, - 0, 3, 1, 3); + 0, 3, 1, S5P_JPEG_COMPR_QUAL_WORST); v4l2_ctrl_new_std(&ctx->ctrl_handler, &s5p_jpeg_ctrl_ops, V4L2_CID_JPEG_RESTART_INTERVAL, 0, 3, 0xffff, 0); - mask = ~0x06; /* 422, 420 */ + if (ctx->jpeg->variant->version == SJPEG_S5P) + mask = ~0x06; /* 422, 420 */ } ctrl = v4l2_ctrl_new_std_menu(&ctx->ctrl_handler, &s5p_jpeg_ctrl_ops, @@ -948,13 +1318,24 @@ static int s5p_jpeg_controls_create(struct s5p_jpeg_ctx *ctx) V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY, mask, V4L2_JPEG_CHROMA_SUBSAMPLING_422); - if (ctx->ctrl_handler.error) - return ctx->ctrl_handler.error; + if (ctx->ctrl_handler.error) { + ret = ctx->ctrl_handler.error; + goto error_free; + } if (ctx->mode == S5P_JPEG_DECODE) ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY; - return 0; + + ret = v4l2_ctrl_handler_setup(&ctx->ctrl_handler); + if (ret < 0) + goto error_free; + + return ret; + +error_free: + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + return ret; } static const struct v4l2_ioctl_ops s5p_jpeg_ioctl_ops = { @@ -972,14 +1353,13 @@ static const struct v4l2_ioctl_ops s5p_jpeg_ioctl_ops = { .vidioc_s_fmt_vid_cap = s5p_jpeg_s_fmt_vid_cap, .vidioc_s_fmt_vid_out = s5p_jpeg_s_fmt_vid_out, - .vidioc_reqbufs = s5p_jpeg_reqbufs, - .vidioc_querybuf = s5p_jpeg_querybuf, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, - .vidioc_qbuf = s5p_jpeg_qbuf, - .vidioc_dqbuf = s5p_jpeg_dqbuf, - - .vidioc_streamon = s5p_jpeg_streamon, - .vidioc_streamoff = s5p_jpeg_streamoff, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, .vidioc_g_selection = s5p_jpeg_g_selection, }; @@ -995,74 +1375,181 @@ static void s5p_jpeg_device_run(void *priv) struct s5p_jpeg_ctx *ctx = priv; struct s5p_jpeg *jpeg = ctx->jpeg; struct vb2_buffer *src_buf, *dst_buf; - unsigned long src_addr, dst_addr; + unsigned long src_addr, dst_addr, flags; + + spin_lock_irqsave(&ctx->jpeg->slock, flags); - src_buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); - dst_buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); src_addr = vb2_dma_contig_plane_dma_addr(src_buf, 0); dst_addr = vb2_dma_contig_plane_dma_addr(dst_buf, 0); - jpeg_reset(jpeg->regs); - jpeg_poweron(jpeg->regs); - jpeg_proc_mode(jpeg->regs, ctx->mode); + s5p_jpeg_reset(jpeg->regs); + s5p_jpeg_poweron(jpeg->regs); + s5p_jpeg_proc_mode(jpeg->regs, ctx->mode); if (ctx->mode == S5P_JPEG_ENCODE) { if (ctx->out_q.fmt->fourcc == V4L2_PIX_FMT_RGB565) - jpeg_input_raw_mode(jpeg->regs, S5P_JPEG_RAW_IN_565); + s5p_jpeg_input_raw_mode(jpeg->regs, + S5P_JPEG_RAW_IN_565); else - jpeg_input_raw_mode(jpeg->regs, S5P_JPEG_RAW_IN_422); - jpeg_subsampling_mode(jpeg->regs, ctx->subsampling); - jpeg_dri(jpeg->regs, ctx->restart_interval); - jpeg_x(jpeg->regs, ctx->out_q.w); - jpeg_y(jpeg->regs, ctx->out_q.h); - jpeg_imgadr(jpeg->regs, src_addr); - jpeg_jpgadr(jpeg->regs, dst_addr); + s5p_jpeg_input_raw_mode(jpeg->regs, + S5P_JPEG_RAW_IN_422); + s5p_jpeg_subsampling_mode(jpeg->regs, ctx->subsampling); + s5p_jpeg_dri(jpeg->regs, ctx->restart_interval); + s5p_jpeg_x(jpeg->regs, ctx->out_q.w); + s5p_jpeg_y(jpeg->regs, ctx->out_q.h); + s5p_jpeg_imgadr(jpeg->regs, src_addr); + s5p_jpeg_jpgadr(jpeg->regs, dst_addr); /* ultimately comes from sizeimage from userspace */ - jpeg_enc_stream_int(jpeg->regs, ctx->cap_q.size); + s5p_jpeg_enc_stream_int(jpeg->regs, ctx->cap_q.size); /* JPEG RGB to YCbCr conversion matrix */ - jpeg_coef(jpeg->regs, 1, 1, S5P_JPEG_COEF11); - jpeg_coef(jpeg->regs, 1, 2, S5P_JPEG_COEF12); - jpeg_coef(jpeg->regs, 1, 3, S5P_JPEG_COEF13); - jpeg_coef(jpeg->regs, 2, 1, S5P_JPEG_COEF21); - jpeg_coef(jpeg->regs, 2, 2, S5P_JPEG_COEF22); - jpeg_coef(jpeg->regs, 2, 3, S5P_JPEG_COEF23); - jpeg_coef(jpeg->regs, 3, 1, S5P_JPEG_COEF31); - jpeg_coef(jpeg->regs, 3, 2, S5P_JPEG_COEF32); - jpeg_coef(jpeg->regs, 3, 3, S5P_JPEG_COEF33); + s5p_jpeg_coef(jpeg->regs, 1, 1, S5P_JPEG_COEF11); + s5p_jpeg_coef(jpeg->regs, 1, 2, S5P_JPEG_COEF12); + s5p_jpeg_coef(jpeg->regs, 1, 3, S5P_JPEG_COEF13); + s5p_jpeg_coef(jpeg->regs, 2, 1, S5P_JPEG_COEF21); + s5p_jpeg_coef(jpeg->regs, 2, 2, S5P_JPEG_COEF22); + s5p_jpeg_coef(jpeg->regs, 2, 3, S5P_JPEG_COEF23); + s5p_jpeg_coef(jpeg->regs, 3, 1, S5P_JPEG_COEF31); + s5p_jpeg_coef(jpeg->regs, 3, 2, S5P_JPEG_COEF32); + s5p_jpeg_coef(jpeg->regs, 3, 3, S5P_JPEG_COEF33); /* * JPEG IP allows storing 4 quantization tables * We fill table 0 for luma and table 1 for chroma */ - jpeg_set_qtbl_lum(jpeg->regs, ctx->compr_quality); - jpeg_set_qtbl_chr(jpeg->regs, ctx->compr_quality); + s5p_jpeg_set_qtbl_lum(jpeg->regs, ctx->compr_quality); + s5p_jpeg_set_qtbl_chr(jpeg->regs, ctx->compr_quality); /* use table 0 for Y */ - jpeg_qtbl(jpeg->regs, 1, 0); + s5p_jpeg_qtbl(jpeg->regs, 1, 0); /* use table 1 for Cb and Cr*/ - jpeg_qtbl(jpeg->regs, 2, 1); - jpeg_qtbl(jpeg->regs, 3, 1); + s5p_jpeg_qtbl(jpeg->regs, 2, 1); + s5p_jpeg_qtbl(jpeg->regs, 3, 1); /* Y, Cb, Cr use Huffman table 0 */ - jpeg_htbl_ac(jpeg->regs, 1); - jpeg_htbl_dc(jpeg->regs, 1); - jpeg_htbl_ac(jpeg->regs, 2); - jpeg_htbl_dc(jpeg->regs, 2); - jpeg_htbl_ac(jpeg->regs, 3); - jpeg_htbl_dc(jpeg->regs, 3); + s5p_jpeg_htbl_ac(jpeg->regs, 1); + s5p_jpeg_htbl_dc(jpeg->regs, 1); + s5p_jpeg_htbl_ac(jpeg->regs, 2); + s5p_jpeg_htbl_dc(jpeg->regs, 2); + s5p_jpeg_htbl_ac(jpeg->regs, 3); + s5p_jpeg_htbl_dc(jpeg->regs, 3); } else { /* S5P_JPEG_DECODE */ - jpeg_rst_int_enable(jpeg->regs, true); - jpeg_data_num_int_enable(jpeg->regs, true); - jpeg_final_mcu_num_int_enable(jpeg->regs, true); + s5p_jpeg_rst_int_enable(jpeg->regs, true); + s5p_jpeg_data_num_int_enable(jpeg->regs, true); + s5p_jpeg_final_mcu_num_int_enable(jpeg->regs, true); if (ctx->cap_q.fmt->fourcc == V4L2_PIX_FMT_YUYV) - jpeg_outform_raw(jpeg->regs, S5P_JPEG_RAW_OUT_422); + s5p_jpeg_outform_raw(jpeg->regs, S5P_JPEG_RAW_OUT_422); else - jpeg_outform_raw(jpeg->regs, S5P_JPEG_RAW_OUT_420); - jpeg_jpgadr(jpeg->regs, src_addr); - jpeg_imgadr(jpeg->regs, dst_addr); + s5p_jpeg_outform_raw(jpeg->regs, S5P_JPEG_RAW_OUT_420); + s5p_jpeg_jpgadr(jpeg->regs, src_addr); + s5p_jpeg_imgadr(jpeg->regs, dst_addr); } - jpeg_start(jpeg->regs); + s5p_jpeg_start(jpeg->regs); + + spin_unlock_irqrestore(&ctx->jpeg->slock, flags); +} + +static void exynos4_jpeg_set_img_addr(struct s5p_jpeg_ctx *ctx) +{ + struct s5p_jpeg *jpeg = ctx->jpeg; + struct s5p_jpeg_fmt *fmt; + struct vb2_buffer *vb; + struct s5p_jpeg_addr jpeg_addr; + u32 pix_size, padding_bytes = 0; + + pix_size = ctx->cap_q.w * ctx->cap_q.h; + + if (ctx->mode == S5P_JPEG_ENCODE) { + vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + fmt = ctx->out_q.fmt; + if (ctx->out_q.w % 2 && fmt->h_align > 0) + padding_bytes = ctx->out_q.h; + } else { + fmt = ctx->cap_q.fmt; + vb = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + } + + jpeg_addr.y = vb2_dma_contig_plane_dma_addr(vb, 0); + + if (fmt->colplanes == 2) { + jpeg_addr.cb = jpeg_addr.y + pix_size - padding_bytes; + } else if (fmt->colplanes == 3) { + jpeg_addr.cb = jpeg_addr.y + pix_size; + if (fmt->fourcc == V4L2_PIX_FMT_YUV420) + jpeg_addr.cr = jpeg_addr.cb + pix_size / 4; + else + jpeg_addr.cr = jpeg_addr.cb + pix_size / 2; + } + + exynos4_jpeg_set_frame_buf_address(jpeg->regs, &jpeg_addr); +} + +static void exynos4_jpeg_set_jpeg_addr(struct s5p_jpeg_ctx *ctx) +{ + struct s5p_jpeg *jpeg = ctx->jpeg; + struct vb2_buffer *vb; + unsigned int jpeg_addr = 0; + + if (ctx->mode == S5P_JPEG_ENCODE) + vb = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + else + vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + + jpeg_addr = vb2_dma_contig_plane_dma_addr(vb, 0); + exynos4_jpeg_set_stream_buf_address(jpeg->regs, jpeg_addr); +} + +static void exynos4_jpeg_device_run(void *priv) +{ + struct s5p_jpeg_ctx *ctx = priv; + struct s5p_jpeg *jpeg = ctx->jpeg; + unsigned int bitstream_size; + unsigned long flags; + + spin_lock_irqsave(&ctx->jpeg->slock, flags); + + if (ctx->mode == S5P_JPEG_ENCODE) { + exynos4_jpeg_sw_reset(jpeg->regs); + exynos4_jpeg_set_interrupt(jpeg->regs); + exynos4_jpeg_set_huf_table_enable(jpeg->regs, 1); + + exynos4_jpeg_set_huff_tbl(jpeg->regs); + + /* + * JPEG IP allows storing 4 quantization tables + * We fill table 0 for luma and table 1 for chroma + */ + exynos4_jpeg_set_qtbl_lum(jpeg->regs, ctx->compr_quality); + exynos4_jpeg_set_qtbl_chr(jpeg->regs, ctx->compr_quality); + + exynos4_jpeg_set_encode_tbl_select(jpeg->regs, + ctx->compr_quality); + exynos4_jpeg_set_stream_size(jpeg->regs, ctx->cap_q.w, + ctx->cap_q.h); + + exynos4_jpeg_set_enc_out_fmt(jpeg->regs, ctx->subsampling); + exynos4_jpeg_set_img_fmt(jpeg->regs, ctx->out_q.fmt->fourcc); + exynos4_jpeg_set_img_addr(ctx); + exynos4_jpeg_set_jpeg_addr(ctx); + exynos4_jpeg_set_encode_hoff_cnt(jpeg->regs, + ctx->out_q.fmt->fourcc); + } else { + exynos4_jpeg_sw_reset(jpeg->regs); + exynos4_jpeg_set_interrupt(jpeg->regs); + exynos4_jpeg_set_img_addr(ctx); + exynos4_jpeg_set_jpeg_addr(ctx); + exynos4_jpeg_set_img_fmt(jpeg->regs, ctx->cap_q.fmt->fourcc); + + bitstream_size = DIV_ROUND_UP(ctx->out_q.size, 32); + + exynos4_jpeg_set_dec_bitstream_size(jpeg->regs, bitstream_size); + } + + exynos4_jpeg_set_enc_dec_mode(jpeg->regs, ctx->mode); + + spin_unlock_irqrestore(&ctx->jpeg->slock, flags); } static int s5p_jpeg_job_ready(void *priv) @@ -1082,6 +1569,12 @@ static struct v4l2_m2m_ops s5p_jpeg_m2m_ops = { .device_run = s5p_jpeg_device_run, .job_ready = s5p_jpeg_job_ready, .job_abort = s5p_jpeg_job_abort, +} +; +static struct v4l2_m2m_ops exynos_jpeg_m2m_ops = { + .device_run = exynos4_jpeg_device_run, + .job_ready = s5p_jpeg_job_ready, + .job_abort = s5p_jpeg_job_abort, }; /* @@ -1149,7 +1642,7 @@ static void s5p_jpeg_buf_queue(struct vb2_buffer *vb) ctx->hdr_parsed = s5p_jpeg_parse_hdr(&tmp, (unsigned long)vb2_plane_vaddr(vb, 0), min((unsigned long)ctx->out_q.size, - vb2_get_plane_payload(vb, 0))); + vb2_get_plane_payload(vb, 0)), ctx); if (!ctx->hdr_parsed) { vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); return; @@ -1162,30 +1655,9 @@ static void s5p_jpeg_buf_queue(struct vb2_buffer *vb) q_data = &ctx->cap_q; q_data->w = tmp.w; q_data->h = tmp.h; - - jpeg_bound_align_image(&q_data->w, S5P_JPEG_MIN_WIDTH, - S5P_JPEG_MAX_WIDTH, q_data->fmt->h_align, - &q_data->h, S5P_JPEG_MIN_HEIGHT, - S5P_JPEG_MAX_HEIGHT, q_data->fmt->v_align - ); - q_data->size = q_data->w * q_data->h * q_data->fmt->depth >> 3; } - if (ctx->m2m_ctx) - v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); -} - -static void s5p_jpeg_wait_prepare(struct vb2_queue *vq) -{ - struct s5p_jpeg_ctx *ctx = vb2_get_drv_priv(vq); - mutex_unlock(&ctx->jpeg->lock); -} - -static void s5p_jpeg_wait_finish(struct vb2_queue *vq) -{ - struct s5p_jpeg_ctx *ctx = vb2_get_drv_priv(vq); - - mutex_lock(&ctx->jpeg->lock); + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vb); } static int s5p_jpeg_start_streaming(struct vb2_queue *q, unsigned int count) @@ -1211,8 +1683,8 @@ static struct vb2_ops s5p_jpeg_qops = { .queue_setup = s5p_jpeg_queue_setup, .buf_prepare = s5p_jpeg_buf_prepare, .buf_queue = s5p_jpeg_buf_queue, - .wait_prepare = s5p_jpeg_wait_prepare, - .wait_finish = s5p_jpeg_wait_finish, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, .start_streaming = s5p_jpeg_start_streaming, .stop_streaming = s5p_jpeg_stop_streaming, }; @@ -1230,6 +1702,7 @@ static int queue_init(void *priv, struct vb2_queue *src_vq, src_vq->ops = &s5p_jpeg_qops; src_vq->mem_ops = &vb2_dma_contig_memops; src_vq->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->jpeg->lock; ret = vb2_queue_init(src_vq); if (ret) @@ -1242,6 +1715,7 @@ static int queue_init(void *priv, struct vb2_queue *src_vq, dst_vq->ops = &s5p_jpeg_qops; dst_vq->mem_ops = &vb2_dma_contig_memops; dst_vq->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->jpeg->lock; return vb2_queue_init(dst_vq); } @@ -1267,26 +1741,27 @@ static irqreturn_t s5p_jpeg_irq(int irq, void *dev_id) curr_ctx = v4l2_m2m_get_curr_priv(jpeg->m2m_dev); - src_buf = v4l2_m2m_src_buf_remove(curr_ctx->m2m_ctx); - dst_buf = v4l2_m2m_dst_buf_remove(curr_ctx->m2m_ctx); + src_buf = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx); if (curr_ctx->mode == S5P_JPEG_ENCODE) - enc_jpeg_too_large = jpeg_enc_stream_stat(jpeg->regs); - timer_elapsed = jpeg_timer_stat(jpeg->regs); - op_completed = jpeg_result_stat_ok(jpeg->regs); + enc_jpeg_too_large = s5p_jpeg_enc_stream_stat(jpeg->regs); + timer_elapsed = s5p_jpeg_timer_stat(jpeg->regs); + op_completed = s5p_jpeg_result_stat_ok(jpeg->regs); if (curr_ctx->mode == S5P_JPEG_DECODE) - op_completed = op_completed && jpeg_stream_stat_ok(jpeg->regs); + op_completed = op_completed && + s5p_jpeg_stream_stat_ok(jpeg->regs); if (enc_jpeg_too_large) { state = VB2_BUF_STATE_ERROR; - jpeg_clear_enc_stream_stat(jpeg->regs); + s5p_jpeg_clear_enc_stream_stat(jpeg->regs); } else if (timer_elapsed) { state = VB2_BUF_STATE_ERROR; - jpeg_clear_timer_stat(jpeg->regs); + s5p_jpeg_clear_timer_stat(jpeg->regs); } else if (!op_completed) { state = VB2_BUF_STATE_ERROR; } else { - payload_size = jpeg_compressed_size(jpeg->regs); + payload_size = s5p_jpeg_compressed_size(jpeg->regs); } dst_buf->v4l2_buf.timecode = src_buf->v4l2_buf.timecode; @@ -1296,16 +1771,79 @@ static irqreturn_t s5p_jpeg_irq(int irq, void *dev_id) if (curr_ctx->mode == S5P_JPEG_ENCODE) vb2_set_plane_payload(dst_buf, 0, payload_size); v4l2_m2m_buf_done(dst_buf, state); - v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->m2m_ctx); + v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx); - curr_ctx->subsampling = jpeg_get_subsampling_mode(jpeg->regs); + curr_ctx->subsampling = s5p_jpeg_get_subsampling_mode(jpeg->regs); spin_unlock(&jpeg->slock); - jpeg_clear_int(jpeg->regs); + s5p_jpeg_clear_int(jpeg->regs); return IRQ_HANDLED; } +static irqreturn_t exynos4_jpeg_irq(int irq, void *priv) +{ + unsigned int int_status; + struct vb2_buffer *src_vb, *dst_vb; + struct s5p_jpeg *jpeg = priv; + struct s5p_jpeg_ctx *curr_ctx; + unsigned long payload_size = 0; + + spin_lock(&jpeg->slock); + + curr_ctx = v4l2_m2m_get_curr_priv(jpeg->m2m_dev); + + src_vb = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx); + + int_status = exynos4_jpeg_get_int_status(jpeg->regs); + + if (int_status) { + switch (int_status & 0x1f) { + case 0x1: + jpeg->irq_ret = ERR_PROT; + break; + case 0x2: + jpeg->irq_ret = OK_ENC_OR_DEC; + break; + case 0x4: + jpeg->irq_ret = ERR_DEC_INVALID_FORMAT; + break; + case 0x8: + jpeg->irq_ret = ERR_MULTI_SCAN; + break; + case 0x10: + jpeg->irq_ret = ERR_FRAME; + break; + default: + jpeg->irq_ret = ERR_UNKNOWN; + break; + } + } else { + jpeg->irq_ret = ERR_UNKNOWN; + } + + if (jpeg->irq_ret == OK_ENC_OR_DEC) { + if (curr_ctx->mode == S5P_JPEG_ENCODE) { + payload_size = exynos4_jpeg_get_stream_size(jpeg->regs); + vb2_set_plane_payload(dst_vb, 0, payload_size); + } + v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_DONE); + } else { + v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_ERROR); + v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_ERROR); + } + + v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx); + curr_ctx->subsampling = exynos4_jpeg_get_frame_fmt(jpeg->regs); + + spin_unlock(&jpeg->slock); + return IRQ_HANDLED; +} + +static void *jpeg_get_drv_data(struct platform_device *pdev); + /* * ============================================================================ * Driver basic infrastructure @@ -1316,13 +1854,19 @@ static int s5p_jpeg_probe(struct platform_device *pdev) { struct s5p_jpeg *jpeg; struct resource *res; + struct v4l2_m2m_ops *samsung_jpeg_m2m_ops; int ret; + if (!pdev->dev.of_node) + return -ENODEV; + /* JPEG IP abstraction struct */ jpeg = devm_kzalloc(&pdev->dev, sizeof(struct s5p_jpeg), GFP_KERNEL); if (!jpeg) return -ENOMEM; + jpeg->variant = jpeg_get_drv_data(pdev); + mutex_init(&jpeg->lock); spin_lock_init(&jpeg->slock); jpeg->dev = &pdev->dev; @@ -1341,8 +1885,8 @@ static int s5p_jpeg_probe(struct platform_device *pdev) return ret; } - ret = devm_request_irq(&pdev->dev, jpeg->irq, s5p_jpeg_irq, 0, - dev_name(&pdev->dev), jpeg); + ret = devm_request_irq(&pdev->dev, jpeg->irq, jpeg->variant->jpeg_irq, + 0, dev_name(&pdev->dev), jpeg); if (ret) { dev_err(&pdev->dev, "cannot claim IRQ %d\n", jpeg->irq); return ret; @@ -1356,7 +1900,6 @@ static int s5p_jpeg_probe(struct platform_device *pdev) return ret; } dev_dbg(&pdev->dev, "clock source %p\n", jpeg->clk); - clk_prepare_enable(jpeg->clk); /* v4l2 device */ ret = v4l2_device_register(&pdev->dev, &jpeg->v4l2_dev); @@ -1365,8 +1908,13 @@ static int s5p_jpeg_probe(struct platform_device *pdev) goto clk_get_rollback; } + if (jpeg->variant->version == SJPEG_S5P) + samsung_jpeg_m2m_ops = &s5p_jpeg_m2m_ops; + else + samsung_jpeg_m2m_ops = &exynos_jpeg_m2m_ops; + /* mem2mem device */ - jpeg->m2m_dev = v4l2_m2m_init(&s5p_jpeg_m2m_ops); + jpeg->m2m_dev = v4l2_m2m_init(samsung_jpeg_m2m_ops); if (IS_ERR(jpeg->m2m_dev)) { v4l2_err(&jpeg->v4l2_dev, "Failed to init mem2mem device\n"); ret = PTR_ERR(jpeg->m2m_dev); @@ -1387,8 +1935,8 @@ static int s5p_jpeg_probe(struct platform_device *pdev) ret = -ENOMEM; goto vb2_allocator_rollback; } - strlcpy(jpeg->vfd_encoder->name, S5P_JPEG_M2M_NAME, - sizeof(jpeg->vfd_encoder->name)); + snprintf(jpeg->vfd_encoder->name, sizeof(jpeg->vfd_encoder->name), + "%s-enc", S5P_JPEG_M2M_NAME); jpeg->vfd_encoder->fops = &s5p_jpeg_fops; jpeg->vfd_encoder->ioctl_ops = &s5p_jpeg_ioctl_ops; jpeg->vfd_encoder->minor = -1; @@ -1415,8 +1963,8 @@ static int s5p_jpeg_probe(struct platform_device *pdev) ret = -ENOMEM; goto enc_vdev_register_rollback; } - strlcpy(jpeg->vfd_decoder->name, S5P_JPEG_M2M_NAME, - sizeof(jpeg->vfd_decoder->name)); + snprintf(jpeg->vfd_decoder->name, sizeof(jpeg->vfd_decoder->name), + "%s-dec", S5P_JPEG_M2M_NAME); jpeg->vfd_decoder->fops = &s5p_jpeg_fops; jpeg->vfd_decoder->ioctl_ops = &s5p_jpeg_ioctl_ops; jpeg->vfd_decoder->minor = -1; @@ -1464,7 +2012,6 @@ device_register_rollback: v4l2_device_unregister(&jpeg->v4l2_dev); clk_get_rollback: - clk_disable_unprepare(jpeg->clk); clk_put(jpeg->clk); return ret; @@ -1484,7 +2031,9 @@ static int s5p_jpeg_remove(struct platform_device *pdev) v4l2_m2m_release(jpeg->m2m_dev); v4l2_device_unregister(&jpeg->v4l2_dev); - clk_disable_unprepare(jpeg->clk); + if (!pm_runtime_status_suspended(&pdev->dev)) + clk_disable_unprepare(jpeg->clk); + clk_put(jpeg->clk); return 0; @@ -1492,41 +2041,119 @@ static int s5p_jpeg_remove(struct platform_device *pdev) static int s5p_jpeg_runtime_suspend(struct device *dev) { + struct s5p_jpeg *jpeg = dev_get_drvdata(dev); + + clk_disable_unprepare(jpeg->clk); + return 0; } static int s5p_jpeg_runtime_resume(struct device *dev) { struct s5p_jpeg *jpeg = dev_get_drvdata(dev); + unsigned long flags; + int ret; + + ret = clk_prepare_enable(jpeg->clk); + if (ret < 0) + return ret; + + spin_lock_irqsave(&jpeg->slock, flags); + /* * JPEG IP allows storing two Huffman tables for each component - * We fill table 0 for each component + * We fill table 0 for each component and do this here only + * for S5PC210 device as Exynos4x12 requires programming its + * Huffman tables each time the encoding process is initialized. */ - jpeg_set_hdctbl(jpeg->regs); - jpeg_set_hdctblg(jpeg->regs); - jpeg_set_hactbl(jpeg->regs); - jpeg_set_hactblg(jpeg->regs); + if (jpeg->variant->version == SJPEG_S5P) { + s5p_jpeg_set_hdctbl(jpeg->regs); + s5p_jpeg_set_hdctblg(jpeg->regs); + s5p_jpeg_set_hactbl(jpeg->regs); + s5p_jpeg_set_hactblg(jpeg->regs); + } + + spin_unlock_irqrestore(&jpeg->slock, flags); + return 0; } +static int s5p_jpeg_suspend(struct device *dev) +{ + if (pm_runtime_suspended(dev)) + return 0; + + return s5p_jpeg_runtime_suspend(dev); +} + +static int s5p_jpeg_resume(struct device *dev) +{ + if (pm_runtime_suspended(dev)) + return 0; + + return s5p_jpeg_runtime_resume(dev); +} + static const struct dev_pm_ops s5p_jpeg_pm_ops = { - .runtime_suspend = s5p_jpeg_runtime_suspend, - .runtime_resume = s5p_jpeg_runtime_resume, + SET_SYSTEM_SLEEP_PM_OPS(s5p_jpeg_suspend, s5p_jpeg_resume) + SET_RUNTIME_PM_OPS(s5p_jpeg_runtime_suspend, s5p_jpeg_runtime_resume, NULL) +}; + +#ifdef CONFIG_OF +static struct s5p_jpeg_variant s5p_jpeg_drvdata = { + .version = SJPEG_S5P, + .jpeg_irq = s5p_jpeg_irq, +}; + +static struct s5p_jpeg_variant exynos4_jpeg_drvdata = { + .version = SJPEG_EXYNOS4, + .jpeg_irq = exynos4_jpeg_irq, +}; + +static const struct of_device_id samsung_jpeg_match[] = { + { + .compatible = "samsung,s5pv210-jpeg", + .data = &s5p_jpeg_drvdata, + }, { + .compatible = "samsung,exynos4210-jpeg", + .data = &s5p_jpeg_drvdata, + }, { + .compatible = "samsung,exynos4212-jpeg", + .data = &exynos4_jpeg_drvdata, + }, + {}, }; +MODULE_DEVICE_TABLE(of, samsung_jpeg_match); + +static void *jpeg_get_drv_data(struct platform_device *pdev) +{ + struct s5p_jpeg_variant *driver_data = NULL; + const struct of_device_id *match; + + match = of_match_node(of_match_ptr(samsung_jpeg_match), + pdev->dev.of_node); + if (match) + driver_data = (struct s5p_jpeg_variant *)match->data; + + return driver_data; +} +#endif + static struct platform_driver s5p_jpeg_driver = { .probe = s5p_jpeg_probe, .remove = s5p_jpeg_remove, .driver = { - .owner = THIS_MODULE, - .name = S5P_JPEG_M2M_NAME, - .pm = &s5p_jpeg_pm_ops, + .of_match_table = of_match_ptr(samsung_jpeg_match), + .owner = THIS_MODULE, + .name = S5P_JPEG_M2M_NAME, + .pm = &s5p_jpeg_pm_ops, }, }; module_platform_driver(s5p_jpeg_driver); MODULE_AUTHOR("Andrzej Pietrasiewicz <andrzej.p@samsung.com>"); +MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>"); MODULE_DESCRIPTION("Samsung JPEG codec driver"); MODULE_LICENSE("GPL"); - diff --git a/drivers/media/platform/s5p-jpeg/jpeg-core.h b/drivers/media/platform/s5p-jpeg/jpeg-core.h index 8a4013e3aee7..f482dbf55d5f 100644 --- a/drivers/media/platform/s5p-jpeg/jpeg-core.h +++ b/drivers/media/platform/s5p-jpeg/jpeg-core.h @@ -13,6 +13,7 @@ #ifndef JPEG_CORE_H_ #define JPEG_CORE_H_ +#include <linux/interrupt.h> #include <media/v4l2-device.h> #include <media/v4l2-fh.h> #include <media/v4l2-ctrls.h> @@ -43,8 +44,45 @@ #define DHP 0xde /* Flags that indicate a format can be used for capture/output */ -#define MEM2MEM_CAPTURE (1 << 0) -#define MEM2MEM_OUTPUT (1 << 1) +#define SJPEG_FMT_FLAG_ENC_CAPTURE (1 << 0) +#define SJPEG_FMT_FLAG_ENC_OUTPUT (1 << 1) +#define SJPEG_FMT_FLAG_DEC_CAPTURE (1 << 2) +#define SJPEG_FMT_FLAG_DEC_OUTPUT (1 << 3) +#define SJPEG_FMT_FLAG_S5P (1 << 4) +#define SJPEG_FMT_FLAG_EXYNOS4 (1 << 5) +#define SJPEG_FMT_RGB (1 << 6) +#define SJPEG_FMT_NON_RGB (1 << 7) + +#define S5P_JPEG_ENCODE 0 +#define S5P_JPEG_DECODE 1 + +#define FMT_TYPE_OUTPUT 0 +#define FMT_TYPE_CAPTURE 1 + +#define SJPEG_SUBSAMPLING_444 0x11 +#define SJPEG_SUBSAMPLING_422 0x21 +#define SJPEG_SUBSAMPLING_420 0x22 + +/* Version numbers */ + +#define SJPEG_S5P 1 +#define SJPEG_EXYNOS4 2 + +enum exynos4_jpeg_result { + OK_ENC_OR_DEC, + ERR_PROT, + ERR_DEC_INVALID_FORMAT, + ERR_MULTI_SCAN, + ERR_FRAME, + ERR_UNKNOWN, +}; + +enum exynos4_jpeg_img_quality_level { + QUALITY_LEVEL_1 = 0, /* high */ + QUALITY_LEVEL_2, + QUALITY_LEVEL_3, + QUALITY_LEVEL_4, /* low */ +}; /** * struct s5p_jpeg - JPEG IP abstraction @@ -71,9 +109,16 @@ struct s5p_jpeg { void __iomem *regs; unsigned int irq; + enum exynos4_jpeg_result irq_ret; struct clk *clk; struct device *dev; void *alloc_ctx; + struct s5p_jpeg_variant *variant; +}; + +struct s5p_jpeg_variant { + unsigned int version; + irqreturn_t (*jpeg_irq)(int irq, void *priv); }; /** @@ -84,16 +129,18 @@ struct s5p_jpeg { * @colplanes: number of color planes (1 for packed formats) * @h_align: horizontal alignment order (align to 2^h_align) * @v_align: vertical alignment order (align to 2^v_align) - * @types: types of queue this format is applicable to + * @flags: flags describing format applicability */ struct s5p_jpeg_fmt { char *name; u32 fourcc; int depth; int colplanes; + int memplanes; int h_align; int v_align; - u32 types; + int subsampling; + u32 flags; }; /** @@ -115,7 +162,6 @@ struct s5p_jpeg_q_data { * @jpeg: JPEG IP device for this context * @mode: compression (encode) operation or decompression (decode) * @compr_quality: destination image quality in compression (encode) mode - * @m2m_ctx: mem2mem device context * @out_q: source (output) queue information * @cap_fmt: destination (capture) queue queue information * @hdr_parsed: set if header has been parsed during decompression @@ -127,7 +173,6 @@ struct s5p_jpeg_ctx { unsigned short compr_quality; unsigned short restart_interval; unsigned short subsampling; - struct v4l2_m2m_ctx *m2m_ctx; struct s5p_jpeg_q_data out_q; struct s5p_jpeg_q_data cap_q; struct v4l2_fh fh; @@ -147,4 +192,16 @@ struct s5p_jpeg_buffer { unsigned long data; }; +/** + * struct s5p_jpeg_addr - JPEG converter physical address set for DMA + * @y: luminance plane physical address + * @cb: Cb plane physical address + * @cr: Cr plane physical address + */ +struct s5p_jpeg_addr { + u32 y; + u32 cb; + u32 cr; +}; + #endif /* JPEG_CORE_H */ diff --git a/drivers/media/platform/s5p-jpeg/jpeg-hw-exynos4.c b/drivers/media/platform/s5p-jpeg/jpeg-hw-exynos4.c new file mode 100644 index 000000000000..da8d6a1a984f --- /dev/null +++ b/drivers/media/platform/s5p-jpeg/jpeg-hw-exynos4.c @@ -0,0 +1,279 @@ +/* Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * Author: Jacek Anaszewski <j.anaszewski@samsung.com> + * + * Register interface file for JPEG driver on Exynos4x12. + * + * 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. + */ +#include <linux/io.h> +#include <linux/delay.h> + +#include "jpeg-core.h" +#include "jpeg-hw-exynos4.h" +#include "jpeg-regs.h" + +void exynos4_jpeg_sw_reset(void __iomem *base) +{ + unsigned int reg; + + reg = readl(base + EXYNOS4_JPEG_CNTL_REG); + writel(reg & ~EXYNOS4_SOFT_RESET_HI, base + EXYNOS4_JPEG_CNTL_REG); + + ndelay(100000); + + writel(reg | EXYNOS4_SOFT_RESET_HI, base + EXYNOS4_JPEG_CNTL_REG); +} + +void exynos4_jpeg_set_enc_dec_mode(void __iomem *base, unsigned int mode) +{ + unsigned int reg; + + reg = readl(base + EXYNOS4_JPEG_CNTL_REG); + /* set exynos4_jpeg mod register */ + if (mode == S5P_JPEG_DECODE) { + writel((reg & EXYNOS4_ENC_DEC_MODE_MASK) | + EXYNOS4_DEC_MODE, + base + EXYNOS4_JPEG_CNTL_REG); + } else {/* encode */ + writel((reg & EXYNOS4_ENC_DEC_MODE_MASK) | + EXYNOS4_ENC_MODE, + base + EXYNOS4_JPEG_CNTL_REG); + } +} + +void exynos4_jpeg_set_img_fmt(void __iomem *base, unsigned int img_fmt) +{ + unsigned int reg; + + reg = readl(base + EXYNOS4_IMG_FMT_REG) & + EXYNOS4_ENC_IN_FMT_MASK; /* clear except enc format */ + + switch (img_fmt) { + case V4L2_PIX_FMT_GREY: + reg = reg | EXYNOS4_ENC_GRAY_IMG | EXYNOS4_GRAY_IMG_IP; + break; + case V4L2_PIX_FMT_RGB32: + reg = reg | EXYNOS4_ENC_RGB_IMG | + EXYNOS4_RGB_IP_RGB_32BIT_IMG; + break; + case V4L2_PIX_FMT_RGB565: + reg = reg | EXYNOS4_ENC_RGB_IMG | + EXYNOS4_RGB_IP_RGB_16BIT_IMG; + break; + case V4L2_PIX_FMT_NV24: + reg = reg | EXYNOS4_ENC_YUV_444_IMG | + EXYNOS4_YUV_444_IP_YUV_444_2P_IMG | + EXYNOS4_SWAP_CHROMA_CBCR; + break; + case V4L2_PIX_FMT_NV42: + reg = reg | EXYNOS4_ENC_YUV_444_IMG | + EXYNOS4_YUV_444_IP_YUV_444_2P_IMG | + EXYNOS4_SWAP_CHROMA_CRCB; + break; + case V4L2_PIX_FMT_YUYV: + reg = reg | EXYNOS4_DEC_YUV_422_IMG | + EXYNOS4_YUV_422_IP_YUV_422_1P_IMG | + EXYNOS4_SWAP_CHROMA_CBCR; + break; + + case V4L2_PIX_FMT_YVYU: + reg = reg | EXYNOS4_DEC_YUV_422_IMG | + EXYNOS4_YUV_422_IP_YUV_422_1P_IMG | + EXYNOS4_SWAP_CHROMA_CRCB; + break; + case V4L2_PIX_FMT_NV16: + reg = reg | EXYNOS4_DEC_YUV_422_IMG | + EXYNOS4_YUV_422_IP_YUV_422_2P_IMG | + EXYNOS4_SWAP_CHROMA_CBCR; + break; + case V4L2_PIX_FMT_NV61: + reg = reg | EXYNOS4_DEC_YUV_422_IMG | + EXYNOS4_YUV_422_IP_YUV_422_2P_IMG | + EXYNOS4_SWAP_CHROMA_CRCB; + break; + case V4L2_PIX_FMT_NV12: + reg = reg | EXYNOS4_DEC_YUV_420_IMG | + EXYNOS4_YUV_420_IP_YUV_420_2P_IMG | + EXYNOS4_SWAP_CHROMA_CBCR; + break; + case V4L2_PIX_FMT_NV21: + reg = reg | EXYNOS4_DEC_YUV_420_IMG | + EXYNOS4_YUV_420_IP_YUV_420_2P_IMG | + EXYNOS4_SWAP_CHROMA_CRCB; + break; + case V4L2_PIX_FMT_YUV420: + reg = reg | EXYNOS4_DEC_YUV_420_IMG | + EXYNOS4_YUV_420_IP_YUV_420_3P_IMG | + EXYNOS4_SWAP_CHROMA_CBCR; + break; + default: + break; + + } + + writel(reg, base + EXYNOS4_IMG_FMT_REG); +} + +void exynos4_jpeg_set_enc_out_fmt(void __iomem *base, unsigned int out_fmt) +{ + unsigned int reg; + + reg = readl(base + EXYNOS4_IMG_FMT_REG) & + ~EXYNOS4_ENC_FMT_MASK; /* clear enc format */ + + switch (out_fmt) { + case V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY: + reg = reg | EXYNOS4_ENC_FMT_GRAY; + break; + + case V4L2_JPEG_CHROMA_SUBSAMPLING_444: + reg = reg | EXYNOS4_ENC_FMT_YUV_444; + break; + + case V4L2_JPEG_CHROMA_SUBSAMPLING_422: + reg = reg | EXYNOS4_ENC_FMT_YUV_422; + break; + + case V4L2_JPEG_CHROMA_SUBSAMPLING_420: + reg = reg | EXYNOS4_ENC_FMT_YUV_420; + break; + + default: + break; + } + + writel(reg, base + EXYNOS4_IMG_FMT_REG); +} + +void exynos4_jpeg_set_interrupt(void __iomem *base) +{ + unsigned int reg; + + reg = readl(base + EXYNOS4_INT_EN_REG) & ~EXYNOS4_INT_EN_MASK; + writel(EXYNOS4_INT_EN_ALL, base + EXYNOS4_INT_EN_REG); +} + +unsigned int exynos4_jpeg_get_int_status(void __iomem *base) +{ + unsigned int int_status; + + int_status = readl(base + EXYNOS4_INT_STATUS_REG); + + return int_status; +} + +unsigned int exynos4_jpeg_get_fifo_status(void __iomem *base) +{ + unsigned int fifo_status; + + fifo_status = readl(base + EXYNOS4_FIFO_STATUS_REG); + + return fifo_status; +} + +void exynos4_jpeg_set_huf_table_enable(void __iomem *base, int value) +{ + unsigned int reg; + + reg = readl(base + EXYNOS4_JPEG_CNTL_REG) & ~EXYNOS4_HUF_TBL_EN; + + if (value == 1) + writel(reg | EXYNOS4_HUF_TBL_EN, + base + EXYNOS4_JPEG_CNTL_REG); + else + writel(reg | ~EXYNOS4_HUF_TBL_EN, + base + EXYNOS4_JPEG_CNTL_REG); +} + +void exynos4_jpeg_set_sys_int_enable(void __iomem *base, int value) +{ + unsigned int reg; + + reg = readl(base + EXYNOS4_JPEG_CNTL_REG) & ~(EXYNOS4_SYS_INT_EN); + + if (value == 1) + writel(EXYNOS4_SYS_INT_EN, base + EXYNOS4_JPEG_CNTL_REG); + else + writel(~EXYNOS4_SYS_INT_EN, base + EXYNOS4_JPEG_CNTL_REG); +} + +void exynos4_jpeg_set_stream_buf_address(void __iomem *base, + unsigned int address) +{ + writel(address, base + EXYNOS4_OUT_MEM_BASE_REG); +} + +void exynos4_jpeg_set_stream_size(void __iomem *base, + unsigned int x_value, unsigned int y_value) +{ + writel(0x0, base + EXYNOS4_JPEG_IMG_SIZE_REG); /* clear */ + writel(EXYNOS4_X_SIZE(x_value) | EXYNOS4_Y_SIZE(y_value), + base + EXYNOS4_JPEG_IMG_SIZE_REG); +} + +void exynos4_jpeg_set_frame_buf_address(void __iomem *base, + struct s5p_jpeg_addr *exynos4_jpeg_addr) +{ + writel(exynos4_jpeg_addr->y, base + EXYNOS4_IMG_BA_PLANE_1_REG); + writel(exynos4_jpeg_addr->cb, base + EXYNOS4_IMG_BA_PLANE_2_REG); + writel(exynos4_jpeg_addr->cr, base + EXYNOS4_IMG_BA_PLANE_3_REG); +} + +void exynos4_jpeg_set_encode_tbl_select(void __iomem *base, + enum exynos4_jpeg_img_quality_level level) +{ + unsigned int reg; + + reg = EXYNOS4_Q_TBL_COMP1_0 | EXYNOS4_Q_TBL_COMP2_1 | + EXYNOS4_Q_TBL_COMP3_1 | + EXYNOS4_HUFF_TBL_COMP1_AC_0_DC_1 | + EXYNOS4_HUFF_TBL_COMP2_AC_0_DC_0 | + EXYNOS4_HUFF_TBL_COMP3_AC_1_DC_1; + + writel(reg, base + EXYNOS4_TBL_SEL_REG); +} + +void exynos4_jpeg_set_encode_hoff_cnt(void __iomem *base, unsigned int fmt) +{ + if (fmt == V4L2_PIX_FMT_GREY) + writel(0xd2, base + EXYNOS4_HUFF_CNT_REG); + else + writel(0x1a2, base + EXYNOS4_HUFF_CNT_REG); +} + +unsigned int exynos4_jpeg_get_stream_size(void __iomem *base) +{ + unsigned int size; + + size = readl(base + EXYNOS4_BITSTREAM_SIZE_REG); + return size; +} + +void exynos4_jpeg_set_dec_bitstream_size(void __iomem *base, unsigned int size) +{ + writel(size, base + EXYNOS4_BITSTREAM_SIZE_REG); +} + +void exynos4_jpeg_get_frame_size(void __iomem *base, + unsigned int *width, unsigned int *height) +{ + *width = (readl(base + EXYNOS4_DECODE_XY_SIZE_REG) & + EXYNOS4_DECODED_SIZE_MASK); + *height = (readl(base + EXYNOS4_DECODE_XY_SIZE_REG) >> 16) & + EXYNOS4_DECODED_SIZE_MASK; +} + +unsigned int exynos4_jpeg_get_frame_fmt(void __iomem *base) +{ + return readl(base + EXYNOS4_DECODE_IMG_FMT_REG) & + EXYNOS4_JPEG_DECODED_IMG_FMT_MASK; +} + +void exynos4_jpeg_set_timer_count(void __iomem *base, unsigned int size) +{ + writel(size, base + EXYNOS4_INT_TIMER_COUNT_REG); +} diff --git a/drivers/media/platform/s5p-jpeg/jpeg-hw-exynos4.h b/drivers/media/platform/s5p-jpeg/jpeg-hw-exynos4.h new file mode 100644 index 000000000000..c228d28a4bc7 --- /dev/null +++ b/drivers/media/platform/s5p-jpeg/jpeg-hw-exynos4.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * Author: Jacek Anaszewski <j.anaszewski@samsung.com> + * + * Header file of the register interface for JPEG driver on Exynos4x12. + * + * 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 JPEG_HW_EXYNOS4_H_ +#define JPEG_HW_EXYNOS4_H_ + +void exynos4_jpeg_sw_reset(void __iomem *base); +void exynos4_jpeg_set_enc_dec_mode(void __iomem *base, unsigned int mode); +void exynos4_jpeg_set_img_fmt(void __iomem *base, unsigned int img_fmt); +void exynos4_jpeg_set_enc_out_fmt(void __iomem *base, unsigned int out_fmt); +void exynos4_jpeg_set_enc_tbl(void __iomem *base); +void exynos4_jpeg_set_interrupt(void __iomem *base); +unsigned int exynos4_jpeg_get_int_status(void __iomem *base); +void exynos4_jpeg_set_huf_table_enable(void __iomem *base, int value); +void exynos4_jpeg_set_sys_int_enable(void __iomem *base, int value); +void exynos4_jpeg_set_stream_buf_address(void __iomem *base, + unsigned int address); +void exynos4_jpeg_set_stream_size(void __iomem *base, + unsigned int x_value, unsigned int y_value); +void exynos4_jpeg_set_frame_buf_address(void __iomem *base, + struct s5p_jpeg_addr *jpeg_addr); +void exynos4_jpeg_set_encode_tbl_select(void __iomem *base, + enum exynos4_jpeg_img_quality_level level); +void exynos4_jpeg_set_encode_hoff_cnt(void __iomem *base, unsigned int fmt); +void exynos4_jpeg_set_dec_bitstream_size(void __iomem *base, unsigned int size); +unsigned int exynos4_jpeg_get_stream_size(void __iomem *base); +void exynos4_jpeg_get_frame_size(void __iomem *base, + unsigned int *width, unsigned int *height); +unsigned int exynos4_jpeg_get_frame_fmt(void __iomem *base); +unsigned int exynos4_jpeg_get_fifo_status(void __iomem *base); +void exynos4_jpeg_set_timer_count(void __iomem *base, unsigned int size); + +#endif /* JPEG_HW_EXYNOS4_H_ */ diff --git a/drivers/media/platform/s5p-jpeg/jpeg-hw.h b/drivers/media/platform/s5p-jpeg/jpeg-hw-s5p.c index b47e887b6138..52407d790726 100644 --- a/drivers/media/platform/s5p-jpeg/jpeg-hw.h +++ b/drivers/media/platform/s5p-jpeg/jpeg-hw-s5p.c @@ -9,27 +9,15 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ -#ifndef JPEG_HW_H_ -#define JPEG_HW_H_ #include <linux/io.h> #include <linux/videodev2.h> -#include "jpeg-hw.h" +#include "jpeg-core.h" #include "jpeg-regs.h" +#include "jpeg-hw-s5p.h" -#define S5P_JPEG_MIN_WIDTH 32 -#define S5P_JPEG_MIN_HEIGHT 32 -#define S5P_JPEG_MAX_WIDTH 8192 -#define S5P_JPEG_MAX_HEIGHT 8192 -#define S5P_JPEG_ENCODE 0 -#define S5P_JPEG_DECODE 1 -#define S5P_JPEG_RAW_IN_565 0 -#define S5P_JPEG_RAW_IN_422 1 -#define S5P_JPEG_RAW_OUT_422 0 -#define S5P_JPEG_RAW_OUT_420 1 - -static inline void jpeg_reset(void __iomem *regs) +void s5p_jpeg_reset(void __iomem *regs) { unsigned long reg; @@ -42,12 +30,12 @@ static inline void jpeg_reset(void __iomem *regs) } } -static inline void jpeg_poweron(void __iomem *regs) +void s5p_jpeg_poweron(void __iomem *regs) { writel(S5P_POWER_ON, regs + S5P_JPGCLKCON); } -static inline void jpeg_input_raw_mode(void __iomem *regs, unsigned long mode) +void s5p_jpeg_input_raw_mode(void __iomem *regs, unsigned long mode) { unsigned long reg, m; @@ -63,7 +51,7 @@ static inline void jpeg_input_raw_mode(void __iomem *regs, unsigned long mode) writel(reg, regs + S5P_JPGCMOD); } -static inline void jpeg_input_raw_y16(void __iomem *regs, bool y16) +void s5p_jpeg_input_raw_y16(void __iomem *regs, bool y16) { unsigned long reg; @@ -75,7 +63,7 @@ static inline void jpeg_input_raw_y16(void __iomem *regs, bool y16) writel(reg, regs + S5P_JPGCMOD); } -static inline void jpeg_proc_mode(void __iomem *regs, unsigned long mode) +void s5p_jpeg_proc_mode(void __iomem *regs, unsigned long mode) { unsigned long reg, m; @@ -90,7 +78,7 @@ static inline void jpeg_proc_mode(void __iomem *regs, unsigned long mode) writel(reg, regs + S5P_JPGMOD); } -static inline void jpeg_subsampling_mode(void __iomem *regs, unsigned int mode) +void s5p_jpeg_subsampling_mode(void __iomem *regs, unsigned int mode) { unsigned long reg, m; @@ -105,12 +93,12 @@ static inline void jpeg_subsampling_mode(void __iomem *regs, unsigned int mode) writel(reg, regs + S5P_JPGMOD); } -static inline unsigned int jpeg_get_subsampling_mode(void __iomem *regs) +unsigned int s5p_jpeg_get_subsampling_mode(void __iomem *regs) { return readl(regs + S5P_JPGMOD) & S5P_SUBSAMPLING_MODE_MASK; } -static inline void jpeg_dri(void __iomem *regs, unsigned int dri) +void s5p_jpeg_dri(void __iomem *regs, unsigned int dri) { unsigned long reg; @@ -125,7 +113,7 @@ static inline void jpeg_dri(void __iomem *regs, unsigned int dri) writel(reg, regs + S5P_JPGDRI_L); } -static inline void jpeg_qtbl(void __iomem *regs, unsigned int t, unsigned int n) +void s5p_jpeg_qtbl(void __iomem *regs, unsigned int t, unsigned int n) { unsigned long reg; @@ -135,7 +123,7 @@ static inline void jpeg_qtbl(void __iomem *regs, unsigned int t, unsigned int n) writel(reg, regs + S5P_JPG_QTBL); } -static inline void jpeg_htbl_ac(void __iomem *regs, unsigned int t) +void s5p_jpeg_htbl_ac(void __iomem *regs, unsigned int t) { unsigned long reg; @@ -146,7 +134,7 @@ static inline void jpeg_htbl_ac(void __iomem *regs, unsigned int t) writel(reg, regs + S5P_JPG_HTBL); } -static inline void jpeg_htbl_dc(void __iomem *regs, unsigned int t) +void s5p_jpeg_htbl_dc(void __iomem *regs, unsigned int t) { unsigned long reg; @@ -157,7 +145,7 @@ static inline void jpeg_htbl_dc(void __iomem *regs, unsigned int t) writel(reg, regs + S5P_JPG_HTBL); } -static inline void jpeg_y(void __iomem *regs, unsigned int y) +void s5p_jpeg_y(void __iomem *regs, unsigned int y) { unsigned long reg; @@ -172,7 +160,7 @@ static inline void jpeg_y(void __iomem *regs, unsigned int y) writel(reg, regs + S5P_JPGY_L); } -static inline void jpeg_x(void __iomem *regs, unsigned int x) +void s5p_jpeg_x(void __iomem *regs, unsigned int x) { unsigned long reg; @@ -187,7 +175,7 @@ static inline void jpeg_x(void __iomem *regs, unsigned int x) writel(reg, regs + S5P_JPGX_L); } -static inline void jpeg_rst_int_enable(void __iomem *regs, bool enable) +void s5p_jpeg_rst_int_enable(void __iomem *regs, bool enable) { unsigned long reg; @@ -198,7 +186,7 @@ static inline void jpeg_rst_int_enable(void __iomem *regs, bool enable) writel(reg, regs + S5P_JPGINTSE); } -static inline void jpeg_data_num_int_enable(void __iomem *regs, bool enable) +void s5p_jpeg_data_num_int_enable(void __iomem *regs, bool enable) { unsigned long reg; @@ -209,7 +197,7 @@ static inline void jpeg_data_num_int_enable(void __iomem *regs, bool enable) writel(reg, regs + S5P_JPGINTSE); } -static inline void jpeg_final_mcu_num_int_enable(void __iomem *regs, bool enbl) +void s5p_jpeg_final_mcu_num_int_enable(void __iomem *regs, bool enbl) { unsigned long reg; @@ -220,7 +208,7 @@ static inline void jpeg_final_mcu_num_int_enable(void __iomem *regs, bool enbl) writel(reg, regs + S5P_JPGINTSE); } -static inline void jpeg_timer_enable(void __iomem *regs, unsigned long val) +void s5p_jpeg_timer_enable(void __iomem *regs, unsigned long val) { unsigned long reg; @@ -231,7 +219,7 @@ static inline void jpeg_timer_enable(void __iomem *regs, unsigned long val) writel(reg, regs + S5P_JPG_TIMER_SE); } -static inline void jpeg_timer_disable(void __iomem *regs) +void s5p_jpeg_timer_disable(void __iomem *regs) { unsigned long reg; @@ -240,13 +228,13 @@ static inline void jpeg_timer_disable(void __iomem *regs) writel(reg, regs + S5P_JPG_TIMER_SE); } -static inline int jpeg_timer_stat(void __iomem *regs) +int s5p_jpeg_timer_stat(void __iomem *regs) { return (int)((readl(regs + S5P_JPG_TIMER_ST) & S5P_TIMER_INT_STAT_MASK) >> S5P_TIMER_INT_STAT_SHIFT); } -static inline void jpeg_clear_timer_stat(void __iomem *regs) +void s5p_jpeg_clear_timer_stat(void __iomem *regs) { unsigned long reg; @@ -255,7 +243,7 @@ static inline void jpeg_clear_timer_stat(void __iomem *regs) writel(reg, regs + S5P_JPG_TIMER_SE); } -static inline void jpeg_enc_stream_int(void __iomem *regs, unsigned long size) +void s5p_jpeg_enc_stream_int(void __iomem *regs, unsigned long size) { unsigned long reg; @@ -266,13 +254,13 @@ static inline void jpeg_enc_stream_int(void __iomem *regs, unsigned long size) writel(reg, regs + S5P_JPG_ENC_STREAM_INTSE); } -static inline int jpeg_enc_stream_stat(void __iomem *regs) +int s5p_jpeg_enc_stream_stat(void __iomem *regs) { return (int)(readl(regs + S5P_JPG_ENC_STREAM_INTST) & S5P_ENC_STREAM_INT_STAT_MASK); } -static inline void jpeg_clear_enc_stream_stat(void __iomem *regs) +void s5p_jpeg_clear_enc_stream_stat(void __iomem *regs) { unsigned long reg; @@ -281,7 +269,7 @@ static inline void jpeg_clear_enc_stream_stat(void __iomem *regs) writel(reg, regs + S5P_JPG_ENC_STREAM_INTSE); } -static inline void jpeg_outform_raw(void __iomem *regs, unsigned long format) +void s5p_jpeg_outform_raw(void __iomem *regs, unsigned long format) { unsigned long reg, f; @@ -296,17 +284,17 @@ static inline void jpeg_outform_raw(void __iomem *regs, unsigned long format) writel(reg, regs + S5P_JPG_OUTFORM); } -static inline void jpeg_jpgadr(void __iomem *regs, unsigned long addr) +void s5p_jpeg_jpgadr(void __iomem *regs, unsigned long addr) { writel(addr, regs + S5P_JPG_JPGADR); } -static inline void jpeg_imgadr(void __iomem *regs, unsigned long addr) +void s5p_jpeg_imgadr(void __iomem *regs, unsigned long addr) { writel(addr, regs + S5P_JPG_IMGADR); } -static inline void jpeg_coef(void __iomem *regs, unsigned int i, +void s5p_jpeg_coef(void __iomem *regs, unsigned int i, unsigned int j, unsigned int coef) { unsigned long reg; @@ -317,24 +305,24 @@ static inline void jpeg_coef(void __iomem *regs, unsigned int i, writel(reg, regs + S5P_JPG_COEF(i)); } -static inline void jpeg_start(void __iomem *regs) +void s5p_jpeg_start(void __iomem *regs) { writel(1, regs + S5P_JSTART); } -static inline int jpeg_result_stat_ok(void __iomem *regs) +int s5p_jpeg_result_stat_ok(void __iomem *regs) { return (int)((readl(regs + S5P_JPGINTST) & S5P_RESULT_STAT_MASK) >> S5P_RESULT_STAT_SHIFT); } -static inline int jpeg_stream_stat_ok(void __iomem *regs) +int s5p_jpeg_stream_stat_ok(void __iomem *regs) { return !(int)((readl(regs + S5P_JPGINTST) & S5P_STREAM_STAT_MASK) >> S5P_STREAM_STAT_SHIFT); } -static inline void jpeg_clear_int(void __iomem *regs) +void s5p_jpeg_clear_int(void __iomem *regs) { unsigned long reg; @@ -343,7 +331,7 @@ static inline void jpeg_clear_int(void __iomem *regs) reg = readl(regs + S5P_JPGOPR); } -static inline unsigned int jpeg_compressed_size(void __iomem *regs) +unsigned int s5p_jpeg_compressed_size(void __iomem *regs) { unsigned long jpeg_size = 0; @@ -353,5 +341,3 @@ static inline unsigned int jpeg_compressed_size(void __iomem *regs) return (unsigned int)jpeg_size; } - -#endif /* JPEG_HW_H_ */ diff --git a/drivers/media/platform/s5p-jpeg/jpeg-hw-s5p.h b/drivers/media/platform/s5p-jpeg/jpeg-hw-s5p.h new file mode 100644 index 000000000000..c11ebe86b9c9 --- /dev/null +++ b/drivers/media/platform/s5p-jpeg/jpeg-hw-s5p.h @@ -0,0 +1,63 @@ +/* linux/drivers/media/platform/s5p-jpeg/jpeg-hw.h + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz <andrzej.p@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 JPEG_HW_S5P_H_ +#define JPEG_HW_S5P_H_ + +#include <linux/io.h> +#include <linux/videodev2.h> + +#include "jpeg-regs.h" + +#define S5P_JPEG_MIN_WIDTH 32 +#define S5P_JPEG_MIN_HEIGHT 32 +#define S5P_JPEG_MAX_WIDTH 8192 +#define S5P_JPEG_MAX_HEIGHT 8192 +#define S5P_JPEG_RAW_IN_565 0 +#define S5P_JPEG_RAW_IN_422 1 +#define S5P_JPEG_RAW_OUT_422 0 +#define S5P_JPEG_RAW_OUT_420 1 + +void s5p_jpeg_reset(void __iomem *regs); +void s5p_jpeg_poweron(void __iomem *regs); +void s5p_jpeg_input_raw_mode(void __iomem *regs, unsigned long mode); +void s5p_jpeg_input_raw_y16(void __iomem *regs, bool y16); +void s5p_jpeg_proc_mode(void __iomem *regs, unsigned long mode); +void s5p_jpeg_subsampling_mode(void __iomem *regs, unsigned int mode); +unsigned int s5p_jpeg_get_subsampling_mode(void __iomem *regs); +void s5p_jpeg_dri(void __iomem *regs, unsigned int dri); +void s5p_jpeg_qtbl(void __iomem *regs, unsigned int t, unsigned int n); +void s5p_jpeg_htbl_ac(void __iomem *regs, unsigned int t); +void s5p_jpeg_htbl_dc(void __iomem *regs, unsigned int t); +void s5p_jpeg_y(void __iomem *regs, unsigned int y); +void s5p_jpeg_x(void __iomem *regs, unsigned int x); +void s5p_jpeg_rst_int_enable(void __iomem *regs, bool enable); +void s5p_jpeg_data_num_int_enable(void __iomem *regs, bool enable); +void s5p_jpeg_final_mcu_num_int_enable(void __iomem *regs, bool enbl); +void s5p_jpeg_timer_enable(void __iomem *regs, unsigned long val); +void s5p_jpeg_timer_disable(void __iomem *regs); +int s5p_jpeg_timer_stat(void __iomem *regs); +void s5p_jpeg_clear_timer_stat(void __iomem *regs); +void s5p_jpeg_enc_stream_int(void __iomem *regs, unsigned long size); +int s5p_jpeg_enc_stream_stat(void __iomem *regs); +void s5p_jpeg_clear_enc_stream_stat(void __iomem *regs); +void s5p_jpeg_outform_raw(void __iomem *regs, unsigned long format); +void s5p_jpeg_jpgadr(void __iomem *regs, unsigned long addr); +void s5p_jpeg_imgadr(void __iomem *regs, unsigned long addr); +void s5p_jpeg_coef(void __iomem *regs, unsigned int i, + unsigned int j, unsigned int coef); +void s5p_jpeg_start(void __iomem *regs); +int s5p_jpeg_result_stat_ok(void __iomem *regs); +int s5p_jpeg_stream_stat_ok(void __iomem *regs); +void s5p_jpeg_clear_int(void __iomem *regs); +unsigned int s5p_jpeg_compressed_size(void __iomem *regs); + +#endif /* JPEG_HW_S5P_H_ */ diff --git a/drivers/media/platform/s5p-jpeg/jpeg-regs.h b/drivers/media/platform/s5p-jpeg/jpeg-regs.h index 38e50815668c..33f2c7374cfd 100644 --- a/drivers/media/platform/s5p-jpeg/jpeg-regs.h +++ b/drivers/media/platform/s5p-jpeg/jpeg-regs.h @@ -2,10 +2,11 @@ * * Register definition file for Samsung JPEG codec driver * - * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * Copyright (c) 2011-2013 Samsung Electronics Co., Ltd. * http://www.samsung.com * * Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com> + * Author: Jacek Anaszewski <j.anaszewski@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 @@ -15,6 +16,8 @@ #ifndef JPEG_REGS_H_ #define JPEG_REGS_H_ +/* Register and bit definitions for S5PC210 */ + /* JPEG mode register */ #define S5P_JPGMOD 0x00 #define S5P_PROC_MODE_MASK (0x1 << 3) @@ -166,5 +169,209 @@ /* JPEG AC Huffman table register */ #define S5P_JPG_HACTBLG(n) (0x8c0 + (n) * 0x400) + +/* Register and bit definitions for Exynos 4x12 */ + +/* JPEG Codec Control Registers */ +#define EXYNOS4_JPEG_CNTL_REG 0x00 +#define EXYNOS4_INT_EN_REG 0x04 +#define EXYNOS4_INT_TIMER_COUNT_REG 0x08 +#define EXYNOS4_INT_STATUS_REG 0x0c +#define EXYNOS4_OUT_MEM_BASE_REG 0x10 +#define EXYNOS4_JPEG_IMG_SIZE_REG 0x14 +#define EXYNOS4_IMG_BA_PLANE_1_REG 0x18 +#define EXYNOS4_IMG_SO_PLANE_1_REG 0x1c +#define EXYNOS4_IMG_PO_PLANE_1_REG 0x20 +#define EXYNOS4_IMG_BA_PLANE_2_REG 0x24 +#define EXYNOS4_IMG_SO_PLANE_2_REG 0x28 +#define EXYNOS4_IMG_PO_PLANE_2_REG 0x2c +#define EXYNOS4_IMG_BA_PLANE_3_REG 0x30 +#define EXYNOS4_IMG_SO_PLANE_3_REG 0x34 +#define EXYNOS4_IMG_PO_PLANE_3_REG 0x38 + +#define EXYNOS4_TBL_SEL_REG 0x3c + +#define EXYNOS4_IMG_FMT_REG 0x40 + +#define EXYNOS4_BITSTREAM_SIZE_REG 0x44 +#define EXYNOS4_PADDING_REG 0x48 +#define EXYNOS4_HUFF_CNT_REG 0x4c +#define EXYNOS4_FIFO_STATUS_REG 0x50 +#define EXYNOS4_DECODE_XY_SIZE_REG 0x54 +#define EXYNOS4_DECODE_IMG_FMT_REG 0x58 + +#define EXYNOS4_QUAN_TBL_ENTRY_REG 0x100 +#define EXYNOS4_HUFF_TBL_ENTRY_REG 0x200 + + +/****************************************************************/ +/* Bit definition part */ +/****************************************************************/ + +/* JPEG CNTL Register bit */ +#define EXYNOS4_ENC_DEC_MODE_MASK (0xfffffffc << 0) +#define EXYNOS4_DEC_MODE (1 << 0) +#define EXYNOS4_ENC_MODE (1 << 1) +#define EXYNOS4_AUTO_RST_MARKER (1 << 2) +#define EXYNOS4_RST_INTERVAL_SHIFT 3 +#define EXYNOS4_RST_INTERVAL(x) (((x) & 0xffff) \ + << EXYNOS4_RST_INTERVAL_SHIFT) +#define EXYNOS4_HUF_TBL_EN (1 << 19) +#define EXYNOS4_HOR_SCALING_SHIFT 20 +#define EXYNOS4_HOR_SCALING_MASK (3 << EXYNOS4_HOR_SCALING_SHIFT) +#define EXYNOS4_HOR_SCALING(x) (((x) & 0x3) \ + << EXYNOS4_HOR_SCALING_SHIFT) +#define EXYNOS4_VER_SCALING_SHIFT 22 +#define EXYNOS4_VER_SCALING_MASK (3 << EXYNOS4_VER_SCALING_SHIFT) +#define EXYNOS4_VER_SCALING(x) (((x) & 0x3) \ + << EXYNOS4_VER_SCALING_SHIFT) +#define EXYNOS4_PADDING (1 << 27) +#define EXYNOS4_SYS_INT_EN (1 << 28) +#define EXYNOS4_SOFT_RESET_HI (1 << 29) + +/* JPEG INT Register bit */ +#define EXYNOS4_INT_EN_MASK (0x1f << 0) +#define EXYNOS4_PROT_ERR_INT_EN (1 << 0) +#define EXYNOS4_IMG_COMPLETION_INT_EN (1 << 1) +#define EXYNOS4_DEC_INVALID_FORMAT_EN (1 << 2) +#define EXYNOS4_MULTI_SCAN_ERROR_EN (1 << 3) +#define EXYNOS4_FRAME_ERR_EN (1 << 4) +#define EXYNOS4_INT_EN_ALL (0x1f << 0) + +#define EXYNOS4_MOD_REG_PROC_ENC (0 << 3) +#define EXYNOS4_MOD_REG_PROC_DEC (1 << 3) + +#define EXYNOS4_MOD_REG_SUBSAMPLE_444 (0 << 0) +#define EXYNOS4_MOD_REG_SUBSAMPLE_422 (1 << 0) +#define EXYNOS4_MOD_REG_SUBSAMPLE_420 (2 << 0) +#define EXYNOS4_MOD_REG_SUBSAMPLE_GRAY (3 << 0) + + +/* JPEG IMAGE SIZE Register bit */ +#define EXYNOS4_X_SIZE_SHIFT 0 +#define EXYNOS4_X_SIZE_MASK (0xffff << EXYNOS4_X_SIZE_SHIFT) +#define EXYNOS4_X_SIZE(x) (((x) & 0xffff) << EXYNOS4_X_SIZE_SHIFT) +#define EXYNOS4_Y_SIZE_SHIFT 16 +#define EXYNOS4_Y_SIZE_MASK (0xffff << EXYNOS4_Y_SIZE_SHIFT) +#define EXYNOS4_Y_SIZE(x) (((x) & 0xffff) << EXYNOS4_Y_SIZE_SHIFT) + +/* JPEG IMAGE FORMAT Register bit */ +#define EXYNOS4_ENC_IN_FMT_MASK 0xffff0000 +#define EXYNOS4_ENC_GRAY_IMG (0 << 0) +#define EXYNOS4_ENC_RGB_IMG (1 << 0) +#define EXYNOS4_ENC_YUV_444_IMG (2 << 0) +#define EXYNOS4_ENC_YUV_422_IMG (3 << 0) +#define EXYNOS4_ENC_YUV_440_IMG (4 << 0) + +#define EXYNOS4_DEC_GRAY_IMG (0 << 0) +#define EXYNOS4_DEC_RGB_IMG (1 << 0) +#define EXYNOS4_DEC_YUV_444_IMG (2 << 0) +#define EXYNOS4_DEC_YUV_422_IMG (3 << 0) +#define EXYNOS4_DEC_YUV_420_IMG (4 << 0) + +#define EXYNOS4_GRAY_IMG_IP_SHIFT 3 +#define EXYNOS4_GRAY_IMG_IP_MASK (7 << EXYNOS4_GRAY_IMG_IP_SHIFT) +#define EXYNOS4_GRAY_IMG_IP (4 << EXYNOS4_GRAY_IMG_IP_SHIFT) + +#define EXYNOS4_RGB_IP_SHIFT 6 +#define EXYNOS4_RGB_IP_MASK (7 << EXYNOS4_RGB_IP_SHIFT) +#define EXYNOS4_RGB_IP_RGB_16BIT_IMG (4 << EXYNOS4_RGB_IP_SHIFT) +#define EXYNOS4_RGB_IP_RGB_32BIT_IMG (5 << EXYNOS4_RGB_IP_SHIFT) + +#define EXYNOS4_YUV_444_IP_SHIFT 9 +#define EXYNOS4_YUV_444_IP_MASK (7 << EXYNOS4_YUV_444_IP_SHIFT) +#define EXYNOS4_YUV_444_IP_YUV_444_2P_IMG (4 << EXYNOS4_YUV_444_IP_SHIFT) +#define EXYNOS4_YUV_444_IP_YUV_444_3P_IMG (5 << EXYNOS4_YUV_444_IP_SHIFT) + +#define EXYNOS4_YUV_422_IP_SHIFT 12 +#define EXYNOS4_YUV_422_IP_MASK (7 << EXYNOS4_YUV_422_IP_SHIFT) +#define EXYNOS4_YUV_422_IP_YUV_422_1P_IMG (4 << EXYNOS4_YUV_422_IP_SHIFT) +#define EXYNOS4_YUV_422_IP_YUV_422_2P_IMG (5 << EXYNOS4_YUV_422_IP_SHIFT) +#define EXYNOS4_YUV_422_IP_YUV_422_3P_IMG (6 << EXYNOS4_YUV_422_IP_SHIFT) + +#define EXYNOS4_YUV_420_IP_SHIFT 15 +#define EXYNOS4_YUV_420_IP_MASK (7 << EXYNOS4_YUV_420_IP_SHIFT) +#define EXYNOS4_YUV_420_IP_YUV_420_2P_IMG (4 << EXYNOS4_YUV_420_IP_SHIFT) +#define EXYNOS4_YUV_420_IP_YUV_420_3P_IMG (5 << EXYNOS4_YUV_420_IP_SHIFT) + +#define EXYNOS4_ENC_FMT_SHIFT 24 +#define EXYNOS4_ENC_FMT_MASK (3 << EXYNOS4_ENC_FMT_SHIFT) +#define EXYNOS4_ENC_FMT_GRAY (0 << EXYNOS4_ENC_FMT_SHIFT) +#define EXYNOS4_ENC_FMT_YUV_444 (1 << EXYNOS4_ENC_FMT_SHIFT) +#define EXYNOS4_ENC_FMT_YUV_422 (2 << EXYNOS4_ENC_FMT_SHIFT) +#define EXYNOS4_ENC_FMT_YUV_420 (3 << EXYNOS4_ENC_FMT_SHIFT) + +#define EXYNOS4_JPEG_DECODED_IMG_FMT_MASK 0x03 + +#define EXYNOS4_SWAP_CHROMA_CRCB (1 << 26) +#define EXYNOS4_SWAP_CHROMA_CBCR (0 << 26) + +/* JPEG HUFF count Register bit */ +#define EXYNOS4_HUFF_COUNT_MASK 0xffff + +/* JPEG Decoded_img_x_y_size Register bit */ +#define EXYNOS4_DECODED_SIZE_MASK 0x0000ffff + +/* JPEG Decoded image format Register bit */ +#define EXYNOS4_DECODED_IMG_FMT_MASK 0x3 + +/* JPEG TBL SEL Register bit */ +#define EXYNOS4_Q_TBL_COMP1_0 (0 << 0) +#define EXYNOS4_Q_TBL_COMP1_1 (1 << 0) +#define EXYNOS4_Q_TBL_COMP1_2 (2 << 0) +#define EXYNOS4_Q_TBL_COMP1_3 (3 << 0) + +#define EXYNOS4_Q_TBL_COMP2_0 (0 << 2) +#define EXYNOS4_Q_TBL_COMP2_1 (1 << 2) +#define EXYNOS4_Q_TBL_COMP2_2 (2 << 2) +#define EXYNOS4_Q_TBL_COMP2_3 (3 << 2) + +#define EXYNOS4_Q_TBL_COMP3_0 (0 << 4) +#define EXYNOS4_Q_TBL_COMP3_1 (1 << 4) +#define EXYNOS4_Q_TBL_COMP3_2 (2 << 4) +#define EXYNOS4_Q_TBL_COMP3_3 (3 << 4) + +#define EXYNOS4_HUFF_TBL_COMP1_AC_0_DC_0 (0 << 6) +#define EXYNOS4_HUFF_TBL_COMP1_AC_0_DC_1 (1 << 6) +#define EXYNOS4_HUFF_TBL_COMP1_AC_1_DC_0 (2 << 6) +#define EXYNOS4_HUFF_TBL_COMP1_AC_1_DC_1 (3 << 6) + +#define EXYNOS4_HUFF_TBL_COMP2_AC_0_DC_0 (0 << 8) +#define EXYNOS4_HUFF_TBL_COMP2_AC_0_DC_1 (1 << 8) +#define EXYNOS4_HUFF_TBL_COMP2_AC_1_DC_0 (2 << 8) +#define EXYNOS4_HUFF_TBL_COMP2_AC_1_DC_1 (3 << 8) + +#define EXYNOS4_HUFF_TBL_COMP3_AC_0_DC_0 (0 << 10) +#define EXYNOS4_HUFF_TBL_COMP3_AC_0_DC_1 (1 << 10) +#define EXYNOS4_HUFF_TBL_COMP3_AC_1_DC_0 (2 << 10) +#define EXYNOS4_HUFF_TBL_COMP3_AC_1_DC_1 (3 << 10) + +/* JPEG quantizer table register */ +#define EXYNOS4_QTBL_CONTENT(n) (0x100 + (n) * 0x40) + +/* JPEG DC luminance (code length) Huffman table register */ +#define EXYNOS4_HUFF_TBL_HDCLL 0x200 + +/* JPEG DC luminance (values) Huffman table register */ +#define EXYNOS4_HUFF_TBL_HDCLV 0x210 + +/* JPEG DC chrominance (code length) Huffman table register */ +#define EXYNOS4_HUFF_TBL_HDCCL 0x220 + +/* JPEG DC chrominance (values) Huffman table register */ +#define EXYNOS4_HUFF_TBL_HDCCV 0x230 + +/* JPEG AC luminance (code length) Huffman table register */ +#define EXYNOS4_HUFF_TBL_HACLL 0x240 + +/* JPEG AC luminance (values) Huffman table register */ +#define EXYNOS4_HUFF_TBL_HACLV 0x250 + +/* JPEG AC chrominance (code length) Huffman table register */ +#define EXYNOS4_HUFF_TBL_HACCL 0x300 + +/* JPEG AC chrominance (values) Huffman table register */ +#define EXYNOS4_HUFF_TBL_HACCV 0x310 + #endif /* JPEG_REGS_H_ */ diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc.c b/drivers/media/platform/s5p-mfc/s5p_mfc.c index e46067a57853..e2aac592d29f 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc.c @@ -177,21 +177,6 @@ unlock: mutex_unlock(&dev->mfc_mutex); } -static enum s5p_mfc_node_type s5p_mfc_get_node_type(struct file *file) -{ - struct video_device *vdev = video_devdata(file); - - if (!vdev) { - mfc_err("failed to get video_device"); - return MFCNODE_INVALID; - } - if (vdev->index == 0) - return MFCNODE_DECODER; - else if (vdev->index == 1) - return MFCNODE_ENCODER; - return MFCNODE_INVALID; -} - static void s5p_mfc_clear_int_flags(struct s5p_mfc_dev *dev) { mfc_write(dev, 0, S5P_FIMV_RISC_HOST_INT); @@ -705,6 +690,7 @@ irq_cleanup_hw: /* Open an MFC node */ static int s5p_mfc_open(struct file *file) { + struct video_device *vdev = video_devdata(file); struct s5p_mfc_dev *dev = video_drvdata(file); struct s5p_mfc_ctx *ctx = NULL; struct vb2_queue *q; @@ -742,7 +728,7 @@ static int s5p_mfc_open(struct file *file) /* Mark context as idle */ clear_work_bit_irqsave(ctx); dev->ctx[ctx->num] = ctx; - if (s5p_mfc_get_node_type(file) == MFCNODE_DECODER) { + if (vdev == dev->vfd_dec) { ctx->type = MFCINST_DECODER; ctx->c_ops = get_dec_codec_ops(); s5p_mfc_dec_init(ctx); @@ -752,7 +738,7 @@ static int s5p_mfc_open(struct file *file) mfc_err("Failed to setup mfc controls\n"); goto err_ctrls_setup; } - } else if (s5p_mfc_get_node_type(file) == MFCNODE_ENCODER) { + } else if (vdev == dev->vfd_enc) { ctx->type = MFCINST_ENCODER; ctx->c_ops = get_enc_codec_ops(); /* only for encoder */ @@ -797,10 +783,10 @@ static int s5p_mfc_open(struct file *file) q = &ctx->vq_dst; q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; q->drv_priv = &ctx->fh; - if (s5p_mfc_get_node_type(file) == MFCNODE_DECODER) { + if (vdev == dev->vfd_dec) { q->io_modes = VB2_MMAP; q->ops = get_dec_queue_ops(); - } else if (s5p_mfc_get_node_type(file) == MFCNODE_ENCODER) { + } else if (vdev == dev->vfd_enc) { q->io_modes = VB2_MMAP | VB2_USERPTR; q->ops = get_enc_queue_ops(); } else { @@ -819,10 +805,10 @@ static int s5p_mfc_open(struct file *file) q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; q->io_modes = VB2_MMAP; q->drv_priv = &ctx->fh; - if (s5p_mfc_get_node_type(file) == MFCNODE_DECODER) { + if (vdev == dev->vfd_dec) { q->io_modes = VB2_MMAP; q->ops = get_dec_queue_ops(); - } else if (s5p_mfc_get_node_type(file) == MFCNODE_ENCODER) { + } else if (vdev == dev->vfd_enc) { q->io_modes = VB2_MMAP | VB2_USERPTR; q->ops = get_enc_queue_ops(); } else { diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_common.h b/drivers/media/platform/s5p-mfc/s5p_mfc_common.h index 6920b546181a..f723f1f2f578 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_common.h +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_common.h @@ -115,15 +115,6 @@ enum s5p_mfc_fmt_type { }; /** - * enum s5p_mfc_node_type - The type of an MFC device node. - */ -enum s5p_mfc_node_type { - MFCNODE_INVALID = -1, - MFCNODE_DECODER = 0, - MFCNODE_ENCODER = 1, -}; - -/** * enum s5p_mfc_inst_type - The type of an MFC instance. */ enum s5p_mfc_inst_type { @@ -422,6 +413,11 @@ struct s5p_mfc_vp8_enc_params { enum v4l2_vp8_golden_frame_sel golden_frame_sel; u8 hier_layer; u8 hier_layer_qp[3]; + u8 rc_min_qp; + u8 rc_max_qp; + u8 rc_frame_qp; + u8 rc_p_frame_qp; + u8 profile; }; /** diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c b/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c index 4ff3b6cd6842..91b6e020ddf3 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c @@ -618,6 +618,46 @@ static struct mfc_control controls[] = { .default_value = V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_USE_PREV, .menu_skip_mask = 0, }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_MAX_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 127, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_MIN_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 11, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_I_FRAME_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 10, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_P_FRAME_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 10, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_PROFILE, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 3, + .step = 1, + .default_value = 0, + }, }; #define NUM_CTRLS ARRAY_SIZE(controls) @@ -1557,6 +1597,21 @@ static int s5p_mfc_enc_s_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_SEL: p->codec.vp8.golden_frame_sel = ctrl->val; break; + case V4L2_CID_MPEG_VIDEO_VPX_MIN_QP: + p->codec.vp8.rc_min_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_MAX_QP: + p->codec.vp8.rc_max_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_I_FRAME_QP: + p->codec.vp8.rc_frame_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_P_FRAME_QP: + p->codec.vp8.rc_p_frame_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_PROFILE: + p->codec.vp8.profile = ctrl->val; + break; default: v4l2_err(&dev->v4l2_dev, "Invalid control, id=%d, val=%d\n", ctrl->id, ctrl->val); @@ -1863,7 +1918,7 @@ static int s5p_mfc_start_streaming(struct vb2_queue *q, unsigned int count) if (ctx->src_bufs_cnt < ctx->pb_count) { mfc_err("Need minimum %d OUTPUT buffers\n", ctx->pb_count); - return -EINVAL; + return -ENOBUFS; } } 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 461358c4a790..f6ff2dbf3a1d 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v6.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v6.c @@ -1197,10 +1197,8 @@ static int s5p_mfc_set_enc_params_vp8(struct s5p_mfc_ctx *ctx) reg |= ((p->num_b_frame & 0x3) << 16); WRITEL(reg, S5P_FIMV_E_GOP_CONFIG_V6); - /* profile & level */ - reg = 0; - /** profile */ - reg |= (0x1 << 4); + /* profile - 0 ~ 3 */ + reg = p_vp8->profile & 0x3; WRITEL(reg, S5P_FIMV_E_PICTURE_PROFILE_V6); /* rate control config. */ @@ -1218,6 +1216,26 @@ static int s5p_mfc_set_enc_params_vp8(struct s5p_mfc_ctx *ctx) WRITEL(reg, S5P_FIMV_E_RC_FRAME_RATE_V6); } + /* frame QP */ + reg &= ~(0x7F); + reg |= p_vp8->rc_frame_qp & 0x7F; + WRITEL(reg, S5P_FIMV_E_RC_CONFIG_V6); + + /* other QPs */ + WRITEL(0x0, S5P_FIMV_E_FIXED_PICTURE_QP_V6); + if (!p->rc_frame && !p->rc_mb) { + reg = 0; + reg |= ((p_vp8->rc_p_frame_qp & 0x7F) << 8); + reg |= p_vp8->rc_frame_qp & 0x7F; + WRITEL(reg, S5P_FIMV_E_FIXED_PICTURE_QP_V6); + } + + /* max QP */ + reg = ((p_vp8->rc_max_qp & 0x7F) << 8); + /* min QP */ + reg |= p_vp8->rc_min_qp & 0x7F; + WRITEL(reg, S5P_FIMV_E_RC_QP_BOUND_V6); + /* vbv buffer size */ if (p->frame_skip_mode == V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT) { diff --git a/drivers/media/platform/s5p-tv/mixer_drv.c b/drivers/media/platform/s5p-tv/mixer_drv.c index 51805a5e2beb..bc08b5f28e44 100644 --- a/drivers/media/platform/s5p-tv/mixer_drv.c +++ b/drivers/media/platform/s5p-tv/mixer_drv.c @@ -347,19 +347,41 @@ static int mxr_runtime_resume(struct device *dev) { struct mxr_device *mdev = to_mdev(dev); struct mxr_resources *res = &mdev->res; + int ret; mxr_dbg(mdev, "resume - start\n"); mutex_lock(&mdev->mutex); /* turn clocks on */ - clk_enable(res->mixer); - clk_enable(res->vp); - clk_enable(res->sclk_mixer); + ret = clk_prepare_enable(res->mixer); + if (ret < 0) { + dev_err(mdev->dev, "clk_prepare_enable(mixer) failed\n"); + goto fail; + } + ret = clk_prepare_enable(res->vp); + if (ret < 0) { + dev_err(mdev->dev, "clk_prepare_enable(vp) failed\n"); + goto fail_mixer; + } + ret = clk_prepare_enable(res->sclk_mixer); + if (ret < 0) { + dev_err(mdev->dev, "clk_prepare_enable(sclk_mixer) failed\n"); + goto fail_vp; + } /* apply default configuration */ mxr_reg_reset(mdev); mxr_dbg(mdev, "resume - finished\n"); mutex_unlock(&mdev->mutex); return 0; + +fail_vp: + clk_disable_unprepare(res->vp); +fail_mixer: + clk_disable_unprepare(res->mixer); +fail: + mutex_unlock(&mdev->mutex); + dev_err(mdev->dev, "resume failed\n"); + return ret; } static int mxr_runtime_suspend(struct device *dev) @@ -369,9 +391,9 @@ static int mxr_runtime_suspend(struct device *dev) mxr_dbg(mdev, "suspend - start\n"); mutex_lock(&mdev->mutex); /* turn clocks off */ - clk_disable(res->sclk_mixer); - clk_disable(res->vp); - clk_disable(res->mixer); + clk_disable_unprepare(res->sclk_mixer); + clk_disable_unprepare(res->vp); + clk_disable_unprepare(res->mixer); mutex_unlock(&mdev->mutex); mxr_dbg(mdev, "suspend - finished\n"); return 0; diff --git a/drivers/media/platform/s5p-tv/mixer_video.c b/drivers/media/platform/s5p-tv/mixer_video.c index 81b97db111d8..c5059ba0d733 100644 --- a/drivers/media/platform/s5p-tv/mixer_video.c +++ b/drivers/media/platform/s5p-tv/mixer_video.c @@ -948,7 +948,7 @@ static int start_streaming(struct vb2_queue *vq, unsigned int count) if (count == 0) { mxr_dbg(mdev, "no output buffers queued\n"); - return -EINVAL; + return -ENOBUFS; } /* block any changes in output configuration */ diff --git a/drivers/media/platform/s5p-tv/sdo_drv.c b/drivers/media/platform/s5p-tv/sdo_drv.c index 0afa90f0f6ab..5a7c3796f22e 100644 --- a/drivers/media/platform/s5p-tv/sdo_drv.c +++ b/drivers/media/platform/s5p-tv/sdo_drv.c @@ -55,6 +55,8 @@ struct sdo_device { struct clk *dacphy; /** clock for control of VPLL */ struct clk *fout_vpll; + /** vpll rate before sdo stream was on */ + unsigned long vpll_rate; /** regulator for SDO IP power */ struct regulator *vdac; /** regulator for SDO plug detection */ @@ -193,17 +195,33 @@ static int sdo_s_power(struct v4l2_subdev *sd, int on) static int sdo_streamon(struct sdo_device *sdev) { + int ret; + /* set proper clock for Timing Generator */ - clk_set_rate(sdev->fout_vpll, 54000000); + sdev->vpll_rate = clk_get_rate(sdev->fout_vpll); + ret = clk_set_rate(sdev->fout_vpll, 54000000); + if (ret < 0) { + dev_err(sdev->dev, "Failed to set vpll rate\n"); + return ret; + } dev_info(sdev->dev, "fout_vpll.rate = %lu\n", clk_get_rate(sdev->fout_vpll)); /* enable clock in SDO */ sdo_write_mask(sdev, SDO_CLKCON, ~0, SDO_TVOUT_CLOCK_ON); - clk_enable(sdev->dacphy); + ret = clk_prepare_enable(sdev->dacphy); + if (ret < 0) { + dev_err(sdev->dev, "clk_prepare_enable(dacphy) failed\n"); + goto fail; + } /* enable DAC */ sdo_write_mask(sdev, SDO_DAC, ~0, SDO_POWER_ON_DAC); sdo_reg_debug(sdev); return 0; + +fail: + sdo_write_mask(sdev, SDO_CLKCON, 0, SDO_TVOUT_CLOCK_ON); + clk_set_rate(sdev->fout_vpll, sdev->vpll_rate); + return ret; } static int sdo_streamoff(struct sdo_device *sdev) @@ -211,7 +229,7 @@ static int sdo_streamoff(struct sdo_device *sdev) int tries; sdo_write_mask(sdev, SDO_DAC, 0, SDO_POWER_ON_DAC); - clk_disable(sdev->dacphy); + clk_disable_unprepare(sdev->dacphy); sdo_write_mask(sdev, SDO_CLKCON, 0, SDO_TVOUT_CLOCK_ON); for (tries = 100; tries; --tries) { if (sdo_read(sdev, SDO_CLKCON) & SDO_TVOUT_CLOCK_READY) @@ -220,6 +238,7 @@ static int sdo_streamoff(struct sdo_device *sdev) } if (tries == 0) dev_err(sdev->dev, "failed to stop streaming\n"); + clk_set_rate(sdev->fout_vpll, sdev->vpll_rate); return tries ? 0 : -EIO; } @@ -254,7 +273,7 @@ static int sdo_runtime_suspend(struct device *dev) dev_info(dev, "suspend\n"); regulator_disable(sdev->vdet); regulator_disable(sdev->vdac); - clk_disable(sdev->sclk_dac); + clk_disable_unprepare(sdev->sclk_dac); return 0; } @@ -266,7 +285,7 @@ static int sdo_runtime_resume(struct device *dev) dev_info(dev, "resume\n"); - ret = clk_enable(sdev->sclk_dac); + ret = clk_prepare_enable(sdev->sclk_dac); if (ret < 0) return ret; @@ -299,7 +318,7 @@ static int sdo_runtime_resume(struct device *dev) vdac_r_dis: regulator_disable(sdev->vdac); dac_clk_dis: - clk_disable(sdev->sclk_dac); + clk_disable_unprepare(sdev->sclk_dac); return ret; } @@ -405,7 +424,11 @@ static int sdo_probe(struct platform_device *pdev) } /* enable gate for dac clock, because mixer uses it */ - clk_enable(sdev->dac); + ret = clk_prepare_enable(sdev->dac); + if (ret < 0) { + dev_err(dev, "clk_prepare_enable(dac) failed\n"); + goto fail_fout_vpll; + } /* configure power management */ pm_runtime_enable(dev); @@ -444,7 +467,7 @@ static int sdo_remove(struct platform_device *pdev) struct sdo_device *sdev = sd_to_sdev(sd); pm_runtime_disable(&pdev->dev); - clk_disable(sdev->dac); + clk_disable_unprepare(sdev->dac); clk_put(sdev->fout_vpll); clk_put(sdev->dacphy); clk_put(sdev->dac); diff --git a/drivers/media/platform/sh_vou.c b/drivers/media/platform/sh_vou.c index 4f30341dc2ab..e5f1d4c14f2c 100644 --- a/drivers/media/platform/sh_vou.c +++ b/drivers/media/platform/sh_vou.c @@ -286,7 +286,7 @@ static int sh_vou_buf_prepare(struct videobuf_queue *vq, vb->size = vb->height * bytes_per_line; if (vb->baddr && vb->bsize < vb->size) { /* User buffer too small */ - dev_warn(vq->dev, "User buffer too small: [%u] @ %lx\n", + dev_warn(vq->dev, "User buffer too small: [%zu] @ %lx\n", vb->bsize, vb->baddr); return -EINVAL; } @@ -302,9 +302,10 @@ static int sh_vou_buf_prepare(struct videobuf_queue *vq, } dev_dbg(vou_dev->v4l2_dev.dev, - "%s(): fmt #%d, %u bytes per line, phys 0x%x, type %d, state %d\n", + "%s(): fmt #%d, %u bytes per line, phys %pad, type %d, state %d\n", __func__, vou_dev->pix_idx, bytes_per_line, - videobuf_to_dma_contig(vb), vb->memory, vb->state); + ({ dma_addr_t addr = videobuf_to_dma_contig(vb); &addr; }), + vb->memory, vb->state); return 0; } @@ -442,7 +443,7 @@ static void sh_vou_configure_geometry(struct sh_vou_device *vou_dev, int pix_idx, int w_idx, int h_idx) { struct sh_vou_fmt *fmt = vou_fmt + pix_idx; - unsigned int black_left, black_top, width_max, height_max, + unsigned int black_left, black_top, width_max, frame_in_height, frame_out_height, frame_out_top; struct v4l2_rect *rect = &vou_dev->rect; struct v4l2_pix_format *pix = &vou_dev->pix; @@ -450,10 +451,10 @@ static void sh_vou_configure_geometry(struct sh_vou_device *vou_dev, if (vou_dev->std & V4L2_STD_525_60) { width_max = 858; - height_max = 262; + /* height_max = 262; */ } else { width_max = 864; - height_max = 312; + /* height_max = 312; */ } frame_in_height = pix->height / 2; @@ -1052,7 +1053,6 @@ static irqreturn_t sh_vou_isr(int irq, void *dev_id) static unsigned long j; struct videobuf_buffer *vb; static int cnt; - static int side; u32 irq_status = sh_vou_reg_a_read(vou_dev, VOUIR), masked; u32 vou_status = sh_vou_reg_a_read(vou_dev, VOUSTR); @@ -1080,7 +1080,7 @@ static irqreturn_t sh_vou_isr(int irq, void *dev_id) irq_status, masked, vou_status, cnt); cnt++; - side = vou_status & 0x10000; + /* side = vou_status & 0x10000; */ /* Clear only set interrupts */ sh_vou_reg_a_write(vou_dev, VOUIR, masked); diff --git a/drivers/media/platform/soc_camera/atmel-isi.c b/drivers/media/platform/soc_camera/atmel-isi.c index 104485632501..4835173d7f80 100644 --- a/drivers/media/platform/soc_camera/atmel-isi.c +++ b/drivers/media/platform/soc_camera/atmel-isi.c @@ -34,13 +34,6 @@ #define MIN_FRAME_RATE 15 #define FRAME_INTERVAL_MILLI_SEC (1000 / MIN_FRAME_RATE) -/* ISI states */ -enum { - ISI_STATE_IDLE = 0, - ISI_STATE_READY, - ISI_STATE_WAIT_SOF, -}; - /* Frame buffer descriptor */ struct fbd { /* Physical address of the frame buffer */ @@ -75,11 +68,6 @@ struct atmel_isi { void __iomem *regs; int sequence; - /* State of the ISI module in capturing mode */ - int state; - - /* Wait queue for waiting for SOF */ - wait_queue_head_t vsync_wq; struct vb2_alloc_ctx *alloc_ctx; @@ -124,16 +112,16 @@ static int configure_geometry(struct atmel_isi *isi, u32 width, case V4L2_MBUS_FMT_Y8_1X8: cr = ISI_CFG2_GRAYSCALE; break; - case V4L2_MBUS_FMT_UYVY8_2X8: + case V4L2_MBUS_FMT_VYUY8_2X8: cr = ISI_CFG2_YCC_SWAP_MODE_3; break; - case V4L2_MBUS_FMT_VYUY8_2X8: + case V4L2_MBUS_FMT_UYVY8_2X8: cr = ISI_CFG2_YCC_SWAP_MODE_2; break; - case V4L2_MBUS_FMT_YUYV8_2X8: + case V4L2_MBUS_FMT_YVYU8_2X8: cr = ISI_CFG2_YCC_SWAP_MODE_1; break; - case V4L2_MBUS_FMT_YVYU8_2X8: + case V4L2_MBUS_FMT_YUYV8_2X8: cr = ISI_CFG2_YCC_SWAP_DEFAULT; break; /* RGB, TODO */ @@ -144,6 +132,8 @@ static int configure_geometry(struct atmel_isi *isi, u32 width, isi_writel(isi, ISI_CTRL, ISI_CTRL_DIS); cfg2 = isi_readl(isi, ISI_CFG2); + /* Set YCC swap mode */ + cfg2 &= ~ISI_CFG2_YCC_SWAP_MODE_MASK; cfg2 |= cr; /* Set width */ cfg2 &= ~(ISI_CFG2_IM_HSIZE_MASK); @@ -207,12 +197,6 @@ static irqreturn_t isi_interrupt(int irq, void *dev_id) isi_writel(isi, ISI_INTDIS, ISI_CTRL_DIS); ret = IRQ_HANDLED; } else { - if ((pending & ISI_SR_VSYNC) && - (isi->state == ISI_STATE_IDLE)) { - isi->state = ISI_STATE_READY; - wake_up_interruptible(&isi->vsync_wq); - ret = IRQ_HANDLED; - } if (likely(pending & ISI_SR_CXFR_DONE)) ret = atmel_isi_handle_streaming(isi); } @@ -259,16 +243,6 @@ static int queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt, struct soc_camera_host *ici = to_soc_camera_host(icd->parent); struct atmel_isi *isi = ici->priv; unsigned long size; - int ret; - - /* Reset ISI */ - ret = atmel_isi_wait_status(isi, WAIT_ISI_RESET); - if (ret < 0) { - dev_err(icd->parent, "Reset ISI timed out\n"); - return ret; - } - /* Disable all interrupts */ - isi_writel(isi, ISI_INTDIS, ~0UL); size = icd->sizeimage; @@ -374,6 +348,7 @@ static void start_dma(struct atmel_isi *isi, struct frame_buffer *buffer) isi_writel(isi, ISI_DMA_C_CTRL, ISI_DMA_CTRL_FETCH | ISI_DMA_CTRL_DONE); isi_writel(isi, ISI_DMA_CHER, ISI_DMA_CHSR_C_CH); + cfg1 &= ~ISI_CFG1_FRATE_DIV_MASK; /* Enable linked list */ cfg1 |= isi->pdata->frate | ISI_CFG1_DISCR; @@ -407,43 +382,27 @@ static int start_streaming(struct vb2_queue *vq, unsigned int count) struct soc_camera_device *icd = soc_camera_from_vb2q(vq); struct soc_camera_host *ici = to_soc_camera_host(icd->parent); struct atmel_isi *isi = ici->priv; - u32 sr = 0; int ret; - spin_lock_irq(&isi->lock); - isi->state = ISI_STATE_IDLE; - /* Clear any pending SOF interrupt */ - sr = isi_readl(isi, ISI_STATUS); - /* Enable VSYNC interrupt for SOF */ - isi_writel(isi, ISI_INTEN, ISI_SR_VSYNC); - isi_writel(isi, ISI_CTRL, ISI_CTRL_EN); - spin_unlock_irq(&isi->lock); - - dev_dbg(icd->parent, "Waiting for SOF\n"); - ret = wait_event_interruptible(isi->vsync_wq, - isi->state != ISI_STATE_IDLE); - if (ret) - goto err; - - if (isi->state != ISI_STATE_READY) { - ret = -EIO; - goto err; + /* Reset ISI */ + ret = atmel_isi_wait_status(isi, WAIT_ISI_RESET); + if (ret < 0) { + dev_err(icd->parent, "Reset ISI timed out\n"); + return ret; } + /* Disable all interrupts */ + isi_writel(isi, ISI_INTDIS, ~0UL); spin_lock_irq(&isi->lock); - isi->state = ISI_STATE_WAIT_SOF; - isi_writel(isi, ISI_INTDIS, ISI_SR_VSYNC); + /* Clear any pending interrupt */ + sr = isi_readl(isi, ISI_STATUS); + if (count) start_dma(isi, isi->active); spin_unlock_irq(&isi->lock); return 0; -err: - isi->active = NULL; - isi->sequence = 0; - INIT_LIST_HEAD(&isi->video_buffer_list); - return ret; } /* abort streaming and wait for last buffer */ @@ -765,14 +724,16 @@ static int isi_camera_clock_start(struct soc_camera_host *ici) struct atmel_isi *isi = ici->priv; int ret; - ret = clk_enable(isi->pclk); + ret = clk_prepare_enable(isi->pclk); if (ret) return ret; - ret = clk_enable(isi->mck); - if (ret) { - clk_disable(isi->pclk); - return ret; + if (!IS_ERR(isi->mck)) { + ret = clk_prepare_enable(isi->mck); + if (ret) { + clk_disable_unprepare(isi->pclk); + return ret; + } } return 0; @@ -783,8 +744,9 @@ static void isi_camera_clock_stop(struct soc_camera_host *ici) { struct atmel_isi *isi = ici->priv; - clk_disable(isi->mck); - clk_disable(isi->pclk); + if (!IS_ERR(isi->mck)) + clk_disable_unprepare(isi->mck); + clk_disable_unprepare(isi->pclk); } static unsigned int isi_camera_poll(struct file *file, poll_table *pt) @@ -906,7 +868,6 @@ static int atmel_isi_remove(struct platform_device *pdev) struct atmel_isi *isi = container_of(soc_host, struct atmel_isi, soc_host); - free_irq(isi->irq, isi); soc_camera_host_unregister(soc_host); vb2_dma_contig_cleanup_ctx(isi->alloc_ctx); dma_free_coherent(&pdev->dev, @@ -914,13 +875,6 @@ static int atmel_isi_remove(struct platform_device *pdev) isi->p_fb_descriptors, isi->fb_descriptors_phys); - iounmap(isi->regs); - clk_unprepare(isi->mck); - clk_put(isi->mck); - clk_unprepare(isi->pclk); - clk_put(isi->pclk); - kfree(isi); - return 0; } @@ -928,7 +882,6 @@ static int atmel_isi_probe(struct platform_device *pdev) { unsigned int irq; struct atmel_isi *isi; - struct clk *pclk; struct resource *regs; int ret, i; struct device *dev = &pdev->dev; @@ -936,64 +889,50 @@ static int atmel_isi_probe(struct platform_device *pdev) struct isi_platform_data *pdata; pdata = dev->platform_data; - if (!pdata || !pdata->data_width_flags || !pdata->mck_hz) { + if (!pdata || !pdata->data_width_flags) { dev_err(&pdev->dev, "No config available for Atmel ISI\n"); return -EINVAL; } - regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!regs) - return -ENXIO; - - pclk = clk_get(&pdev->dev, "isi_clk"); - if (IS_ERR(pclk)) - return PTR_ERR(pclk); - - ret = clk_prepare(pclk); - if (ret) - goto err_clk_prepare_pclk; - - isi = kzalloc(sizeof(struct atmel_isi), GFP_KERNEL); + isi = devm_kzalloc(&pdev->dev, sizeof(struct atmel_isi), GFP_KERNEL); if (!isi) { - ret = -ENOMEM; dev_err(&pdev->dev, "Can't allocate interface!\n"); - goto err_alloc_isi; + return -ENOMEM; } - isi->pclk = pclk; + isi->pclk = devm_clk_get(&pdev->dev, "isi_clk"); + if (IS_ERR(isi->pclk)) + return PTR_ERR(isi->pclk); + isi->pdata = pdata; isi->active = NULL; spin_lock_init(&isi->lock); - init_waitqueue_head(&isi->vsync_wq); INIT_LIST_HEAD(&isi->video_buffer_list); INIT_LIST_HEAD(&isi->dma_desc_head); - /* Get ISI_MCK, provided by programmable clock or external clock */ - isi->mck = clk_get(dev, "isi_mck"); - if (IS_ERR(isi->mck)) { - dev_err(dev, "Failed to get isi_mck\n"); - ret = PTR_ERR(isi->mck); - goto err_clk_get; + /* ISI_MCK is the sensor master clock. It should be handled by the + * sensor driver directly, as the ISI has no use for that clock. Make + * the clock optional here while platforms transition to the correct + * model. + */ + isi->mck = devm_clk_get(dev, "isi_mck"); + if (!IS_ERR(isi->mck)) { + /* Set ISI_MCK's frequency, it should be faster than pixel + * clock. + */ + ret = clk_set_rate(isi->mck, pdata->mck_hz); + if (ret < 0) + return ret; } - ret = clk_prepare(isi->mck); - if (ret) - goto err_clk_prepare_mck; - - /* Set ISI_MCK's frequency, it should be faster than pixel clock */ - ret = clk_set_rate(isi->mck, pdata->mck_hz); - if (ret < 0) - goto err_set_mck_rate; - isi->p_fb_descriptors = dma_alloc_coherent(&pdev->dev, sizeof(struct fbd) * MAX_BUFFER_NUM, &isi->fb_descriptors_phys, GFP_KERNEL); if (!isi->p_fb_descriptors) { - ret = -ENOMEM; dev_err(&pdev->dev, "Can't allocate descriptors!\n"); - goto err_alloc_descriptors; + return -ENOMEM; } for (i = 0; i < MAX_BUFFER_NUM; i++) { @@ -1009,9 +948,10 @@ static int atmel_isi_probe(struct platform_device *pdev) goto err_alloc_ctx; } - isi->regs = ioremap(regs->start, resource_size(regs)); - if (!isi->regs) { - ret = -ENOMEM; + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + isi->regs = devm_ioremap_resource(&pdev->dev, regs); + if (IS_ERR(isi->regs)) { + ret = PTR_ERR(isi->regs); goto err_ioremap; } @@ -1028,7 +968,7 @@ static int atmel_isi_probe(struct platform_device *pdev) goto err_req_irq; } - ret = request_irq(irq, isi_interrupt, 0, "isi", isi); + ret = devm_request_irq(&pdev->dev, irq, isi_interrupt, 0, "isi", isi); if (ret) { dev_err(&pdev->dev, "Unable to request irq %d\n", irq); goto err_req_irq; @@ -1050,9 +990,7 @@ static int atmel_isi_probe(struct platform_device *pdev) return 0; err_register_soc_camera_host: - free_irq(isi->irq, isi); err_req_irq: - iounmap(isi->regs); err_ioremap: vb2_dma_contig_cleanup_ctx(isi->alloc_ctx); err_alloc_ctx: @@ -1060,17 +998,6 @@ err_alloc_ctx: sizeof(struct fbd) * MAX_BUFFER_NUM, isi->p_fb_descriptors, isi->fb_descriptors_phys); -err_alloc_descriptors: -err_set_mck_rate: - clk_unprepare(isi->mck); -err_clk_prepare_mck: - clk_put(isi->mck); -err_clk_get: - kfree(isi); -err_alloc_isi: - clk_unprepare(pclk); -err_clk_prepare_pclk: - clk_put(pclk); return ret; } diff --git a/drivers/media/platform/soc_camera/mx2_camera.c b/drivers/media/platform/soc_camera/mx2_camera.c index 45a0276be4e5..d73abca9c6ee 100644 --- a/drivers/media/platform/soc_camera/mx2_camera.c +++ b/drivers/media/platform/soc_camera/mx2_camera.c @@ -659,7 +659,7 @@ static int mx2_start_streaming(struct vb2_queue *q, unsigned int count) unsigned long flags; if (count < 2) - return -EINVAL; + return -ENOBUFS; spin_lock_irqsave(&pcdev->lock, flags); diff --git a/drivers/media/platform/soc_camera/rcar_vin.c b/drivers/media/platform/soc_camera/rcar_vin.c index 6866bb4fbebc..3b1c05a72d00 100644 --- a/drivers/media/platform/soc_camera/rcar_vin.c +++ b/drivers/media/platform/soc_camera/rcar_vin.c @@ -106,7 +106,7 @@ #define VIN_MAX_HEIGHT 2048 enum chip_id { - RCAR_H2, + RCAR_GEN2, RCAR_H1, RCAR_M1, RCAR_E1, @@ -302,7 +302,7 @@ static int rcar_vin_setup(struct rcar_vin_priv *priv) dmr = 0; break; case V4L2_PIX_FMT_RGB32: - if (priv->chip == RCAR_H2 || priv->chip == RCAR_H1 || + if (priv->chip == RCAR_GEN2 || priv->chip == RCAR_H1 || priv->chip == RCAR_E1) { dmr = VNDMR_EXRGB; break; @@ -1384,7 +1384,8 @@ static struct soc_camera_host_ops rcar_vin_host_ops = { }; static struct platform_device_id rcar_vin_id_table[] = { - { "r8a7790-vin", RCAR_H2 }, + { "r8a7791-vin", RCAR_GEN2 }, + { "r8a7790-vin", RCAR_GEN2 }, { "r8a7779-vin", RCAR_H1 }, { "r8a7778-vin", RCAR_M1 }, { "uPD35004-vin", RCAR_E1 }, diff --git a/drivers/media/platform/soc_camera/soc_scale_crop.c b/drivers/media/platform/soc_camera/soc_scale_crop.c index cbd3a34f4f3f..8e74fb7f2a07 100644 --- a/drivers/media/platform/soc_camera/soc_scale_crop.c +++ b/drivers/media/platform/soc_camera/soc_scale_crop.c @@ -141,8 +141,8 @@ int soc_camera_client_s_crop(struct v4l2_subdev *sd, * Popular special case - some cameras can only handle fixed sizes like * QVGA, VGA,... Take care to avoid infinite loop. */ - width = max(cam_rect->width, 2); - height = max(cam_rect->height, 2); + width = max_t(unsigned int, cam_rect->width, 2); + height = max_t(unsigned int, cam_rect->height, 2); /* * Loop as long as sensor is not covering the requested rectangle and diff --git a/drivers/media/platform/ti-vpe/Makefile b/drivers/media/platform/ti-vpe/Makefile index cbf0a806ba1d..be680f839e77 100644 --- a/drivers/media/platform/ti-vpe/Makefile +++ b/drivers/media/platform/ti-vpe/Makefile @@ -1,5 +1,5 @@ obj-$(CONFIG_VIDEO_TI_VPE) += ti-vpe.o -ti-vpe-y := vpe.o vpdma.o +ti-vpe-y := vpe.o sc.o csc.o vpdma.o ccflags-$(CONFIG_VIDEO_TI_VPE_DEBUG) += -DDEBUG diff --git a/drivers/media/platform/ti-vpe/csc.c b/drivers/media/platform/ti-vpe/csc.c new file mode 100644 index 000000000000..acfea500710e --- /dev/null +++ b/drivers/media/platform/ti-vpe/csc.c @@ -0,0 +1,196 @@ +/* + * Color space converter library + * + * Copyright (c) 2013 Texas Instruments Inc. + * + * David Griego, <dagriego@biglakesoftware.com> + * Dale Farnsworth, <dale@farnsworth.org> + * Archit Taneja, <archit@ti.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. + */ + +#include <linux/err.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/videodev2.h> + +#include "csc.h" + +/* + * 16 coefficients in the order: + * a0, b0, c0, a1, b1, c1, a2, b2, c2, d0, d1, d2 + * (we may need to pass non-default values from user space later on, we might + * need to make the coefficient struct more easy to populate) + */ +struct colorspace_coeffs { + u16 sd[12]; + u16 hd[12]; +}; + +/* VIDEO_RANGE: limited range, GRAPHICS_RANGE: full range */ +#define CSC_COEFFS_VIDEO_RANGE_Y2R 0 +#define CSC_COEFFS_GRAPHICS_RANGE_Y2R 1 +#define CSC_COEFFS_VIDEO_RANGE_R2Y 2 +#define CSC_COEFFS_GRAPHICS_RANGE_R2Y 3 + +/* default colorspace coefficients */ +static struct colorspace_coeffs colorspace_coeffs[4] = { + [CSC_COEFFS_VIDEO_RANGE_Y2R] = { + { + /* SDTV */ + 0x0400, 0x0000, 0x057D, 0x0400, 0x1EA7, 0x1D35, + 0x0400, 0x06EF, 0x1FFE, 0x0D40, 0x0210, 0x0C88, + }, + { + /* HDTV */ + 0x0400, 0x0000, 0x0629, 0x0400, 0x1F45, 0x1E2B, + 0x0400, 0x0742, 0x0000, 0x0CEC, 0x0148, 0x0C60, + }, + }, + [CSC_COEFFS_GRAPHICS_RANGE_Y2R] = { + { + /* SDTV */ + 0x04A8, 0x1FFE, 0x0662, 0x04A8, 0x1E6F, 0x1CBF, + 0x04A8, 0x0812, 0x1FFF, 0x0C84, 0x0220, 0x0BAC, + }, + { + /* HDTV */ + 0x04A8, 0x0000, 0x072C, 0x04A8, 0x1F26, 0x1DDE, + 0x04A8, 0x0873, 0x0000, 0x0C20, 0x0134, 0x0B7C, + }, + }, + [CSC_COEFFS_VIDEO_RANGE_R2Y] = { + { + /* SDTV */ + 0x0132, 0x0259, 0x0075, 0x1F50, 0x1EA5, 0x020B, + 0x020B, 0x1E4A, 0x1FAB, 0x0000, 0x0200, 0x0200, + }, + { + /* HDTV */ + 0x00DA, 0x02DC, 0x004A, 0x1F88, 0x1E6C, 0x020C, + 0x020C, 0x1E24, 0x1FD0, 0x0000, 0x0200, 0x0200, + }, + }, + [CSC_COEFFS_GRAPHICS_RANGE_R2Y] = { + { + /* SDTV */ + 0x0107, 0x0204, 0x0064, 0x1F68, 0x1ED6, 0x01C2, + 0x01C2, 0x1E87, 0x1FB7, 0x0040, 0x0200, 0x0200, + }, + { + /* HDTV */ + 0x04A8, 0x0000, 0x072C, 0x04A8, 0x1F26, 0x1DDE, + 0x04A8, 0x0873, 0x0000, 0x0C20, 0x0134, 0x0B7C, + }, + }, +}; + +void csc_dump_regs(struct csc_data *csc) +{ + struct device *dev = &csc->pdev->dev; + + u32 read_reg(struct csc_data *csc, int offset) + { + return ioread32(csc->base + offset); + } + +#define DUMPREG(r) dev_dbg(dev, "%-35s %08x\n", #r, read_reg(csc, CSC_##r)) + + DUMPREG(CSC00); + DUMPREG(CSC01); + DUMPREG(CSC02); + DUMPREG(CSC03); + DUMPREG(CSC04); + DUMPREG(CSC05); + +#undef DUMPREG +} + +void csc_set_coeff_bypass(struct csc_data *csc, u32 *csc_reg5) +{ + *csc_reg5 |= CSC_BYPASS; +} + +/* + * set the color space converter coefficient shadow register values + */ +void csc_set_coeff(struct csc_data *csc, u32 *csc_reg0, + enum v4l2_colorspace src_colorspace, + enum v4l2_colorspace dst_colorspace) +{ + u32 *csc_reg5 = csc_reg0 + 5; + u32 *shadow_csc = csc_reg0; + struct colorspace_coeffs *sd_hd_coeffs; + u16 *coeff, *end_coeff; + enum v4l2_colorspace yuv_colorspace; + int sel = 0; + + /* + * support only graphics data range(full range) for now, a control ioctl + * would be nice here + */ + /* Y2R */ + if (dst_colorspace == V4L2_COLORSPACE_SRGB && + (src_colorspace == V4L2_COLORSPACE_SMPTE170M || + src_colorspace == V4L2_COLORSPACE_REC709)) { + /* Y2R */ + sel = 1; + yuv_colorspace = src_colorspace; + } else if ((dst_colorspace == V4L2_COLORSPACE_SMPTE170M || + dst_colorspace == V4L2_COLORSPACE_REC709) && + src_colorspace == V4L2_COLORSPACE_SRGB) { + /* R2Y */ + sel = 3; + yuv_colorspace = dst_colorspace; + } else { + *csc_reg5 |= CSC_BYPASS; + return; + } + + sd_hd_coeffs = &colorspace_coeffs[sel]; + + /* select between SD or HD coefficients */ + if (yuv_colorspace == V4L2_COLORSPACE_SMPTE170M) + coeff = sd_hd_coeffs->sd; + else + coeff = sd_hd_coeffs->hd; + + end_coeff = coeff + 12; + + for (; coeff < end_coeff; coeff += 2) + *shadow_csc++ = (*(coeff + 1) << 16) | *coeff; +} + +struct csc_data *csc_create(struct platform_device *pdev) +{ + struct csc_data *csc; + + dev_dbg(&pdev->dev, "csc_create\n"); + + csc = devm_kzalloc(&pdev->dev, sizeof(*csc), GFP_KERNEL); + if (!csc) { + dev_err(&pdev->dev, "couldn't alloc csc_data\n"); + return ERR_PTR(-ENOMEM); + } + + csc->pdev = pdev; + + csc->res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "vpe_csc"); + if (csc->res == NULL) { + dev_err(&pdev->dev, "missing platform resources data\n"); + return ERR_PTR(-ENODEV); + } + + csc->base = devm_ioremap_resource(&pdev->dev, csc->res); + if (!csc->base) { + dev_err(&pdev->dev, "failed to ioremap\n"); + return ERR_PTR(-ENOMEM); + } + + return csc; +} diff --git a/drivers/media/platform/ti-vpe/csc.h b/drivers/media/platform/ti-vpe/csc.h new file mode 100644 index 000000000000..1ad2b6dad561 --- /dev/null +++ b/drivers/media/platform/ti-vpe/csc.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2013 Texas Instruments Inc. + * + * David Griego, <dagriego@biglakesoftware.com> + * Dale Farnsworth, <dale@farnsworth.org> + * Archit Taneja, <archit@ti.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 TI_CSC_H +#define TI_CSC_H + +/* VPE color space converter regs */ +#define CSC_CSC00 0x00 +#define CSC_A0_MASK 0x1fff +#define CSC_A0_SHIFT 0 +#define CSC_B0_MASK 0x1fff +#define CSC_B0_SHIFT 16 + +#define CSC_CSC01 0x04 +#define CSC_C0_MASK 0x1fff +#define CSC_C0_SHIFT 0 +#define CSC_A1_MASK 0x1fff +#define CSC_A1_SHIFT 16 + +#define CSC_CSC02 0x08 +#define CSC_B1_MASK 0x1fff +#define CSC_B1_SHIFT 0 +#define CSC_C1_MASK 0x1fff +#define CSC_C1_SHIFT 16 + +#define CSC_CSC03 0x0c +#define CSC_A2_MASK 0x1fff +#define CSC_A2_SHIFT 0 +#define CSC_B2_MASK 0x1fff +#define CSC_B2_SHIFT 16 + +#define CSC_CSC04 0x10 +#define CSC_C2_MASK 0x1fff +#define CSC_C2_SHIFT 0 +#define CSC_D0_MASK 0x0fff +#define CSC_D0_SHIFT 16 + +#define CSC_CSC05 0x14 +#define CSC_D1_MASK 0x0fff +#define CSC_D1_SHIFT 0 +#define CSC_D2_MASK 0x0fff +#define CSC_D2_SHIFT 16 + +#define CSC_BYPASS (1 << 28) + +struct csc_data { + void __iomem *base; + struct resource *res; + + struct platform_device *pdev; +}; + +void csc_dump_regs(struct csc_data *csc); +void csc_set_coeff_bypass(struct csc_data *csc, u32 *csc_reg5); +void csc_set_coeff(struct csc_data *csc, u32 *csc_reg0, + enum v4l2_colorspace src_colorspace, + enum v4l2_colorspace dst_colorspace); +struct csc_data *csc_create(struct platform_device *pdev); + +#endif diff --git a/drivers/media/platform/ti-vpe/sc.c b/drivers/media/platform/ti-vpe/sc.c new file mode 100644 index 000000000000..93f0af546b76 --- /dev/null +++ b/drivers/media/platform/ti-vpe/sc.c @@ -0,0 +1,311 @@ +/* + * Scaler library + * + * Copyright (c) 2013 Texas Instruments Inc. + * + * David Griego, <dagriego@biglakesoftware.com> + * Dale Farnsworth, <dale@farnsworth.org> + * Archit Taneja, <archit@ti.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. + */ + +#include <linux/err.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include "sc.h" +#include "sc_coeff.h" + +void sc_dump_regs(struct sc_data *sc) +{ + struct device *dev = &sc->pdev->dev; + + u32 read_reg(struct sc_data *sc, int offset) + { + return ioread32(sc->base + offset); + } + +#define DUMPREG(r) dev_dbg(dev, "%-35s %08x\n", #r, read_reg(sc, CFG_##r)) + + DUMPREG(SC0); + DUMPREG(SC1); + DUMPREG(SC2); + DUMPREG(SC3); + DUMPREG(SC4); + DUMPREG(SC5); + DUMPREG(SC6); + DUMPREG(SC8); + DUMPREG(SC9); + DUMPREG(SC10); + DUMPREG(SC11); + DUMPREG(SC12); + DUMPREG(SC13); + DUMPREG(SC17); + DUMPREG(SC18); + DUMPREG(SC19); + DUMPREG(SC20); + DUMPREG(SC21); + DUMPREG(SC22); + DUMPREG(SC23); + DUMPREG(SC24); + DUMPREG(SC25); + +#undef DUMPREG +} + +/* + * set the horizontal scaler coefficients according to the ratio of output to + * input widths, after accounting for up to two levels of decimation + */ +void sc_set_hs_coeffs(struct sc_data *sc, void *addr, unsigned int src_w, + unsigned int dst_w) +{ + int sixteenths; + int idx; + int i, j; + u16 *coeff_h = addr; + const u16 *cp; + + if (dst_w > src_w) { + idx = HS_UP_SCALE; + } else { + if ((dst_w << 1) < src_w) + dst_w <<= 1; /* first level decimation */ + if ((dst_w << 1) < src_w) + dst_w <<= 1; /* second level decimation */ + + if (dst_w == src_w) { + idx = HS_LE_16_16_SCALE; + } else { + sixteenths = (dst_w << 4) / src_w; + if (sixteenths < 8) + sixteenths = 8; + idx = HS_LT_9_16_SCALE + sixteenths - 8; + } + } + + if (idx == sc->hs_index) + return; + + cp = scaler_hs_coeffs[idx]; + + for (i = 0; i < SC_NUM_PHASES * 2; i++) { + for (j = 0; j < SC_H_NUM_TAPS; j++) + *coeff_h++ = *cp++; + /* + * for each phase, the scaler expects space for 8 coefficients + * in it's memory. For the horizontal scaler, we copy the first + * 7 coefficients and skip the last slot to move to the next + * row to hold coefficients for the next phase + */ + coeff_h += SC_NUM_TAPS_MEM_ALIGN - SC_H_NUM_TAPS; + } + + sc->hs_index = idx; + + sc->load_coeff_h = true; +} + +/* + * set the vertical scaler coefficients according to the ratio of output to + * input heights + */ +void sc_set_vs_coeffs(struct sc_data *sc, void *addr, unsigned int src_h, + unsigned int dst_h) +{ + int sixteenths; + int idx; + int i, j; + u16 *coeff_v = addr; + const u16 *cp; + + if (dst_h > src_h) { + idx = VS_UP_SCALE; + } else if (dst_h == src_h) { + idx = VS_1_TO_1_SCALE; + } else { + sixteenths = (dst_h << 4) / src_h; + if (sixteenths < 8) + sixteenths = 8; + idx = VS_LT_9_16_SCALE + sixteenths - 8; + } + + if (idx == sc->vs_index) + return; + + cp = scaler_vs_coeffs[idx]; + + for (i = 0; i < SC_NUM_PHASES * 2; i++) { + for (j = 0; j < SC_V_NUM_TAPS; j++) + *coeff_v++ = *cp++; + /* + * for the vertical scaler, we copy the first 5 coefficients and + * skip the last 3 slots to move to the next row to hold + * coefficients for the next phase + */ + coeff_v += SC_NUM_TAPS_MEM_ALIGN - SC_V_NUM_TAPS; + } + + sc->vs_index = idx; + sc->load_coeff_v = true; +} + +void sc_config_scaler(struct sc_data *sc, u32 *sc_reg0, u32 *sc_reg8, + u32 *sc_reg17, unsigned int src_w, unsigned int src_h, + unsigned int dst_w, unsigned int dst_h) +{ + struct device *dev = &sc->pdev->dev; + u32 val; + int dcm_x, dcm_shift; + bool use_rav; + unsigned long lltmp; + u32 lin_acc_inc, lin_acc_inc_u; + u32 col_acc_offset; + u16 factor = 0; + int row_acc_init_rav = 0, row_acc_init_rav_b = 0; + u32 row_acc_inc = 0, row_acc_offset = 0, row_acc_offset_b = 0; + /* + * location of SC register in payload memory with respect to the first + * register in the mmr address data block + */ + u32 *sc_reg9 = sc_reg8 + 1; + u32 *sc_reg12 = sc_reg8 + 4; + u32 *sc_reg13 = sc_reg8 + 5; + u32 *sc_reg24 = sc_reg17 + 7; + + val = sc_reg0[0]; + + /* clear all the features(they may get enabled elsewhere later) */ + val &= ~(CFG_SELFGEN_FID | CFG_TRIM | CFG_ENABLE_SIN2_VER_INTP | + CFG_INTERLACE_I | CFG_DCM_4X | CFG_DCM_2X | CFG_AUTO_HS | + CFG_ENABLE_EV | CFG_USE_RAV | CFG_INVT_FID | CFG_SC_BYPASS | + CFG_INTERLACE_O | CFG_Y_PK_EN | CFG_HP_BYPASS | CFG_LINEAR); + + if (src_w == dst_w && src_h == dst_h) { + val |= CFG_SC_BYPASS; + sc_reg0[0] = val; + return; + } + + /* we only support linear scaling for now */ + val |= CFG_LINEAR; + + /* configure horizontal scaler */ + + /* enable 2X or 4X decimation */ + dcm_x = src_w / dst_w; + if (dcm_x > 4) { + val |= CFG_DCM_4X; + dcm_shift = 2; + } else if (dcm_x > 2) { + val |= CFG_DCM_2X; + dcm_shift = 1; + } else { + dcm_shift = 0; + } + + lltmp = dst_w - 1; + lin_acc_inc = div64_u64(((u64)(src_w >> dcm_shift) - 1) << 24, lltmp); + lin_acc_inc_u = 0; + col_acc_offset = 0; + + dev_dbg(dev, "hs config: src_w = %d, dst_w = %d, decimation = %s, lin_acc_inc = %08x\n", + src_w, dst_w, dcm_shift == 2 ? "4x" : + (dcm_shift == 1 ? "2x" : "none"), lin_acc_inc); + + /* configure vertical scaler */ + + /* use RAV for vertical scaler if vertical downscaling is > 4x */ + if (dst_h < (src_h >> 2)) { + use_rav = true; + val |= CFG_USE_RAV; + } else { + use_rav = false; + } + + if (use_rav) { + /* use RAV */ + factor = (u16) ((dst_h << 10) / src_h); + + row_acc_init_rav = factor + ((1 + factor) >> 1); + if (row_acc_init_rav >= 1024) + row_acc_init_rav -= 1024; + + row_acc_init_rav_b = row_acc_init_rav + + (1 + (row_acc_init_rav >> 1)) - + (1024 >> 1); + + if (row_acc_init_rav_b < 0) { + row_acc_init_rav_b += row_acc_init_rav; + row_acc_init_rav *= 2; + } + + dev_dbg(dev, "vs config(RAV): src_h = %d, dst_h = %d, factor = %d, acc_init = %08x, acc_init_b = %08x\n", + src_h, dst_h, factor, row_acc_init_rav, + row_acc_init_rav_b); + } else { + /* use polyphase */ + row_acc_inc = ((src_h - 1) << 16) / (dst_h - 1); + row_acc_offset = 0; + row_acc_offset_b = 0; + + dev_dbg(dev, "vs config(POLY): src_h = %d, dst_h = %d,row_acc_inc = %08x\n", + src_h, dst_h, row_acc_inc); + } + + + sc_reg0[0] = val; + sc_reg0[1] = row_acc_inc; + sc_reg0[2] = row_acc_offset; + sc_reg0[3] = row_acc_offset_b; + + sc_reg0[4] = ((lin_acc_inc_u & CFG_LIN_ACC_INC_U_MASK) << + CFG_LIN_ACC_INC_U_SHIFT) | (dst_w << CFG_TAR_W_SHIFT) | + (dst_h << CFG_TAR_H_SHIFT); + + sc_reg0[5] = (src_w << CFG_SRC_W_SHIFT) | (src_h << CFG_SRC_H_SHIFT); + + sc_reg0[6] = (row_acc_init_rav_b << CFG_ROW_ACC_INIT_RAV_B_SHIFT) | + (row_acc_init_rav << CFG_ROW_ACC_INIT_RAV_SHIFT); + + *sc_reg9 = lin_acc_inc; + + *sc_reg12 = col_acc_offset << CFG_COL_ACC_OFFSET_SHIFT; + + *sc_reg13 = factor; + + *sc_reg24 = (src_w << CFG_ORG_W_SHIFT) | (src_h << CFG_ORG_H_SHIFT); +} + +struct sc_data *sc_create(struct platform_device *pdev) +{ + struct sc_data *sc; + + dev_dbg(&pdev->dev, "sc_create\n"); + + sc = devm_kzalloc(&pdev->dev, sizeof(*sc), GFP_KERNEL); + if (!sc) { + dev_err(&pdev->dev, "couldn't alloc sc_data\n"); + return ERR_PTR(-ENOMEM); + } + + sc->pdev = pdev; + + sc->res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sc"); + if (!sc->res) { + dev_err(&pdev->dev, "missing platform resources data\n"); + return ERR_PTR(-ENODEV); + } + + sc->base = devm_ioremap_resource(&pdev->dev, sc->res); + if (!sc->base) { + dev_err(&pdev->dev, "failed to ioremap\n"); + return ERR_PTR(-ENOMEM); + } + + return sc; +} diff --git a/drivers/media/platform/ti-vpe/sc.h b/drivers/media/platform/ti-vpe/sc.h new file mode 100644 index 000000000000..60e411e05c30 --- /dev/null +++ b/drivers/media/platform/ti-vpe/sc.h @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2013 Texas Instruments Inc. + * + * David Griego, <dagriego@biglakesoftware.com> + * Dale Farnsworth, <dale@farnsworth.org> + * Archit Taneja, <archit@ti.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 TI_SC_H +#define TI_SC_H + +/* Scaler regs */ +#define CFG_SC0 0x0 +#define CFG_INTERLACE_O (1 << 0) +#define CFG_LINEAR (1 << 1) +#define CFG_SC_BYPASS (1 << 2) +#define CFG_INVT_FID (1 << 3) +#define CFG_USE_RAV (1 << 4) +#define CFG_ENABLE_EV (1 << 5) +#define CFG_AUTO_HS (1 << 6) +#define CFG_DCM_2X (1 << 7) +#define CFG_DCM_4X (1 << 8) +#define CFG_HP_BYPASS (1 << 9) +#define CFG_INTERLACE_I (1 << 10) +#define CFG_ENABLE_SIN2_VER_INTP (1 << 11) +#define CFG_Y_PK_EN (1 << 14) +#define CFG_TRIM (1 << 15) +#define CFG_SELFGEN_FID (1 << 16) + +#define CFG_SC1 0x4 +#define CFG_ROW_ACC_INC_MASK 0x07ffffff +#define CFG_ROW_ACC_INC_SHIFT 0 + +#define CFG_SC2 0x08 +#define CFG_ROW_ACC_OFFSET_MASK 0x0fffffff +#define CFG_ROW_ACC_OFFSET_SHIFT 0 + +#define CFG_SC3 0x0c +#define CFG_ROW_ACC_OFFSET_B_MASK 0x0fffffff +#define CFG_ROW_ACC_OFFSET_B_SHIFT 0 + +#define CFG_SC4 0x10 +#define CFG_TAR_H_MASK 0x07ff +#define CFG_TAR_H_SHIFT 0 +#define CFG_TAR_W_MASK 0x07ff +#define CFG_TAR_W_SHIFT 12 +#define CFG_LIN_ACC_INC_U_MASK 0x07 +#define CFG_LIN_ACC_INC_U_SHIFT 24 +#define CFG_NLIN_ACC_INIT_U_MASK 0x07 +#define CFG_NLIN_ACC_INIT_U_SHIFT 28 + +#define CFG_SC5 0x14 +#define CFG_SRC_H_MASK 0x07ff +#define CFG_SRC_H_SHIFT 0 +#define CFG_SRC_W_MASK 0x07ff +#define CFG_SRC_W_SHIFT 12 +#define CFG_NLIN_ACC_INC_U_MASK 0x07 +#define CFG_NLIN_ACC_INC_U_SHIFT 24 + +#define CFG_SC6 0x18 +#define CFG_ROW_ACC_INIT_RAV_MASK 0x03ff +#define CFG_ROW_ACC_INIT_RAV_SHIFT 0 +#define CFG_ROW_ACC_INIT_RAV_B_MASK 0x03ff +#define CFG_ROW_ACC_INIT_RAV_B_SHIFT 10 + +#define CFG_SC8 0x20 +#define CFG_NLIN_LEFT_MASK 0x07ff +#define CFG_NLIN_LEFT_SHIFT 0 +#define CFG_NLIN_RIGHT_MASK 0x07ff +#define CFG_NLIN_RIGHT_SHIFT 12 + +#define CFG_SC9 0x24 +#define CFG_LIN_ACC_INC CFG_SC9 + +#define CFG_SC10 0x28 +#define CFG_NLIN_ACC_INIT CFG_SC10 + +#define CFG_SC11 0x2c +#define CFG_NLIN_ACC_INC CFG_SC11 + +#define CFG_SC12 0x30 +#define CFG_COL_ACC_OFFSET_MASK 0x01ffffff +#define CFG_COL_ACC_OFFSET_SHIFT 0 + +#define CFG_SC13 0x34 +#define CFG_SC_FACTOR_RAV_MASK 0xff +#define CFG_SC_FACTOR_RAV_SHIFT 0 +#define CFG_CHROMA_INTP_THR_MASK 0x03ff +#define CFG_CHROMA_INTP_THR_SHIFT 12 +#define CFG_DELTA_CHROMA_THR_MASK 0x0f +#define CFG_DELTA_CHROMA_THR_SHIFT 24 + +#define CFG_SC17 0x44 +#define CFG_EV_THR_MASK 0x03ff +#define CFG_EV_THR_SHIFT 12 +#define CFG_DELTA_LUMA_THR_MASK 0x0f +#define CFG_DELTA_LUMA_THR_SHIFT 24 +#define CFG_DELTA_EV_THR_MASK 0x0f +#define CFG_DELTA_EV_THR_SHIFT 28 + +#define CFG_SC18 0x48 +#define CFG_HS_FACTOR_MASK 0x03ff +#define CFG_HS_FACTOR_SHIFT 0 +#define CFG_CONF_DEFAULT_MASK 0x01ff +#define CFG_CONF_DEFAULT_SHIFT 16 + +#define CFG_SC19 0x4c +#define CFG_HPF_COEFF0_MASK 0xff +#define CFG_HPF_COEFF0_SHIFT 0 +#define CFG_HPF_COEFF1_MASK 0xff +#define CFG_HPF_COEFF1_SHIFT 8 +#define CFG_HPF_COEFF2_MASK 0xff +#define CFG_HPF_COEFF2_SHIFT 16 +#define CFG_HPF_COEFF3_MASK 0xff +#define CFG_HPF_COEFF3_SHIFT 23 + +#define CFG_SC20 0x50 +#define CFG_HPF_COEFF4_MASK 0xff +#define CFG_HPF_COEFF4_SHIFT 0 +#define CFG_HPF_COEFF5_MASK 0xff +#define CFG_HPF_COEFF5_SHIFT 8 +#define CFG_HPF_NORM_SHIFT_MASK 0x07 +#define CFG_HPF_NORM_SHIFT_SHIFT 16 +#define CFG_NL_LIMIT_MASK 0x1ff +#define CFG_NL_LIMIT_SHIFT 20 + +#define CFG_SC21 0x54 +#define CFG_NL_LO_THR_MASK 0x01ff +#define CFG_NL_LO_THR_SHIFT 0 +#define CFG_NL_LO_SLOPE_MASK 0xff +#define CFG_NL_LO_SLOPE_SHIFT 16 + +#define CFG_SC22 0x58 +#define CFG_NL_HI_THR_MASK 0x01ff +#define CFG_NL_HI_THR_SHIFT 0 +#define CFG_NL_HI_SLOPE_SH_MASK 0x07 +#define CFG_NL_HI_SLOPE_SH_SHIFT 16 + +#define CFG_SC23 0x5c +#define CFG_GRADIENT_THR_MASK 0x07ff +#define CFG_GRADIENT_THR_SHIFT 0 +#define CFG_GRADIENT_THR_RANGE_MASK 0x0f +#define CFG_GRADIENT_THR_RANGE_SHIFT 12 +#define CFG_MIN_GY_THR_MASK 0xff +#define CFG_MIN_GY_THR_SHIFT 16 +#define CFG_MIN_GY_THR_RANGE_MASK 0x0f +#define CFG_MIN_GY_THR_RANGE_SHIFT 28 + +#define CFG_SC24 0x60 +#define CFG_ORG_H_MASK 0x07ff +#define CFG_ORG_H_SHIFT 0 +#define CFG_ORG_W_MASK 0x07ff +#define CFG_ORG_W_SHIFT 16 + +#define CFG_SC25 0x64 +#define CFG_OFF_H_MASK 0x07ff +#define CFG_OFF_H_SHIFT 0 +#define CFG_OFF_W_MASK 0x07ff +#define CFG_OFF_W_SHIFT 16 + +/* number of phases supported by the polyphase scalers */ +#define SC_NUM_PHASES 32 + +/* number of taps used by horizontal polyphase scaler */ +#define SC_H_NUM_TAPS 7 + +/* number of taps used by vertical polyphase scaler */ +#define SC_V_NUM_TAPS 5 + +/* number of taps expected by the scaler in it's coefficient memory */ +#define SC_NUM_TAPS_MEM_ALIGN 8 + +/* + * coefficient memory size in bytes: + * num phases x num sets(luma and chroma) x num taps(aligned) x coeff size + */ +#define SC_COEF_SRAM_SIZE (SC_NUM_PHASES * 2 * SC_NUM_TAPS_MEM_ALIGN * 2) + +struct sc_data { + void __iomem *base; + struct resource *res; + + dma_addr_t loaded_coeff_h; /* loaded h coeffs in SC */ + dma_addr_t loaded_coeff_v; /* loaded v coeffs in SC */ + + bool load_coeff_h; /* have new h SC coeffs */ + bool load_coeff_v; /* have new v SC coeffs */ + + unsigned int hs_index; /* h SC coeffs selector */ + unsigned int vs_index; /* v SC coeffs selector */ + + struct platform_device *pdev; +}; + +void sc_dump_regs(struct sc_data *sc); +void sc_set_hs_coeffs(struct sc_data *sc, void *addr, unsigned int src_w, + unsigned int dst_w); +void sc_set_vs_coeffs(struct sc_data *sc, void *addr, unsigned int src_h, + unsigned int dst_h); +void sc_config_scaler(struct sc_data *sc, u32 *sc_reg0, u32 *sc_reg8, + u32 *sc_reg17, unsigned int src_w, unsigned int src_h, + unsigned int dst_w, unsigned int dst_h); +struct sc_data *sc_create(struct platform_device *pdev); + +#endif diff --git a/drivers/media/platform/ti-vpe/sc_coeff.h b/drivers/media/platform/ti-vpe/sc_coeff.h new file mode 100644 index 000000000000..5bfa5c03aec6 --- /dev/null +++ b/drivers/media/platform/ti-vpe/sc_coeff.h @@ -0,0 +1,1342 @@ +/* + * VPE SC coefs + * + * Copyright (c) 2013 Texas Instruments Inc. + * + * David Griego, <dagriego@biglakesoftware.com> + * Dale Farnsworth, <dale@farnsworth.org> + * Archit Taneja, <archit@ti.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 __TI_SC_COEFF_H +#define __TI_SC_COEFF_H + +/* horizontal scaler coefficients */ +enum { + HS_UP_SCALE = 0, + HS_LT_9_16_SCALE, + HS_LT_10_16_SCALE, + HS_LT_11_16_SCALE, + HS_LT_12_16_SCALE, + HS_LT_13_16_SCALE, + HS_LT_14_16_SCALE, + HS_LT_15_16_SCALE, + HS_LE_16_16_SCALE, +}; + +static const u16 scaler_hs_coeffs[13][SC_NUM_PHASES * 2 * SC_H_NUM_TAPS] = { + [HS_UP_SCALE] = { + /* Luma */ + 0x001F, 0x1F90, 0x00D2, 0x06FE, 0x00D2, 0x1F90, 0x001F, + 0x001C, 0x1F9E, 0x009F, 0x06FB, 0x0108, 0x1F82, 0x0022, + 0x0019, 0x1FAC, 0x006F, 0x06F3, 0x0140, 0x1F74, 0x0025, + 0x0016, 0x1FB9, 0x0041, 0x06E7, 0x017B, 0x1F66, 0x0028, + 0x0013, 0x1FC6, 0x0017, 0x06D6, 0x01B7, 0x1F58, 0x002B, + 0x0010, 0x1FD3, 0x1FEF, 0x06C0, 0x01F6, 0x1F4B, 0x002D, + 0x000E, 0x1FDF, 0x1FCB, 0x06A5, 0x0235, 0x1F3F, 0x002F, + 0x000B, 0x1FEA, 0x1FAA, 0x0686, 0x0277, 0x1F33, 0x0031, + 0x0009, 0x1FF5, 0x1F8C, 0x0663, 0x02B8, 0x1F28, 0x0033, + 0x0007, 0x1FFF, 0x1F72, 0x063A, 0x02FB, 0x1F1F, 0x0034, + 0x0005, 0x0008, 0x1F5A, 0x060F, 0x033E, 0x1F17, 0x0035, + 0x0003, 0x0010, 0x1F46, 0x05E0, 0x0382, 0x1F10, 0x0035, + 0x0002, 0x0017, 0x1F34, 0x05AF, 0x03C5, 0x1F0B, 0x0034, + 0x0001, 0x001E, 0x1F26, 0x0579, 0x0407, 0x1F08, 0x0033, + 0x0000, 0x0023, 0x1F1A, 0x0541, 0x0449, 0x1F07, 0x0032, + 0x1FFF, 0x0028, 0x1F12, 0x0506, 0x048A, 0x1F08, 0x002F, + 0x002C, 0x1F0C, 0x04C8, 0x04C8, 0x1F0C, 0x002C, 0x0000, + 0x002F, 0x1F08, 0x048A, 0x0506, 0x1F12, 0x0028, 0x1FFF, + 0x0032, 0x1F07, 0x0449, 0x0541, 0x1F1A, 0x0023, 0x0000, + 0x0033, 0x1F08, 0x0407, 0x0579, 0x1F26, 0x001E, 0x0001, + 0x0034, 0x1F0B, 0x03C5, 0x05AF, 0x1F34, 0x0017, 0x0002, + 0x0035, 0x1F10, 0x0382, 0x05E0, 0x1F46, 0x0010, 0x0003, + 0x0035, 0x1F17, 0x033E, 0x060F, 0x1F5A, 0x0008, 0x0005, + 0x0034, 0x1F1F, 0x02FB, 0x063A, 0x1F72, 0x1FFF, 0x0007, + 0x0033, 0x1F28, 0x02B8, 0x0663, 0x1F8C, 0x1FF5, 0x0009, + 0x0031, 0x1F33, 0x0277, 0x0686, 0x1FAA, 0x1FEA, 0x000B, + 0x002F, 0x1F3F, 0x0235, 0x06A5, 0x1FCB, 0x1FDF, 0x000E, + 0x002D, 0x1F4B, 0x01F6, 0x06C0, 0x1FEF, 0x1FD3, 0x0010, + 0x002B, 0x1F58, 0x01B7, 0x06D6, 0x0017, 0x1FC6, 0x0013, + 0x0028, 0x1F66, 0x017B, 0x06E7, 0x0041, 0x1FB9, 0x0016, + 0x0025, 0x1F74, 0x0140, 0x06F3, 0x006F, 0x1FAC, 0x0019, + 0x0022, 0x1F82, 0x0108, 0x06FB, 0x009F, 0x1F9E, 0x001C, + /* Chroma */ + 0x001F, 0x1F90, 0x00D2, 0x06FE, 0x00D2, 0x1F90, 0x001F, + 0x001C, 0x1F9E, 0x009F, 0x06FB, 0x0108, 0x1F82, 0x0022, + 0x0019, 0x1FAC, 0x006F, 0x06F3, 0x0140, 0x1F74, 0x0025, + 0x0016, 0x1FB9, 0x0041, 0x06E7, 0x017B, 0x1F66, 0x0028, + 0x0013, 0x1FC6, 0x0017, 0x06D6, 0x01B7, 0x1F58, 0x002B, + 0x0010, 0x1FD3, 0x1FEF, 0x06C0, 0x01F6, 0x1F4B, 0x002D, + 0x000E, 0x1FDF, 0x1FCB, 0x06A5, 0x0235, 0x1F3F, 0x002F, + 0x000B, 0x1FEA, 0x1FAA, 0x0686, 0x0277, 0x1F33, 0x0031, + 0x0009, 0x1FF5, 0x1F8C, 0x0663, 0x02B8, 0x1F28, 0x0033, + 0x0007, 0x1FFF, 0x1F72, 0x063A, 0x02FB, 0x1F1F, 0x0034, + 0x0005, 0x0008, 0x1F5A, 0x060F, 0x033E, 0x1F17, 0x0035, + 0x0003, 0x0010, 0x1F46, 0x05E0, 0x0382, 0x1F10, 0x0035, + 0x0002, 0x0017, 0x1F34, 0x05AF, 0x03C5, 0x1F0B, 0x0034, + 0x0001, 0x001E, 0x1F26, 0x0579, 0x0407, 0x1F08, 0x0033, + 0x0000, 0x0023, 0x1F1A, 0x0541, 0x0449, 0x1F07, 0x0032, + 0x1FFF, 0x0028, 0x1F12, 0x0506, 0x048A, 0x1F08, 0x002F, + 0x002C, 0x1F0C, 0x04C8, 0x04C8, 0x1F0C, 0x002C, 0x0000, + 0x002F, 0x1F08, 0x048A, 0x0506, 0x1F12, 0x0028, 0x1FFF, + 0x0032, 0x1F07, 0x0449, 0x0541, 0x1F1A, 0x0023, 0x0000, + 0x0033, 0x1F08, 0x0407, 0x0579, 0x1F26, 0x001E, 0x0001, + 0x0034, 0x1F0B, 0x03C5, 0x05AF, 0x1F34, 0x0017, 0x0002, + 0x0035, 0x1F10, 0x0382, 0x05E0, 0x1F46, 0x0010, 0x0003, + 0x0035, 0x1F17, 0x033E, 0x060F, 0x1F5A, 0x0008, 0x0005, + 0x0034, 0x1F1F, 0x02FB, 0x063A, 0x1F72, 0x1FFF, 0x0007, + 0x0033, 0x1F28, 0x02B8, 0x0663, 0x1F8C, 0x1FF5, 0x0009, + 0x0031, 0x1F33, 0x0277, 0x0686, 0x1FAA, 0x1FEA, 0x000B, + 0x002F, 0x1F3F, 0x0235, 0x06A5, 0x1FCB, 0x1FDF, 0x000E, + 0x002D, 0x1F4B, 0x01F6, 0x06C0, 0x1FEF, 0x1FD3, 0x0010, + 0x002B, 0x1F58, 0x01B7, 0x06D6, 0x0017, 0x1FC6, 0x0013, + 0x0028, 0x1F66, 0x017B, 0x06E7, 0x0041, 0x1FB9, 0x0016, + 0x0025, 0x1F74, 0x0140, 0x06F3, 0x006F, 0x1FAC, 0x0019, + 0x0022, 0x1F82, 0x0108, 0x06FB, 0x009F, 0x1F9E, 0x001C, + }, + [HS_LT_9_16_SCALE] = { + /* Luma */ + 0x1FA3, 0x005E, 0x024A, 0x036A, 0x024A, 0x005E, 0x1FA3, + 0x1FA3, 0x0052, 0x023A, 0x036A, 0x0259, 0x006A, 0x1FA4, + 0x1FA3, 0x0046, 0x022A, 0x036A, 0x0269, 0x0076, 0x1FA4, + 0x1FA3, 0x003B, 0x021A, 0x0368, 0x0278, 0x0083, 0x1FA5, + 0x1FA4, 0x0031, 0x020A, 0x0365, 0x0286, 0x0090, 0x1FA6, + 0x1FA5, 0x0026, 0x01F9, 0x0362, 0x0294, 0x009E, 0x1FA8, + 0x1FA6, 0x001C, 0x01E8, 0x035E, 0x02A3, 0x00AB, 0x1FAA, + 0x1FA7, 0x0013, 0x01D7, 0x035A, 0x02B0, 0x00B9, 0x1FAC, + 0x1FA9, 0x000A, 0x01C6, 0x0354, 0x02BD, 0x00C7, 0x1FAF, + 0x1FAA, 0x0001, 0x01B6, 0x034E, 0x02C9, 0x00D6, 0x1FB2, + 0x1FAC, 0x1FF9, 0x01A5, 0x0347, 0x02D5, 0x00E5, 0x1FB5, + 0x1FAE, 0x1FF1, 0x0194, 0x0340, 0x02E1, 0x00F3, 0x1FB9, + 0x1FB0, 0x1FEA, 0x0183, 0x0338, 0x02EC, 0x0102, 0x1FBD, + 0x1FB2, 0x1FE3, 0x0172, 0x0330, 0x02F6, 0x0112, 0x1FC1, + 0x1FB4, 0x1FDC, 0x0161, 0x0327, 0x0301, 0x0121, 0x1FC6, + 0x1FB7, 0x1FD6, 0x0151, 0x031D, 0x030A, 0x0130, 0x1FCB, + 0x1FD2, 0x0136, 0x02F8, 0x02F8, 0x0136, 0x1FD2, 0x0000, + 0x1FCB, 0x0130, 0x030A, 0x031D, 0x0151, 0x1FD6, 0x1FB7, + 0x1FC6, 0x0121, 0x0301, 0x0327, 0x0161, 0x1FDC, 0x1FB4, + 0x1FC1, 0x0112, 0x02F6, 0x0330, 0x0172, 0x1FE3, 0x1FB2, + 0x1FBD, 0x0102, 0x02EC, 0x0338, 0x0183, 0x1FEA, 0x1FB0, + 0x1FB9, 0x00F3, 0x02E1, 0x0340, 0x0194, 0x1FF1, 0x1FAE, + 0x1FB5, 0x00E5, 0x02D5, 0x0347, 0x01A5, 0x1FF9, 0x1FAC, + 0x1FB2, 0x00D6, 0x02C9, 0x034E, 0x01B6, 0x0001, 0x1FAA, + 0x1FAF, 0x00C7, 0x02BD, 0x0354, 0x01C6, 0x000A, 0x1FA9, + 0x1FAC, 0x00B9, 0x02B0, 0x035A, 0x01D7, 0x0013, 0x1FA7, + 0x1FAA, 0x00AB, 0x02A3, 0x035E, 0x01E8, 0x001C, 0x1FA6, + 0x1FA8, 0x009E, 0x0294, 0x0362, 0x01F9, 0x0026, 0x1FA5, + 0x1FA6, 0x0090, 0x0286, 0x0365, 0x020A, 0x0031, 0x1FA4, + 0x1FA5, 0x0083, 0x0278, 0x0368, 0x021A, 0x003B, 0x1FA3, + 0x1FA4, 0x0076, 0x0269, 0x036A, 0x022A, 0x0046, 0x1FA3, + 0x1FA4, 0x006A, 0x0259, 0x036A, 0x023A, 0x0052, 0x1FA3, + /* Chroma */ + 0x1FA3, 0x005E, 0x024A, 0x036A, 0x024A, 0x005E, 0x1FA3, + 0x1FA3, 0x0052, 0x023A, 0x036A, 0x0259, 0x006A, 0x1FA4, + 0x1FA3, 0x0046, 0x022A, 0x036A, 0x0269, 0x0076, 0x1FA4, + 0x1FA3, 0x003B, 0x021A, 0x0368, 0x0278, 0x0083, 0x1FA5, + 0x1FA4, 0x0031, 0x020A, 0x0365, 0x0286, 0x0090, 0x1FA6, + 0x1FA5, 0x0026, 0x01F9, 0x0362, 0x0294, 0x009E, 0x1FA8, + 0x1FA6, 0x001C, 0x01E8, 0x035E, 0x02A3, 0x00AB, 0x1FAA, + 0x1FA7, 0x0013, 0x01D7, 0x035A, 0x02B0, 0x00B9, 0x1FAC, + 0x1FA9, 0x000A, 0x01C6, 0x0354, 0x02BD, 0x00C7, 0x1FAF, + 0x1FAA, 0x0001, 0x01B6, 0x034E, 0x02C9, 0x00D6, 0x1FB2, + 0x1FAC, 0x1FF9, 0x01A5, 0x0347, 0x02D5, 0x00E5, 0x1FB5, + 0x1FAE, 0x1FF1, 0x0194, 0x0340, 0x02E1, 0x00F3, 0x1FB9, + 0x1FB0, 0x1FEA, 0x0183, 0x0338, 0x02EC, 0x0102, 0x1FBD, + 0x1FB2, 0x1FE3, 0x0172, 0x0330, 0x02F6, 0x0112, 0x1FC1, + 0x1FB4, 0x1FDC, 0x0161, 0x0327, 0x0301, 0x0121, 0x1FC6, + 0x1FB7, 0x1FD6, 0x0151, 0x031D, 0x030A, 0x0130, 0x1FCB, + 0x1FD2, 0x0136, 0x02F8, 0x02F8, 0x0136, 0x1FD2, 0x0000, + 0x1FCB, 0x0130, 0x030A, 0x031D, 0x0151, 0x1FD6, 0x1FB7, + 0x1FC6, 0x0121, 0x0301, 0x0327, 0x0161, 0x1FDC, 0x1FB4, + 0x1FC1, 0x0112, 0x02F6, 0x0330, 0x0172, 0x1FE3, 0x1FB2, + 0x1FBD, 0x0102, 0x02EC, 0x0338, 0x0183, 0x1FEA, 0x1FB0, + 0x1FB9, 0x00F3, 0x02E1, 0x0340, 0x0194, 0x1FF1, 0x1FAE, + 0x1FB5, 0x00E5, 0x02D5, 0x0347, 0x01A5, 0x1FF9, 0x1FAC, + 0x1FB2, 0x00D6, 0x02C9, 0x034E, 0x01B6, 0x0001, 0x1FAA, + 0x1FAF, 0x00C7, 0x02BD, 0x0354, 0x01C6, 0x000A, 0x1FA9, + 0x1FAC, 0x00B9, 0x02B0, 0x035A, 0x01D7, 0x0013, 0x1FA7, + 0x1FAA, 0x00AB, 0x02A3, 0x035E, 0x01E8, 0x001C, 0x1FA6, + 0x1FA8, 0x009E, 0x0294, 0x0362, 0x01F9, 0x0026, 0x1FA5, + 0x1FA6, 0x0090, 0x0286, 0x0365, 0x020A, 0x0031, 0x1FA4, + 0x1FA5, 0x0083, 0x0278, 0x0368, 0x021A, 0x003B, 0x1FA3, + 0x1FA4, 0x0076, 0x0269, 0x036A, 0x022A, 0x0046, 0x1FA3, + 0x1FA4, 0x006A, 0x0259, 0x036A, 0x023A, 0x0052, 0x1FA3, + }, + [HS_LT_10_16_SCALE] = { + /* Luma */ + 0x1F8D, 0x000C, 0x026A, 0x03FA, 0x026A, 0x000C, 0x1F8D, + 0x1F8F, 0x0000, 0x0255, 0x03FA, 0x027F, 0x0019, 0x1F8A, + 0x1F92, 0x1FF5, 0x023F, 0x03F8, 0x0293, 0x0027, 0x1F88, + 0x1F95, 0x1FEA, 0x022A, 0x03F6, 0x02A7, 0x0034, 0x1F86, + 0x1F99, 0x1FDF, 0x0213, 0x03F2, 0x02BB, 0x0043, 0x1F85, + 0x1F9C, 0x1FD5, 0x01FE, 0x03ED, 0x02CF, 0x0052, 0x1F83, + 0x1FA0, 0x1FCC, 0x01E8, 0x03E7, 0x02E1, 0x0061, 0x1F83, + 0x1FA4, 0x1FC3, 0x01D2, 0x03E0, 0x02F4, 0x0071, 0x1F82, + 0x1FA7, 0x1FBB, 0x01BC, 0x03D9, 0x0306, 0x0081, 0x1F82, + 0x1FAB, 0x1FB4, 0x01A6, 0x03D0, 0x0317, 0x0092, 0x1F82, + 0x1FAF, 0x1FAD, 0x0190, 0x03C7, 0x0327, 0x00A3, 0x1F83, + 0x1FB3, 0x1FA7, 0x017A, 0x03BC, 0x0337, 0x00B5, 0x1F84, + 0x1FB8, 0x1FA1, 0x0165, 0x03B0, 0x0346, 0x00C7, 0x1F85, + 0x1FBC, 0x1F9C, 0x0150, 0x03A4, 0x0354, 0x00D9, 0x1F87, + 0x1FC0, 0x1F98, 0x013A, 0x0397, 0x0361, 0x00EC, 0x1F8A, + 0x1FC4, 0x1F93, 0x0126, 0x0389, 0x036F, 0x00FE, 0x1F8D, + 0x1F93, 0x010A, 0x0363, 0x0363, 0x010A, 0x1F93, 0x0000, + 0x1F8D, 0x00FE, 0x036F, 0x0389, 0x0126, 0x1F93, 0x1FC4, + 0x1F8A, 0x00EC, 0x0361, 0x0397, 0x013A, 0x1F98, 0x1FC0, + 0x1F87, 0x00D9, 0x0354, 0x03A4, 0x0150, 0x1F9C, 0x1FBC, + 0x1F85, 0x00C7, 0x0346, 0x03B0, 0x0165, 0x1FA1, 0x1FB8, + 0x1F84, 0x00B5, 0x0337, 0x03BC, 0x017A, 0x1FA7, 0x1FB3, + 0x1F83, 0x00A3, 0x0327, 0x03C7, 0x0190, 0x1FAD, 0x1FAF, + 0x1F82, 0x0092, 0x0317, 0x03D0, 0x01A6, 0x1FB4, 0x1FAB, + 0x1F82, 0x0081, 0x0306, 0x03D9, 0x01BC, 0x1FBB, 0x1FA7, + 0x1F82, 0x0071, 0x02F4, 0x03E0, 0x01D2, 0x1FC3, 0x1FA4, + 0x1F83, 0x0061, 0x02E1, 0x03E7, 0x01E8, 0x1FCC, 0x1FA0, + 0x1F83, 0x0052, 0x02CF, 0x03ED, 0x01FE, 0x1FD5, 0x1F9C, + 0x1F85, 0x0043, 0x02BB, 0x03F2, 0x0213, 0x1FDF, 0x1F99, + 0x1F86, 0x0034, 0x02A7, 0x03F6, 0x022A, 0x1FEA, 0x1F95, + 0x1F88, 0x0027, 0x0293, 0x03F8, 0x023F, 0x1FF5, 0x1F92, + 0x1F8A, 0x0019, 0x027F, 0x03FA, 0x0255, 0x0000, 0x1F8F, + /* Chroma */ + 0x1F8D, 0x000C, 0x026A, 0x03FA, 0x026A, 0x000C, 0x1F8D, + 0x1F8F, 0x0000, 0x0255, 0x03FA, 0x027F, 0x0019, 0x1F8A, + 0x1F92, 0x1FF5, 0x023F, 0x03F8, 0x0293, 0x0027, 0x1F88, + 0x1F95, 0x1FEA, 0x022A, 0x03F6, 0x02A7, 0x0034, 0x1F86, + 0x1F99, 0x1FDF, 0x0213, 0x03F2, 0x02BB, 0x0043, 0x1F85, + 0x1F9C, 0x1FD5, 0x01FE, 0x03ED, 0x02CF, 0x0052, 0x1F83, + 0x1FA0, 0x1FCC, 0x01E8, 0x03E7, 0x02E1, 0x0061, 0x1F83, + 0x1FA4, 0x1FC3, 0x01D2, 0x03E0, 0x02F4, 0x0071, 0x1F82, + 0x1FA7, 0x1FBB, 0x01BC, 0x03D9, 0x0306, 0x0081, 0x1F82, + 0x1FAB, 0x1FB4, 0x01A6, 0x03D0, 0x0317, 0x0092, 0x1F82, + 0x1FAF, 0x1FAD, 0x0190, 0x03C7, 0x0327, 0x00A3, 0x1F83, + 0x1FB3, 0x1FA7, 0x017A, 0x03BC, 0x0337, 0x00B5, 0x1F84, + 0x1FB8, 0x1FA1, 0x0165, 0x03B0, 0x0346, 0x00C7, 0x1F85, + 0x1FBC, 0x1F9C, 0x0150, 0x03A4, 0x0354, 0x00D9, 0x1F87, + 0x1FC0, 0x1F98, 0x013A, 0x0397, 0x0361, 0x00EC, 0x1F8A, + 0x1FC4, 0x1F93, 0x0126, 0x0389, 0x036F, 0x00FE, 0x1F8D, + 0x1F93, 0x010A, 0x0363, 0x0363, 0x010A, 0x1F93, 0x0000, + 0x1F8D, 0x00FE, 0x036F, 0x0389, 0x0126, 0x1F93, 0x1FC4, + 0x1F8A, 0x00EC, 0x0361, 0x0397, 0x013A, 0x1F98, 0x1FC0, + 0x1F87, 0x00D9, 0x0354, 0x03A4, 0x0150, 0x1F9C, 0x1FBC, + 0x1F85, 0x00C7, 0x0346, 0x03B0, 0x0165, 0x1FA1, 0x1FB8, + 0x1F84, 0x00B5, 0x0337, 0x03BC, 0x017A, 0x1FA7, 0x1FB3, + 0x1F83, 0x00A3, 0x0327, 0x03C7, 0x0190, 0x1FAD, 0x1FAF, + 0x1F82, 0x0092, 0x0317, 0x03D0, 0x01A6, 0x1FB4, 0x1FAB, + 0x1F82, 0x0081, 0x0306, 0x03D9, 0x01BC, 0x1FBB, 0x1FA7, + 0x1F82, 0x0071, 0x02F4, 0x03E0, 0x01D2, 0x1FC3, 0x1FA4, + 0x1F83, 0x0061, 0x02E1, 0x03E7, 0x01E8, 0x1FCC, 0x1FA0, + 0x1F83, 0x0052, 0x02CF, 0x03ED, 0x01FE, 0x1FD5, 0x1F9C, + 0x1F85, 0x0043, 0x02BB, 0x03F2, 0x0213, 0x1FDF, 0x1F99, + 0x1F86, 0x0034, 0x02A7, 0x03F6, 0x022A, 0x1FEA, 0x1F95, + 0x1F88, 0x0027, 0x0293, 0x03F8, 0x023F, 0x1FF5, 0x1F92, + 0x1F8A, 0x0019, 0x027F, 0x03FA, 0x0255, 0x0000, 0x1F8F, + }, + [HS_LT_11_16_SCALE] = { + /* Luma */ + 0x1F95, 0x1FB5, 0x0272, 0x0488, 0x0272, 0x1FB5, 0x1F95, + 0x1F9B, 0x1FAA, 0x0257, 0x0486, 0x028D, 0x1FC1, 0x1F90, + 0x1FA0, 0x1FA0, 0x023C, 0x0485, 0x02A8, 0x1FCD, 0x1F8A, + 0x1FA6, 0x1F96, 0x0221, 0x0481, 0x02C2, 0x1FDB, 0x1F85, + 0x1FAC, 0x1F8E, 0x0205, 0x047C, 0x02DC, 0x1FE9, 0x1F80, + 0x1FB1, 0x1F86, 0x01E9, 0x0476, 0x02F6, 0x1FF8, 0x1F7C, + 0x1FB7, 0x1F7F, 0x01CE, 0x046E, 0x030F, 0x0008, 0x1F77, + 0x1FBD, 0x1F79, 0x01B3, 0x0465, 0x0326, 0x0019, 0x1F73, + 0x1FC3, 0x1F73, 0x0197, 0x045B, 0x033E, 0x002A, 0x1F70, + 0x1FC8, 0x1F6F, 0x017D, 0x044E, 0x0355, 0x003C, 0x1F6D, + 0x1FCE, 0x1F6B, 0x0162, 0x0441, 0x036B, 0x004F, 0x1F6A, + 0x1FD3, 0x1F68, 0x0148, 0x0433, 0x0380, 0x0063, 0x1F67, + 0x1FD8, 0x1F65, 0x012E, 0x0424, 0x0395, 0x0077, 0x1F65, + 0x1FDE, 0x1F63, 0x0115, 0x0413, 0x03A8, 0x008B, 0x1F64, + 0x1FE3, 0x1F62, 0x00FC, 0x0403, 0x03BA, 0x00A0, 0x1F62, + 0x1FE7, 0x1F62, 0x00E4, 0x03EF, 0x03CC, 0x00B6, 0x1F62, + 0x1F63, 0x00CA, 0x03D3, 0x03D3, 0x00CA, 0x1F63, 0x0000, + 0x1F62, 0x00B6, 0x03CC, 0x03EF, 0x00E4, 0x1F62, 0x1FE7, + 0x1F62, 0x00A0, 0x03BA, 0x0403, 0x00FC, 0x1F62, 0x1FE3, + 0x1F64, 0x008B, 0x03A8, 0x0413, 0x0115, 0x1F63, 0x1FDE, + 0x1F65, 0x0077, 0x0395, 0x0424, 0x012E, 0x1F65, 0x1FD8, + 0x1F67, 0x0063, 0x0380, 0x0433, 0x0148, 0x1F68, 0x1FD3, + 0x1F6A, 0x004F, 0x036B, 0x0441, 0x0162, 0x1F6B, 0x1FCE, + 0x1F6D, 0x003C, 0x0355, 0x044E, 0x017D, 0x1F6F, 0x1FC8, + 0x1F70, 0x002A, 0x033E, 0x045B, 0x0197, 0x1F73, 0x1FC3, + 0x1F73, 0x0019, 0x0326, 0x0465, 0x01B3, 0x1F79, 0x1FBD, + 0x1F77, 0x0008, 0x030F, 0x046E, 0x01CE, 0x1F7F, 0x1FB7, + 0x1F7C, 0x1FF8, 0x02F6, 0x0476, 0x01E9, 0x1F86, 0x1FB1, + 0x1F80, 0x1FE9, 0x02DC, 0x047C, 0x0205, 0x1F8E, 0x1FAC, + 0x1F85, 0x1FDB, 0x02C2, 0x0481, 0x0221, 0x1F96, 0x1FA6, + 0x1F8A, 0x1FCD, 0x02A8, 0x0485, 0x023C, 0x1FA0, 0x1FA0, + 0x1F90, 0x1FC1, 0x028D, 0x0486, 0x0257, 0x1FAA, 0x1F9B, + /* Chroma */ + 0x1F95, 0x1FB5, 0x0272, 0x0488, 0x0272, 0x1FB5, 0x1F95, + 0x1F9B, 0x1FAA, 0x0257, 0x0486, 0x028D, 0x1FC1, 0x1F90, + 0x1FA0, 0x1FA0, 0x023C, 0x0485, 0x02A8, 0x1FCD, 0x1F8A, + 0x1FA6, 0x1F96, 0x0221, 0x0481, 0x02C2, 0x1FDB, 0x1F85, + 0x1FAC, 0x1F8E, 0x0205, 0x047C, 0x02DC, 0x1FE9, 0x1F80, + 0x1FB1, 0x1F86, 0x01E9, 0x0476, 0x02F6, 0x1FF8, 0x1F7C, + 0x1FB7, 0x1F7F, 0x01CE, 0x046E, 0x030F, 0x0008, 0x1F77, + 0x1FBD, 0x1F79, 0x01B3, 0x0465, 0x0326, 0x0019, 0x1F73, + 0x1FC3, 0x1F73, 0x0197, 0x045B, 0x033E, 0x002A, 0x1F70, + 0x1FC8, 0x1F6F, 0x017D, 0x044E, 0x0355, 0x003C, 0x1F6D, + 0x1FCE, 0x1F6B, 0x0162, 0x0441, 0x036B, 0x004F, 0x1F6A, + 0x1FD3, 0x1F68, 0x0148, 0x0433, 0x0380, 0x0063, 0x1F67, + 0x1FD8, 0x1F65, 0x012E, 0x0424, 0x0395, 0x0077, 0x1F65, + 0x1FDE, 0x1F63, 0x0115, 0x0413, 0x03A8, 0x008B, 0x1F64, + 0x1FE3, 0x1F62, 0x00FC, 0x0403, 0x03BA, 0x00A0, 0x1F62, + 0x1FE7, 0x1F62, 0x00E4, 0x03EF, 0x03CC, 0x00B6, 0x1F62, + 0x1F63, 0x00CA, 0x03D3, 0x03D3, 0x00CA, 0x1F63, 0x0000, + 0x1F62, 0x00B6, 0x03CC, 0x03EF, 0x00E4, 0x1F62, 0x1FE7, + 0x1F62, 0x00A0, 0x03BA, 0x0403, 0x00FC, 0x1F62, 0x1FE3, + 0x1F64, 0x008B, 0x03A8, 0x0413, 0x0115, 0x1F63, 0x1FDE, + 0x1F65, 0x0077, 0x0395, 0x0424, 0x012E, 0x1F65, 0x1FD8, + 0x1F67, 0x0063, 0x0380, 0x0433, 0x0148, 0x1F68, 0x1FD3, + 0x1F6A, 0x004F, 0x036B, 0x0441, 0x0162, 0x1F6B, 0x1FCE, + 0x1F6D, 0x003C, 0x0355, 0x044E, 0x017D, 0x1F6F, 0x1FC8, + 0x1F70, 0x002A, 0x033E, 0x045B, 0x0197, 0x1F73, 0x1FC3, + 0x1F73, 0x0019, 0x0326, 0x0465, 0x01B3, 0x1F79, 0x1FBD, + 0x1F77, 0x0008, 0x030F, 0x046E, 0x01CE, 0x1F7F, 0x1FB7, + 0x1F7C, 0x1FF8, 0x02F6, 0x0476, 0x01E9, 0x1F86, 0x1FB1, + 0x1F80, 0x1FE9, 0x02DC, 0x047C, 0x0205, 0x1F8E, 0x1FAC, + 0x1F85, 0x1FDB, 0x02C2, 0x0481, 0x0221, 0x1F96, 0x1FA6, + 0x1F8A, 0x1FCD, 0x02A8, 0x0485, 0x023C, 0x1FA0, 0x1FA0, + 0x1F90, 0x1FC1, 0x028D, 0x0486, 0x0257, 0x1FAA, 0x1F9B, + }, + [HS_LT_12_16_SCALE] = { + /* Luma */ + 0x1FBB, 0x1F65, 0x025E, 0x0504, 0x025E, 0x1F65, 0x1FBB, + 0x1FC3, 0x1F5D, 0x023C, 0x0503, 0x027F, 0x1F6E, 0x1FB4, + 0x1FCA, 0x1F56, 0x021B, 0x0501, 0x02A0, 0x1F78, 0x1FAC, + 0x1FD1, 0x1F50, 0x01FA, 0x04FD, 0x02C0, 0x1F83, 0x1FA5, + 0x1FD8, 0x1F4B, 0x01D9, 0x04F6, 0x02E1, 0x1F90, 0x1F9D, + 0x1FDF, 0x1F47, 0x01B8, 0x04EF, 0x0301, 0x1F9D, 0x1F95, + 0x1FE6, 0x1F43, 0x0198, 0x04E5, 0x0321, 0x1FAB, 0x1F8E, + 0x1FEC, 0x1F41, 0x0178, 0x04DA, 0x0340, 0x1FBB, 0x1F86, + 0x1FF2, 0x1F40, 0x0159, 0x04CC, 0x035E, 0x1FCC, 0x1F7F, + 0x1FF8, 0x1F40, 0x013A, 0x04BE, 0x037B, 0x1FDD, 0x1F78, + 0x1FFE, 0x1F40, 0x011B, 0x04AD, 0x0398, 0x1FF0, 0x1F72, + 0x0003, 0x1F41, 0x00FD, 0x049C, 0x03B4, 0x0004, 0x1F6B, + 0x0008, 0x1F43, 0x00E0, 0x0489, 0x03CE, 0x0019, 0x1F65, + 0x000D, 0x1F46, 0x00C4, 0x0474, 0x03E8, 0x002E, 0x1F5F, + 0x0011, 0x1F49, 0x00A9, 0x045E, 0x0400, 0x0045, 0x1F5A, + 0x0015, 0x1F4D, 0x008E, 0x0447, 0x0418, 0x005C, 0x1F55, + 0x1F4F, 0x0076, 0x043B, 0x043B, 0x0076, 0x1F4F, 0x0000, + 0x1F55, 0x005C, 0x0418, 0x0447, 0x008E, 0x1F4D, 0x0015, + 0x1F5A, 0x0045, 0x0400, 0x045E, 0x00A9, 0x1F49, 0x0011, + 0x1F5F, 0x002E, 0x03E8, 0x0474, 0x00C4, 0x1F46, 0x000D, + 0x1F65, 0x0019, 0x03CE, 0x0489, 0x00E0, 0x1F43, 0x0008, + 0x1F6B, 0x0004, 0x03B4, 0x049C, 0x00FD, 0x1F41, 0x0003, + 0x1F72, 0x1FF0, 0x0398, 0x04AD, 0x011B, 0x1F40, 0x1FFE, + 0x1F78, 0x1FDD, 0x037B, 0x04BE, 0x013A, 0x1F40, 0x1FF8, + 0x1F7F, 0x1FCC, 0x035E, 0x04CC, 0x0159, 0x1F40, 0x1FF2, + 0x1F86, 0x1FBB, 0x0340, 0x04DA, 0x0178, 0x1F41, 0x1FEC, + 0x1F8E, 0x1FAB, 0x0321, 0x04E5, 0x0198, 0x1F43, 0x1FE6, + 0x1F95, 0x1F9D, 0x0301, 0x04EF, 0x01B8, 0x1F47, 0x1FDF, + 0x1F9D, 0x1F90, 0x02E1, 0x04F6, 0x01D9, 0x1F4B, 0x1FD8, + 0x1FA5, 0x1F83, 0x02C0, 0x04FD, 0x01FA, 0x1F50, 0x1FD1, + 0x1FAC, 0x1F78, 0x02A0, 0x0501, 0x021B, 0x1F56, 0x1FCA, + 0x1FB4, 0x1F6E, 0x027F, 0x0503, 0x023C, 0x1F5D, 0x1FC3, + /* Chroma */ + 0x1FBB, 0x1F65, 0x025E, 0x0504, 0x025E, 0x1F65, 0x1FBB, + 0x1FC3, 0x1F5D, 0x023C, 0x0503, 0x027F, 0x1F6E, 0x1FB4, + 0x1FCA, 0x1F56, 0x021B, 0x0501, 0x02A0, 0x1F78, 0x1FAC, + 0x1FD1, 0x1F50, 0x01FA, 0x04FD, 0x02C0, 0x1F83, 0x1FA5, + 0x1FD8, 0x1F4B, 0x01D9, 0x04F6, 0x02E1, 0x1F90, 0x1F9D, + 0x1FDF, 0x1F47, 0x01B8, 0x04EF, 0x0301, 0x1F9D, 0x1F95, + 0x1FE6, 0x1F43, 0x0198, 0x04E5, 0x0321, 0x1FAB, 0x1F8E, + 0x1FEC, 0x1F41, 0x0178, 0x04DA, 0x0340, 0x1FBB, 0x1F86, + 0x1FF2, 0x1F40, 0x0159, 0x04CC, 0x035E, 0x1FCC, 0x1F7F, + 0x1FF8, 0x1F40, 0x013A, 0x04BE, 0x037B, 0x1FDD, 0x1F78, + 0x1FFE, 0x1F40, 0x011B, 0x04AD, 0x0398, 0x1FF0, 0x1F72, + 0x0003, 0x1F41, 0x00FD, 0x049C, 0x03B4, 0x0004, 0x1F6B, + 0x0008, 0x1F43, 0x00E0, 0x0489, 0x03CE, 0x0019, 0x1F65, + 0x000D, 0x1F46, 0x00C4, 0x0474, 0x03E8, 0x002E, 0x1F5F, + 0x0011, 0x1F49, 0x00A9, 0x045E, 0x0400, 0x0045, 0x1F5A, + 0x0015, 0x1F4D, 0x008E, 0x0447, 0x0418, 0x005C, 0x1F55, + 0x1F4F, 0x0076, 0x043B, 0x043B, 0x0076, 0x1F4F, 0x0000, + 0x1F55, 0x005C, 0x0418, 0x0447, 0x008E, 0x1F4D, 0x0015, + 0x1F5A, 0x0045, 0x0400, 0x045E, 0x00A9, 0x1F49, 0x0011, + 0x1F5F, 0x002E, 0x03E8, 0x0474, 0x00C4, 0x1F46, 0x000D, + 0x1F65, 0x0019, 0x03CE, 0x0489, 0x00E0, 0x1F43, 0x0008, + 0x1F6B, 0x0004, 0x03B4, 0x049C, 0x00FD, 0x1F41, 0x0003, + 0x1F72, 0x1FF0, 0x0398, 0x04AD, 0x011B, 0x1F40, 0x1FFE, + 0x1F78, 0x1FDD, 0x037B, 0x04BE, 0x013A, 0x1F40, 0x1FF8, + 0x1F7F, 0x1FCC, 0x035E, 0x04CC, 0x0159, 0x1F40, 0x1FF2, + 0x1F86, 0x1FBB, 0x0340, 0x04DA, 0x0178, 0x1F41, 0x1FEC, + 0x1F8E, 0x1FAB, 0x0321, 0x04E5, 0x0198, 0x1F43, 0x1FE6, + 0x1F95, 0x1F9D, 0x0301, 0x04EF, 0x01B8, 0x1F47, 0x1FDF, + 0x1F9D, 0x1F90, 0x02E1, 0x04F6, 0x01D9, 0x1F4B, 0x1FD8, + 0x1FA5, 0x1F83, 0x02C0, 0x04FD, 0x01FA, 0x1F50, 0x1FD1, + 0x1FAC, 0x1F78, 0x02A0, 0x0501, 0x021B, 0x1F56, 0x1FCA, + 0x1FB4, 0x1F6E, 0x027F, 0x0503, 0x023C, 0x1F5D, 0x1FC3, + }, + [HS_LT_13_16_SCALE] = { + /* Luma */ + 0x1FF4, 0x1F29, 0x022D, 0x056C, 0x022D, 0x1F29, 0x1FF4, + 0x1FFC, 0x1F26, 0x0206, 0x056A, 0x0254, 0x1F2E, 0x1FEC, + 0x0003, 0x1F24, 0x01E0, 0x0567, 0x027A, 0x1F34, 0x1FE4, + 0x000A, 0x1F23, 0x01BA, 0x0561, 0x02A2, 0x1F3B, 0x1FDB, + 0x0011, 0x1F22, 0x0194, 0x055B, 0x02C9, 0x1F43, 0x1FD2, + 0x0017, 0x1F23, 0x016F, 0x0551, 0x02F0, 0x1F4D, 0x1FC9, + 0x001D, 0x1F25, 0x014B, 0x0545, 0x0316, 0x1F58, 0x1FC0, + 0x0022, 0x1F28, 0x0127, 0x0538, 0x033C, 0x1F65, 0x1FB6, + 0x0027, 0x1F2C, 0x0104, 0x0528, 0x0361, 0x1F73, 0x1FAD, + 0x002B, 0x1F30, 0x00E2, 0x0518, 0x0386, 0x1F82, 0x1FA3, + 0x002F, 0x1F36, 0x00C2, 0x0504, 0x03AA, 0x1F92, 0x1F99, + 0x0032, 0x1F3C, 0x00A2, 0x04EF, 0x03CD, 0x1FA4, 0x1F90, + 0x0035, 0x1F42, 0x0083, 0x04D9, 0x03EF, 0x1FB8, 0x1F86, + 0x0038, 0x1F49, 0x0065, 0x04C0, 0x0410, 0x1FCD, 0x1F7D, + 0x003A, 0x1F51, 0x0048, 0x04A6, 0x0431, 0x1FE3, 0x1F73, + 0x003C, 0x1F59, 0x002D, 0x048A, 0x0450, 0x1FFA, 0x1F6A, + 0x1F5D, 0x0014, 0x048F, 0x048F, 0x0014, 0x1F5D, 0x0000, + 0x1F6A, 0x1FFA, 0x0450, 0x048A, 0x002D, 0x1F59, 0x003C, + 0x1F73, 0x1FE3, 0x0431, 0x04A6, 0x0048, 0x1F51, 0x003A, + 0x1F7D, 0x1FCD, 0x0410, 0x04C0, 0x0065, 0x1F49, 0x0038, + 0x1F86, 0x1FB8, 0x03EF, 0x04D9, 0x0083, 0x1F42, 0x0035, + 0x1F90, 0x1FA4, 0x03CD, 0x04EF, 0x00A2, 0x1F3C, 0x0032, + 0x1F99, 0x1F92, 0x03AA, 0x0504, 0x00C2, 0x1F36, 0x002F, + 0x1FA3, 0x1F82, 0x0386, 0x0518, 0x00E2, 0x1F30, 0x002B, + 0x1FAD, 0x1F73, 0x0361, 0x0528, 0x0104, 0x1F2C, 0x0027, + 0x1FB6, 0x1F65, 0x033C, 0x0538, 0x0127, 0x1F28, 0x0022, + 0x1FC0, 0x1F58, 0x0316, 0x0545, 0x014B, 0x1F25, 0x001D, + 0x1FC9, 0x1F4D, 0x02F0, 0x0551, 0x016F, 0x1F23, 0x0017, + 0x1FD2, 0x1F43, 0x02C9, 0x055B, 0x0194, 0x1F22, 0x0011, + 0x1FDB, 0x1F3B, 0x02A2, 0x0561, 0x01BA, 0x1F23, 0x000A, + 0x1FE4, 0x1F34, 0x027A, 0x0567, 0x01E0, 0x1F24, 0x0003, + 0x1FEC, 0x1F2E, 0x0254, 0x056A, 0x0206, 0x1F26, 0x1FFC, + /* Chroma */ + 0x1FF4, 0x1F29, 0x022D, 0x056C, 0x022D, 0x1F29, 0x1FF4, + 0x1FFC, 0x1F26, 0x0206, 0x056A, 0x0254, 0x1F2E, 0x1FEC, + 0x0003, 0x1F24, 0x01E0, 0x0567, 0x027A, 0x1F34, 0x1FE4, + 0x000A, 0x1F23, 0x01BA, 0x0561, 0x02A2, 0x1F3B, 0x1FDB, + 0x0011, 0x1F22, 0x0194, 0x055B, 0x02C9, 0x1F43, 0x1FD2, + 0x0017, 0x1F23, 0x016F, 0x0551, 0x02F0, 0x1F4D, 0x1FC9, + 0x001D, 0x1F25, 0x014B, 0x0545, 0x0316, 0x1F58, 0x1FC0, + 0x0022, 0x1F28, 0x0127, 0x0538, 0x033C, 0x1F65, 0x1FB6, + 0x0027, 0x1F2C, 0x0104, 0x0528, 0x0361, 0x1F73, 0x1FAD, + 0x002B, 0x1F30, 0x00E2, 0x0518, 0x0386, 0x1F82, 0x1FA3, + 0x002F, 0x1F36, 0x00C2, 0x0504, 0x03AA, 0x1F92, 0x1F99, + 0x0032, 0x1F3C, 0x00A2, 0x04EF, 0x03CD, 0x1FA4, 0x1F90, + 0x0035, 0x1F42, 0x0083, 0x04D9, 0x03EF, 0x1FB8, 0x1F86, + 0x0038, 0x1F49, 0x0065, 0x04C0, 0x0410, 0x1FCD, 0x1F7D, + 0x003A, 0x1F51, 0x0048, 0x04A6, 0x0431, 0x1FE3, 0x1F73, + 0x003C, 0x1F59, 0x002D, 0x048A, 0x0450, 0x1FFA, 0x1F6A, + 0x1F5D, 0x0014, 0x048F, 0x048F, 0x0014, 0x1F5D, 0x0000, + 0x1F6A, 0x1FFA, 0x0450, 0x048A, 0x002D, 0x1F59, 0x003C, + 0x1F73, 0x1FE3, 0x0431, 0x04A6, 0x0048, 0x1F51, 0x003A, + 0x1F7D, 0x1FCD, 0x0410, 0x04C0, 0x0065, 0x1F49, 0x0038, + 0x1F86, 0x1FB8, 0x03EF, 0x04D9, 0x0083, 0x1F42, 0x0035, + 0x1F90, 0x1FA4, 0x03CD, 0x04EF, 0x00A2, 0x1F3C, 0x0032, + 0x1F99, 0x1F92, 0x03AA, 0x0504, 0x00C2, 0x1F36, 0x002F, + 0x1FA3, 0x1F82, 0x0386, 0x0518, 0x00E2, 0x1F30, 0x002B, + 0x1FAD, 0x1F73, 0x0361, 0x0528, 0x0104, 0x1F2C, 0x0027, + 0x1FB6, 0x1F65, 0x033C, 0x0538, 0x0127, 0x1F28, 0x0022, + 0x1FC0, 0x1F58, 0x0316, 0x0545, 0x014B, 0x1F25, 0x001D, + 0x1FC9, 0x1F4D, 0x02F0, 0x0551, 0x016F, 0x1F23, 0x0017, + 0x1FD2, 0x1F43, 0x02C9, 0x055B, 0x0194, 0x1F22, 0x0011, + 0x1FDB, 0x1F3B, 0x02A2, 0x0561, 0x01BA, 0x1F23, 0x000A, + 0x1FE4, 0x1F34, 0x027A, 0x0567, 0x01E0, 0x1F24, 0x0003, + 0x1FEC, 0x1F2E, 0x0254, 0x056A, 0x0206, 0x1F26, 0x1FFC, + }, + [HS_LT_14_16_SCALE] = { + /* Luma */ + 0x002F, 0x1F0B, 0x01E7, 0x05BE, 0x01E7, 0x1F0B, 0x002F, + 0x0035, 0x1F0D, 0x01BC, 0x05BD, 0x0213, 0x1F0A, 0x0028, + 0x003A, 0x1F11, 0x0191, 0x05BA, 0x023F, 0x1F0A, 0x0021, + 0x003F, 0x1F15, 0x0167, 0x05B3, 0x026C, 0x1F0C, 0x001A, + 0x0043, 0x1F1B, 0x013E, 0x05AA, 0x0299, 0x1F0F, 0x0012, + 0x0046, 0x1F21, 0x0116, 0x05A1, 0x02C6, 0x1F13, 0x0009, + 0x0049, 0x1F28, 0x00EF, 0x0593, 0x02F4, 0x1F19, 0x0000, + 0x004C, 0x1F30, 0x00C9, 0x0584, 0x0321, 0x1F20, 0x1FF6, + 0x004E, 0x1F39, 0x00A4, 0x0572, 0x034D, 0x1F2A, 0x1FEC, + 0x004F, 0x1F43, 0x0080, 0x055E, 0x037A, 0x1F34, 0x1FE2, + 0x0050, 0x1F4D, 0x005E, 0x0548, 0x03A5, 0x1F41, 0x1FD7, + 0x0050, 0x1F57, 0x003D, 0x0531, 0x03D1, 0x1F4F, 0x1FCB, + 0x0050, 0x1F62, 0x001E, 0x0516, 0x03FB, 0x1F5F, 0x1FC0, + 0x004F, 0x1F6D, 0x0000, 0x04FA, 0x0425, 0x1F71, 0x1FB4, + 0x004E, 0x1F79, 0x1FE4, 0x04DC, 0x044D, 0x1F84, 0x1FA8, + 0x004D, 0x1F84, 0x1FCA, 0x04BC, 0x0474, 0x1F99, 0x1F9C, + 0x1F8C, 0x1FAE, 0x04C6, 0x04C6, 0x1FAE, 0x1F8C, 0x0000, + 0x1F9C, 0x1F99, 0x0474, 0x04BC, 0x1FCA, 0x1F84, 0x004D, + 0x1FA8, 0x1F84, 0x044D, 0x04DC, 0x1FE4, 0x1F79, 0x004E, + 0x1FB4, 0x1F71, 0x0425, 0x04FA, 0x0000, 0x1F6D, 0x004F, + 0x1FC0, 0x1F5F, 0x03FB, 0x0516, 0x001E, 0x1F62, 0x0050, + 0x1FCB, 0x1F4F, 0x03D1, 0x0531, 0x003D, 0x1F57, 0x0050, + 0x1FD7, 0x1F41, 0x03A5, 0x0548, 0x005E, 0x1F4D, 0x0050, + 0x1FE2, 0x1F34, 0x037A, 0x055E, 0x0080, 0x1F43, 0x004F, + 0x1FEC, 0x1F2A, 0x034D, 0x0572, 0x00A4, 0x1F39, 0x004E, + 0x1FF6, 0x1F20, 0x0321, 0x0584, 0x00C9, 0x1F30, 0x004C, + 0x0000, 0x1F19, 0x02F4, 0x0593, 0x00EF, 0x1F28, 0x0049, + 0x0009, 0x1F13, 0x02C6, 0x05A1, 0x0116, 0x1F21, 0x0046, + 0x0012, 0x1F0F, 0x0299, 0x05AA, 0x013E, 0x1F1B, 0x0043, + 0x001A, 0x1F0C, 0x026C, 0x05B3, 0x0167, 0x1F15, 0x003F, + 0x0021, 0x1F0A, 0x023F, 0x05BA, 0x0191, 0x1F11, 0x003A, + 0x0028, 0x1F0A, 0x0213, 0x05BD, 0x01BC, 0x1F0D, 0x0035, + /* Chroma */ + 0x002F, 0x1F0B, 0x01E7, 0x05BE, 0x01E7, 0x1F0B, 0x002F, + 0x0035, 0x1F0D, 0x01BC, 0x05BD, 0x0213, 0x1F0A, 0x0028, + 0x003A, 0x1F11, 0x0191, 0x05BA, 0x023F, 0x1F0A, 0x0021, + 0x003F, 0x1F15, 0x0167, 0x05B3, 0x026C, 0x1F0C, 0x001A, + 0x0043, 0x1F1B, 0x013E, 0x05AA, 0x0299, 0x1F0F, 0x0012, + 0x0046, 0x1F21, 0x0116, 0x05A1, 0x02C6, 0x1F13, 0x0009, + 0x0049, 0x1F28, 0x00EF, 0x0593, 0x02F4, 0x1F19, 0x0000, + 0x004C, 0x1F30, 0x00C9, 0x0584, 0x0321, 0x1F20, 0x1FF6, + 0x004E, 0x1F39, 0x00A4, 0x0572, 0x034D, 0x1F2A, 0x1FEC, + 0x004F, 0x1F43, 0x0080, 0x055E, 0x037A, 0x1F34, 0x1FE2, + 0x0050, 0x1F4D, 0x005E, 0x0548, 0x03A5, 0x1F41, 0x1FD7, + 0x0050, 0x1F57, 0x003D, 0x0531, 0x03D1, 0x1F4F, 0x1FCB, + 0x0050, 0x1F62, 0x001E, 0x0516, 0x03FB, 0x1F5F, 0x1FC0, + 0x004F, 0x1F6D, 0x0000, 0x04FA, 0x0425, 0x1F71, 0x1FB4, + 0x004E, 0x1F79, 0x1FE4, 0x04DC, 0x044D, 0x1F84, 0x1FA8, + 0x004D, 0x1F84, 0x1FCA, 0x04BC, 0x0474, 0x1F99, 0x1F9C, + 0x1F8C, 0x1FAE, 0x04C6, 0x04C6, 0x1FAE, 0x1F8C, 0x0000, + 0x1F9C, 0x1F99, 0x0474, 0x04BC, 0x1FCA, 0x1F84, 0x004D, + 0x1FA8, 0x1F84, 0x044D, 0x04DC, 0x1FE4, 0x1F79, 0x004E, + 0x1FB4, 0x1F71, 0x0425, 0x04FA, 0x0000, 0x1F6D, 0x004F, + 0x1FC0, 0x1F5F, 0x03FB, 0x0516, 0x001E, 0x1F62, 0x0050, + 0x1FCB, 0x1F4F, 0x03D1, 0x0531, 0x003D, 0x1F57, 0x0050, + 0x1FD7, 0x1F41, 0x03A5, 0x0548, 0x005E, 0x1F4D, 0x0050, + 0x1FE2, 0x1F34, 0x037A, 0x055E, 0x0080, 0x1F43, 0x004F, + 0x1FEC, 0x1F2A, 0x034D, 0x0572, 0x00A4, 0x1F39, 0x004E, + 0x1FF6, 0x1F20, 0x0321, 0x0584, 0x00C9, 0x1F30, 0x004C, + 0x0000, 0x1F19, 0x02F4, 0x0593, 0x00EF, 0x1F28, 0x0049, + 0x0009, 0x1F13, 0x02C6, 0x05A1, 0x0116, 0x1F21, 0x0046, + 0x0012, 0x1F0F, 0x0299, 0x05AA, 0x013E, 0x1F1B, 0x0043, + 0x001A, 0x1F0C, 0x026C, 0x05B3, 0x0167, 0x1F15, 0x003F, + 0x0021, 0x1F0A, 0x023F, 0x05BA, 0x0191, 0x1F11, 0x003A, + 0x0028, 0x1F0A, 0x0213, 0x05BD, 0x01BC, 0x1F0D, 0x0035, + }, + [HS_LT_15_16_SCALE] = { + /* Luma */ + 0x005B, 0x1F0A, 0x0195, 0x060C, 0x0195, 0x1F0A, 0x005B, + 0x005D, 0x1F13, 0x0166, 0x0609, 0x01C6, 0x1F03, 0x0058, + 0x005F, 0x1F1C, 0x0138, 0x0605, 0x01F7, 0x1EFD, 0x0054, + 0x0060, 0x1F26, 0x010B, 0x05FF, 0x0229, 0x1EF8, 0x004F, + 0x0060, 0x1F31, 0x00DF, 0x05F5, 0x025C, 0x1EF5, 0x004A, + 0x0060, 0x1F3D, 0x00B5, 0x05E8, 0x028F, 0x1EF3, 0x0044, + 0x005F, 0x1F49, 0x008C, 0x05DA, 0x02C3, 0x1EF2, 0x003D, + 0x005E, 0x1F56, 0x0065, 0x05C7, 0x02F6, 0x1EF4, 0x0036, + 0x005C, 0x1F63, 0x003F, 0x05B3, 0x032B, 0x1EF7, 0x002D, + 0x0059, 0x1F71, 0x001B, 0x059D, 0x035F, 0x1EFB, 0x0024, + 0x0057, 0x1F7F, 0x1FF9, 0x0583, 0x0392, 0x1F02, 0x001A, + 0x0053, 0x1F8D, 0x1FD9, 0x0567, 0x03C5, 0x1F0B, 0x0010, + 0x0050, 0x1F9B, 0x1FBB, 0x0548, 0x03F8, 0x1F15, 0x0005, + 0x004C, 0x1FA9, 0x1F9E, 0x0528, 0x042A, 0x1F22, 0x1FF9, + 0x0048, 0x1FB7, 0x1F84, 0x0505, 0x045A, 0x1F31, 0x1FED, + 0x0043, 0x1FC5, 0x1F6C, 0x04E0, 0x048A, 0x1F42, 0x1FE0, + 0x1FD1, 0x1F50, 0x04DF, 0x04DF, 0x1F50, 0x1FD1, 0x0000, + 0x1FE0, 0x1F42, 0x048A, 0x04E0, 0x1F6C, 0x1FC5, 0x0043, + 0x1FED, 0x1F31, 0x045A, 0x0505, 0x1F84, 0x1FB7, 0x0048, + 0x1FF9, 0x1F22, 0x042A, 0x0528, 0x1F9E, 0x1FA9, 0x004C, + 0x0005, 0x1F15, 0x03F8, 0x0548, 0x1FBB, 0x1F9B, 0x0050, + 0x0010, 0x1F0B, 0x03C5, 0x0567, 0x1FD9, 0x1F8D, 0x0053, + 0x001A, 0x1F02, 0x0392, 0x0583, 0x1FF9, 0x1F7F, 0x0057, + 0x0024, 0x1EFB, 0x035F, 0x059D, 0x001B, 0x1F71, 0x0059, + 0x002D, 0x1EF7, 0x032B, 0x05B3, 0x003F, 0x1F63, 0x005C, + 0x0036, 0x1EF4, 0x02F6, 0x05C7, 0x0065, 0x1F56, 0x005E, + 0x003D, 0x1EF2, 0x02C3, 0x05DA, 0x008C, 0x1F49, 0x005F, + 0x0044, 0x1EF3, 0x028F, 0x05E8, 0x00B5, 0x1F3D, 0x0060, + 0x004A, 0x1EF5, 0x025C, 0x05F5, 0x00DF, 0x1F31, 0x0060, + 0x004F, 0x1EF8, 0x0229, 0x05FF, 0x010B, 0x1F26, 0x0060, + 0x0054, 0x1EFD, 0x01F7, 0x0605, 0x0138, 0x1F1C, 0x005F, + 0x0058, 0x1F03, 0x01C6, 0x0609, 0x0166, 0x1F13, 0x005D, + /* Chroma */ + 0x005B, 0x1F0A, 0x0195, 0x060C, 0x0195, 0x1F0A, 0x005B, + 0x005D, 0x1F13, 0x0166, 0x0609, 0x01C6, 0x1F03, 0x0058, + 0x005F, 0x1F1C, 0x0138, 0x0605, 0x01F7, 0x1EFD, 0x0054, + 0x0060, 0x1F26, 0x010B, 0x05FF, 0x0229, 0x1EF8, 0x004F, + 0x0060, 0x1F31, 0x00DF, 0x05F5, 0x025C, 0x1EF5, 0x004A, + 0x0060, 0x1F3D, 0x00B5, 0x05E8, 0x028F, 0x1EF3, 0x0044, + 0x005F, 0x1F49, 0x008C, 0x05DA, 0x02C3, 0x1EF2, 0x003D, + 0x005E, 0x1F56, 0x0065, 0x05C7, 0x02F6, 0x1EF4, 0x0036, + 0x005C, 0x1F63, 0x003F, 0x05B3, 0x032B, 0x1EF7, 0x002D, + 0x0059, 0x1F71, 0x001B, 0x059D, 0x035F, 0x1EFB, 0x0024, + 0x0057, 0x1F7F, 0x1FF9, 0x0583, 0x0392, 0x1F02, 0x001A, + 0x0053, 0x1F8D, 0x1FD9, 0x0567, 0x03C5, 0x1F0B, 0x0010, + 0x0050, 0x1F9B, 0x1FBB, 0x0548, 0x03F8, 0x1F15, 0x0005, + 0x004C, 0x1FA9, 0x1F9E, 0x0528, 0x042A, 0x1F22, 0x1FF9, + 0x0048, 0x1FB7, 0x1F84, 0x0505, 0x045A, 0x1F31, 0x1FED, + 0x0043, 0x1FC5, 0x1F6C, 0x04E0, 0x048A, 0x1F42, 0x1FE0, + 0x1FD1, 0x1F50, 0x04DF, 0x04DF, 0x1F50, 0x1FD1, 0x0000, + 0x1FE0, 0x1F42, 0x048A, 0x04E0, 0x1F6C, 0x1FC5, 0x0043, + 0x1FED, 0x1F31, 0x045A, 0x0505, 0x1F84, 0x1FB7, 0x0048, + 0x1FF9, 0x1F22, 0x042A, 0x0528, 0x1F9E, 0x1FA9, 0x004C, + 0x0005, 0x1F15, 0x03F8, 0x0548, 0x1FBB, 0x1F9B, 0x0050, + 0x0010, 0x1F0B, 0x03C5, 0x0567, 0x1FD9, 0x1F8D, 0x0053, + 0x001A, 0x1F02, 0x0392, 0x0583, 0x1FF9, 0x1F7F, 0x0057, + 0x0024, 0x1EFB, 0x035F, 0x059D, 0x001B, 0x1F71, 0x0059, + 0x002D, 0x1EF7, 0x032B, 0x05B3, 0x003F, 0x1F63, 0x005C, + 0x0036, 0x1EF4, 0x02F6, 0x05C7, 0x0065, 0x1F56, 0x005E, + 0x003D, 0x1EF2, 0x02C3, 0x05DA, 0x008C, 0x1F49, 0x005F, + 0x0044, 0x1EF3, 0x028F, 0x05E8, 0x00B5, 0x1F3D, 0x0060, + 0x004A, 0x1EF5, 0x025C, 0x05F5, 0x00DF, 0x1F31, 0x0060, + 0x004F, 0x1EF8, 0x0229, 0x05FF, 0x010B, 0x1F26, 0x0060, + 0x0054, 0x1EFD, 0x01F7, 0x0605, 0x0138, 0x1F1C, 0x005F, + 0x0058, 0x1F03, 0x01C6, 0x0609, 0x0166, 0x1F13, 0x005D, + }, + [HS_LE_16_16_SCALE] = { + /* Luma */ + 0x006E, 0x1F24, 0x013E, 0x0660, 0x013E, 0x1F24, 0x006E, + 0x006C, 0x1F33, 0x010B, 0x065D, 0x0172, 0x1F17, 0x0070, + 0x0069, 0x1F41, 0x00DA, 0x0659, 0x01A8, 0x1F0B, 0x0070, + 0x0066, 0x1F51, 0x00AA, 0x0650, 0x01DF, 0x1F00, 0x0070, + 0x0062, 0x1F61, 0x007D, 0x0644, 0x0217, 0x1EF6, 0x006F, + 0x005E, 0x1F71, 0x0051, 0x0636, 0x0250, 0x1EED, 0x006D, + 0x0059, 0x1F81, 0x0028, 0x0624, 0x028A, 0x1EE5, 0x006B, + 0x0054, 0x1F91, 0x0000, 0x060F, 0x02C5, 0x1EE0, 0x0067, + 0x004E, 0x1FA2, 0x1FDB, 0x05F6, 0x0300, 0x1EDC, 0x0063, + 0x0049, 0x1FB2, 0x1FB8, 0x05DB, 0x033B, 0x1EDA, 0x005D, + 0x0043, 0x1FC3, 0x1F98, 0x05BC, 0x0376, 0x1ED9, 0x0057, + 0x003D, 0x1FD3, 0x1F7A, 0x059B, 0x03B1, 0x1EDB, 0x004F, + 0x0036, 0x1FE2, 0x1F5E, 0x0578, 0x03EC, 0x1EDF, 0x0047, + 0x0030, 0x1FF1, 0x1F45, 0x0551, 0x0426, 0x1EE6, 0x003D, + 0x002A, 0x0000, 0x1F2E, 0x0528, 0x045F, 0x1EEE, 0x0033, + 0x0023, 0x000E, 0x1F19, 0x04FD, 0x0498, 0x1EFA, 0x0027, + 0x001B, 0x1F04, 0x04E1, 0x04E1, 0x1F04, 0x001B, 0x0000, + 0x0027, 0x1EFA, 0x0498, 0x04FD, 0x1F19, 0x000E, 0x0023, + 0x0033, 0x1EEE, 0x045F, 0x0528, 0x1F2E, 0x0000, 0x002A, + 0x003D, 0x1EE6, 0x0426, 0x0551, 0x1F45, 0x1FF1, 0x0030, + 0x0047, 0x1EDF, 0x03EC, 0x0578, 0x1F5E, 0x1FE2, 0x0036, + 0x004F, 0x1EDB, 0x03B1, 0x059B, 0x1F7A, 0x1FD3, 0x003D, + 0x0057, 0x1ED9, 0x0376, 0x05BC, 0x1F98, 0x1FC3, 0x0043, + 0x005D, 0x1EDA, 0x033B, 0x05DB, 0x1FB8, 0x1FB2, 0x0049, + 0x0063, 0x1EDC, 0x0300, 0x05F6, 0x1FDB, 0x1FA2, 0x004E, + 0x0067, 0x1EE0, 0x02C5, 0x060F, 0x0000, 0x1F91, 0x0054, + 0x006B, 0x1EE5, 0x028A, 0x0624, 0x0028, 0x1F81, 0x0059, + 0x006D, 0x1EED, 0x0250, 0x0636, 0x0051, 0x1F71, 0x005E, + 0x006F, 0x1EF6, 0x0217, 0x0644, 0x007D, 0x1F61, 0x0062, + 0x0070, 0x1F00, 0x01DF, 0x0650, 0x00AA, 0x1F51, 0x0066, + 0x0070, 0x1F0B, 0x01A8, 0x0659, 0x00DA, 0x1F41, 0x0069, + 0x0070, 0x1F17, 0x0172, 0x065D, 0x010B, 0x1F33, 0x006C, + /* Chroma */ + 0x006E, 0x1F24, 0x013E, 0x0660, 0x013E, 0x1F24, 0x006E, + 0x006C, 0x1F33, 0x010B, 0x065D, 0x0172, 0x1F17, 0x0070, + 0x0069, 0x1F41, 0x00DA, 0x0659, 0x01A8, 0x1F0B, 0x0070, + 0x0066, 0x1F51, 0x00AA, 0x0650, 0x01DF, 0x1F00, 0x0070, + 0x0062, 0x1F61, 0x007D, 0x0644, 0x0217, 0x1EF6, 0x006F, + 0x005E, 0x1F71, 0x0051, 0x0636, 0x0250, 0x1EED, 0x006D, + 0x0059, 0x1F81, 0x0028, 0x0624, 0x028A, 0x1EE5, 0x006B, + 0x0054, 0x1F91, 0x0000, 0x060F, 0x02C5, 0x1EE0, 0x0067, + 0x004E, 0x1FA2, 0x1FDB, 0x05F6, 0x0300, 0x1EDC, 0x0063, + 0x0049, 0x1FB2, 0x1FB8, 0x05DB, 0x033B, 0x1EDA, 0x005D, + 0x0043, 0x1FC3, 0x1F98, 0x05BC, 0x0376, 0x1ED9, 0x0057, + 0x003D, 0x1FD3, 0x1F7A, 0x059B, 0x03B1, 0x1EDB, 0x004F, + 0x0036, 0x1FE2, 0x1F5E, 0x0578, 0x03EC, 0x1EDF, 0x0047, + 0x0030, 0x1FF1, 0x1F45, 0x0551, 0x0426, 0x1EE6, 0x003D, + 0x002A, 0x0000, 0x1F2E, 0x0528, 0x045F, 0x1EEE, 0x0033, + 0x0023, 0x000E, 0x1F19, 0x04FD, 0x0498, 0x1EFA, 0x0027, + 0x001B, 0x1F04, 0x04E1, 0x04E1, 0x1F04, 0x001B, 0x0000, + 0x0027, 0x1EFA, 0x0498, 0x04FD, 0x1F19, 0x000E, 0x0023, + 0x0033, 0x1EEE, 0x045F, 0x0528, 0x1F2E, 0x0000, 0x002A, + 0x003D, 0x1EE6, 0x0426, 0x0551, 0x1F45, 0x1FF1, 0x0030, + 0x0047, 0x1EDF, 0x03EC, 0x0578, 0x1F5E, 0x1FE2, 0x0036, + 0x004F, 0x1EDB, 0x03B1, 0x059B, 0x1F7A, 0x1FD3, 0x003D, + 0x0057, 0x1ED9, 0x0376, 0x05BC, 0x1F98, 0x1FC3, 0x0043, + 0x005D, 0x1EDA, 0x033B, 0x05DB, 0x1FB8, 0x1FB2, 0x0049, + 0x0063, 0x1EDC, 0x0300, 0x05F6, 0x1FDB, 0x1FA2, 0x004E, + 0x0067, 0x1EE0, 0x02C5, 0x060F, 0x0000, 0x1F91, 0x0054, + 0x006B, 0x1EE5, 0x028A, 0x0624, 0x0028, 0x1F81, 0x0059, + 0x006D, 0x1EED, 0x0250, 0x0636, 0x0051, 0x1F71, 0x005E, + 0x006F, 0x1EF6, 0x0217, 0x0644, 0x007D, 0x1F61, 0x0062, + 0x0070, 0x1F00, 0x01DF, 0x0650, 0x00AA, 0x1F51, 0x0066, + 0x0070, 0x1F0B, 0x01A8, 0x0659, 0x00DA, 0x1F41, 0x0069, + 0x0070, 0x1F17, 0x0172, 0x065D, 0x010B, 0x1F33, 0x006C, + }, +}; + +/* vertical scaler coefficients */ +enum { + VS_UP_SCALE = 0, + VS_LT_9_16_SCALE, + VS_LT_10_16_SCALE, + VS_LT_11_16_SCALE, + VS_LT_12_16_SCALE, + VS_LT_13_16_SCALE, + VS_LT_14_16_SCALE, + VS_LT_15_16_SCALE, + VS_LT_16_16_SCALE, + VS_1_TO_1_SCALE, +}; + +static const u16 scaler_vs_coeffs[15][SC_NUM_PHASES * 2 * SC_V_NUM_TAPS] = { + [VS_UP_SCALE] = { + /* Luma */ + 0x1FD1, 0x00B1, 0x06FC, 0x00B1, 0x1FD1, + 0x1FD8, 0x0085, 0x06F9, 0x00E1, 0x1FC9, + 0x1FDF, 0x005B, 0x06F2, 0x0114, 0x1FC0, + 0x1FE5, 0x0035, 0x06E5, 0x014A, 0x1FB7, + 0x1FEB, 0x0012, 0x06D3, 0x0182, 0x1FAE, + 0x1FF1, 0x1FF3, 0x06BA, 0x01BD, 0x1FA5, + 0x1FF5, 0x1FD7, 0x069D, 0x01FB, 0x1F9C, + 0x1FF9, 0x1FBE, 0x067C, 0x023A, 0x1F93, + 0x1FFD, 0x1FA8, 0x0656, 0x027B, 0x1F8A, + 0x0000, 0x1F95, 0x062B, 0x02BF, 0x1F81, + 0x0002, 0x1F86, 0x05FC, 0x0303, 0x1F79, + 0x0004, 0x1F79, 0x05CA, 0x0347, 0x1F72, + 0x0005, 0x1F6F, 0x0594, 0x038D, 0x1F6B, + 0x0006, 0x1F67, 0x055B, 0x03D2, 0x1F66, + 0x0007, 0x1F62, 0x051E, 0x0417, 0x1F62, + 0x0007, 0x1F5F, 0x04DF, 0x045C, 0x1F5F, + 0x1F5E, 0x04A2, 0x04A2, 0x1F5E, 0x0000, + 0x1F5F, 0x045C, 0x04DF, 0x1F5F, 0x0007, + 0x1F62, 0x0417, 0x051E, 0x1F62, 0x0007, + 0x1F66, 0x03D2, 0x055B, 0x1F67, 0x0006, + 0x1F6B, 0x038D, 0x0594, 0x1F6F, 0x0005, + 0x1F72, 0x0347, 0x05CA, 0x1F79, 0x0004, + 0x1F79, 0x0303, 0x05FC, 0x1F86, 0x0002, + 0x1F81, 0x02BF, 0x062B, 0x1F95, 0x0000, + 0x1F8A, 0x027B, 0x0656, 0x1FA8, 0x1FFD, + 0x1F93, 0x023A, 0x067C, 0x1FBE, 0x1FF9, + 0x1F9C, 0x01FB, 0x069D, 0x1FD7, 0x1FF5, + 0x1FA5, 0x01BD, 0x06BA, 0x1FF3, 0x1FF1, + 0x1FAE, 0x0182, 0x06D3, 0x0012, 0x1FEB, + 0x1FB7, 0x014A, 0x06E5, 0x0035, 0x1FE5, + 0x1FC0, 0x0114, 0x06F2, 0x005B, 0x1FDF, + 0x1FC9, 0x00E1, 0x06F9, 0x0085, 0x1FD8, + /* Chroma */ + 0x1FD1, 0x00B1, 0x06FC, 0x00B1, 0x1FD1, + 0x1FD8, 0x0085, 0x06F9, 0x00E1, 0x1FC9, + 0x1FDF, 0x005B, 0x06F2, 0x0114, 0x1FC0, + 0x1FE5, 0x0035, 0x06E5, 0x014A, 0x1FB7, + 0x1FEB, 0x0012, 0x06D3, 0x0182, 0x1FAE, + 0x1FF1, 0x1FF3, 0x06BA, 0x01BD, 0x1FA5, + 0x1FF5, 0x1FD7, 0x069D, 0x01FB, 0x1F9C, + 0x1FF9, 0x1FBE, 0x067C, 0x023A, 0x1F93, + 0x1FFD, 0x1FA8, 0x0656, 0x027B, 0x1F8A, + 0x0000, 0x1F95, 0x062B, 0x02BF, 0x1F81, + 0x0002, 0x1F86, 0x05FC, 0x0303, 0x1F79, + 0x0004, 0x1F79, 0x05CA, 0x0347, 0x1F72, + 0x0005, 0x1F6F, 0x0594, 0x038D, 0x1F6B, + 0x0006, 0x1F67, 0x055B, 0x03D2, 0x1F66, + 0x0007, 0x1F62, 0x051E, 0x0417, 0x1F62, + 0x0007, 0x1F5F, 0x04DF, 0x045C, 0x1F5F, + 0x1F5E, 0x04A2, 0x04A2, 0x1F5E, 0x0000, + 0x1F5F, 0x045C, 0x04DF, 0x1F5F, 0x0007, + 0x1F62, 0x0417, 0x051E, 0x1F62, 0x0007, + 0x1F66, 0x03D2, 0x055B, 0x1F67, 0x0006, + 0x1F6B, 0x038D, 0x0594, 0x1F6F, 0x0005, + 0x1F72, 0x0347, 0x05CA, 0x1F79, 0x0004, + 0x1F79, 0x0303, 0x05FC, 0x1F86, 0x0002, + 0x1F81, 0x02BF, 0x062B, 0x1F95, 0x0000, + 0x1F8A, 0x027B, 0x0656, 0x1FA8, 0x1FFD, + 0x1F93, 0x023A, 0x067C, 0x1FBE, 0x1FF9, + 0x1F9C, 0x01FB, 0x069D, 0x1FD7, 0x1FF5, + 0x1FA5, 0x01BD, 0x06BA, 0x1FF3, 0x1FF1, + 0x1FAE, 0x0182, 0x06D3, 0x0012, 0x1FEB, + 0x1FB7, 0x014A, 0x06E5, 0x0035, 0x1FE5, + 0x1FC0, 0x0114, 0x06F2, 0x005B, 0x1FDF, + 0x1FC9, 0x00E1, 0x06F9, 0x0085, 0x1FD8, + }, + [VS_LT_9_16_SCALE] = { + /* Luma */ + 0x001C, 0x01F6, 0x03DC, 0x01F6, 0x001C, + 0x0018, 0x01DF, 0x03DB, 0x020C, 0x0022, + 0x0013, 0x01C9, 0x03D9, 0x0223, 0x0028, + 0x000F, 0x01B3, 0x03D6, 0x023A, 0x002E, + 0x000C, 0x019D, 0x03D2, 0x0250, 0x0035, + 0x0009, 0x0188, 0x03CC, 0x0266, 0x003D, + 0x0006, 0x0173, 0x03C5, 0x027D, 0x0045, + 0x0004, 0x015E, 0x03BD, 0x0293, 0x004E, + 0x0002, 0x014A, 0x03B4, 0x02A8, 0x0058, + 0x0000, 0x0136, 0x03AA, 0x02BE, 0x0062, + 0x1FFF, 0x0123, 0x039E, 0x02D3, 0x006D, + 0x1FFE, 0x0110, 0x0392, 0x02E8, 0x0078, + 0x1FFD, 0x00FE, 0x0384, 0x02FC, 0x0085, + 0x1FFD, 0x00ED, 0x0376, 0x030F, 0x0091, + 0x1FFC, 0x00DC, 0x0367, 0x0322, 0x009F, + 0x1FFC, 0x00CC, 0x0357, 0x0334, 0x00AD, + 0x00BC, 0x0344, 0x0344, 0x00BC, 0x0000, + 0x00AD, 0x0334, 0x0357, 0x00CC, 0x1FFC, + 0x009F, 0x0322, 0x0367, 0x00DC, 0x1FFC, + 0x0091, 0x030F, 0x0376, 0x00ED, 0x1FFD, + 0x0085, 0x02FC, 0x0384, 0x00FE, 0x1FFD, + 0x0078, 0x02E8, 0x0392, 0x0110, 0x1FFE, + 0x006D, 0x02D3, 0x039E, 0x0123, 0x1FFF, + 0x0062, 0x02BE, 0x03AA, 0x0136, 0x0000, + 0x0058, 0x02A8, 0x03B4, 0x014A, 0x0002, + 0x004E, 0x0293, 0x03BD, 0x015E, 0x0004, + 0x0045, 0x027D, 0x03C5, 0x0173, 0x0006, + 0x003D, 0x0266, 0x03CC, 0x0188, 0x0009, + 0x0035, 0x0250, 0x03D2, 0x019D, 0x000C, + 0x002E, 0x023A, 0x03D6, 0x01B3, 0x000F, + 0x0028, 0x0223, 0x03D9, 0x01C9, 0x0013, + 0x0022, 0x020C, 0x03DB, 0x01DF, 0x0018, + /* Chroma */ + 0x001C, 0x01F6, 0x03DC, 0x01F6, 0x001C, + 0x0018, 0x01DF, 0x03DB, 0x020C, 0x0022, + 0x0013, 0x01C9, 0x03D9, 0x0223, 0x0028, + 0x000F, 0x01B3, 0x03D6, 0x023A, 0x002E, + 0x000C, 0x019D, 0x03D2, 0x0250, 0x0035, + 0x0009, 0x0188, 0x03CC, 0x0266, 0x003D, + 0x0006, 0x0173, 0x03C5, 0x027D, 0x0045, + 0x0004, 0x015E, 0x03BD, 0x0293, 0x004E, + 0x0002, 0x014A, 0x03B4, 0x02A8, 0x0058, + 0x0000, 0x0136, 0x03AA, 0x02BE, 0x0062, + 0x1FFF, 0x0123, 0x039E, 0x02D3, 0x006D, + 0x1FFE, 0x0110, 0x0392, 0x02E8, 0x0078, + 0x1FFD, 0x00FE, 0x0384, 0x02FC, 0x0085, + 0x1FFD, 0x00ED, 0x0376, 0x030F, 0x0091, + 0x1FFC, 0x00DC, 0x0367, 0x0322, 0x009F, + 0x1FFC, 0x00CC, 0x0357, 0x0334, 0x00AD, + 0x00BC, 0x0344, 0x0344, 0x00BC, 0x0000, + 0x00AD, 0x0334, 0x0357, 0x00CC, 0x1FFC, + 0x009F, 0x0322, 0x0367, 0x00DC, 0x1FFC, + 0x0091, 0x030F, 0x0376, 0x00ED, 0x1FFD, + 0x0085, 0x02FC, 0x0384, 0x00FE, 0x1FFD, + 0x0078, 0x02E8, 0x0392, 0x0110, 0x1FFE, + 0x006D, 0x02D3, 0x039E, 0x0123, 0x1FFF, + 0x0062, 0x02BE, 0x03AA, 0x0136, 0x0000, + 0x0058, 0x02A8, 0x03B4, 0x014A, 0x0002, + 0x004E, 0x0293, 0x03BD, 0x015E, 0x0004, + 0x0045, 0x027D, 0x03C5, 0x0173, 0x0006, + 0x003D, 0x0266, 0x03CC, 0x0188, 0x0009, + 0x0035, 0x0250, 0x03D2, 0x019D, 0x000C, + 0x002E, 0x023A, 0x03D6, 0x01B3, 0x000F, + 0x0028, 0x0223, 0x03D9, 0x01C9, 0x0013, + 0x0022, 0x020C, 0x03DB, 0x01DF, 0x0018, + }, + [VS_LT_10_16_SCALE] = { + /* Luma */ + 0x0003, 0x01E9, 0x0428, 0x01E9, 0x0003, + 0x0000, 0x01D0, 0x0426, 0x0203, 0x0007, + 0x1FFD, 0x01B7, 0x0424, 0x021C, 0x000C, + 0x1FFB, 0x019E, 0x0420, 0x0236, 0x0011, + 0x1FF9, 0x0186, 0x041A, 0x0250, 0x0017, + 0x1FF7, 0x016E, 0x0414, 0x026A, 0x001D, + 0x1FF6, 0x0157, 0x040B, 0x0284, 0x0024, + 0x1FF5, 0x0140, 0x0401, 0x029E, 0x002C, + 0x1FF4, 0x012A, 0x03F6, 0x02B7, 0x0035, + 0x1FF4, 0x0115, 0x03E9, 0x02D0, 0x003E, + 0x1FF4, 0x0100, 0x03DB, 0x02E9, 0x0048, + 0x1FF4, 0x00EC, 0x03CC, 0x0301, 0x0053, + 0x1FF4, 0x00D9, 0x03BC, 0x0318, 0x005F, + 0x1FF5, 0x00C7, 0x03AA, 0x032F, 0x006B, + 0x1FF6, 0x00B5, 0x0398, 0x0345, 0x0078, + 0x1FF6, 0x00A5, 0x0384, 0x035B, 0x0086, + 0x0094, 0x036C, 0x036C, 0x0094, 0x0000, + 0x0086, 0x035B, 0x0384, 0x00A5, 0x1FF6, + 0x0078, 0x0345, 0x0398, 0x00B5, 0x1FF6, + 0x006B, 0x032F, 0x03AA, 0x00C7, 0x1FF5, + 0x005F, 0x0318, 0x03BC, 0x00D9, 0x1FF4, + 0x0053, 0x0301, 0x03CC, 0x00EC, 0x1FF4, + 0x0048, 0x02E9, 0x03DB, 0x0100, 0x1FF4, + 0x003E, 0x02D0, 0x03E9, 0x0115, 0x1FF4, + 0x0035, 0x02B7, 0x03F6, 0x012A, 0x1FF4, + 0x002C, 0x029E, 0x0401, 0x0140, 0x1FF5, + 0x0024, 0x0284, 0x040B, 0x0157, 0x1FF6, + 0x001D, 0x026A, 0x0414, 0x016E, 0x1FF7, + 0x0017, 0x0250, 0x041A, 0x0186, 0x1FF9, + 0x0011, 0x0236, 0x0420, 0x019E, 0x1FFB, + 0x000C, 0x021C, 0x0424, 0x01B7, 0x1FFD, + 0x0007, 0x0203, 0x0426, 0x01D0, 0x0000, + /* Chroma */ + 0x0003, 0x01E9, 0x0428, 0x01E9, 0x0003, + 0x0000, 0x01D0, 0x0426, 0x0203, 0x0007, + 0x1FFD, 0x01B7, 0x0424, 0x021C, 0x000C, + 0x1FFB, 0x019E, 0x0420, 0x0236, 0x0011, + 0x1FF9, 0x0186, 0x041A, 0x0250, 0x0017, + 0x1FF7, 0x016E, 0x0414, 0x026A, 0x001D, + 0x1FF6, 0x0157, 0x040B, 0x0284, 0x0024, + 0x1FF5, 0x0140, 0x0401, 0x029E, 0x002C, + 0x1FF4, 0x012A, 0x03F6, 0x02B7, 0x0035, + 0x1FF4, 0x0115, 0x03E9, 0x02D0, 0x003E, + 0x1FF4, 0x0100, 0x03DB, 0x02E9, 0x0048, + 0x1FF4, 0x00EC, 0x03CC, 0x0301, 0x0053, + 0x1FF4, 0x00D9, 0x03BC, 0x0318, 0x005F, + 0x1FF5, 0x00C7, 0x03AA, 0x032F, 0x006B, + 0x1FF6, 0x00B5, 0x0398, 0x0345, 0x0078, + 0x1FF6, 0x00A5, 0x0384, 0x035B, 0x0086, + 0x0094, 0x036C, 0x036C, 0x0094, 0x0000, + 0x0086, 0x035B, 0x0384, 0x00A5, 0x1FF6, + 0x0078, 0x0345, 0x0398, 0x00B5, 0x1FF6, + 0x006B, 0x032F, 0x03AA, 0x00C7, 0x1FF5, + 0x005F, 0x0318, 0x03BC, 0x00D9, 0x1FF4, + 0x0053, 0x0301, 0x03CC, 0x00EC, 0x1FF4, + 0x0048, 0x02E9, 0x03DB, 0x0100, 0x1FF4, + 0x003E, 0x02D0, 0x03E9, 0x0115, 0x1FF4, + 0x0035, 0x02B7, 0x03F6, 0x012A, 0x1FF4, + 0x002C, 0x029E, 0x0401, 0x0140, 0x1FF5, + 0x0024, 0x0284, 0x040B, 0x0157, 0x1FF6, + 0x001D, 0x026A, 0x0414, 0x016E, 0x1FF7, + 0x0017, 0x0250, 0x041A, 0x0186, 0x1FF9, + 0x0011, 0x0236, 0x0420, 0x019E, 0x1FFB, + 0x000C, 0x021C, 0x0424, 0x01B7, 0x1FFD, + 0x0007, 0x0203, 0x0426, 0x01D0, 0x0000, + }, + [VS_LT_11_16_SCALE] = { + /* Luma */ + 0x1FEC, 0x01D6, 0x047C, 0x01D6, 0x1FEC, + 0x1FEA, 0x01BA, 0x047B, 0x01F3, 0x1FEE, + 0x1FE9, 0x019D, 0x0478, 0x0211, 0x1FF1, + 0x1FE8, 0x0182, 0x0473, 0x022E, 0x1FF5, + 0x1FE8, 0x0167, 0x046C, 0x024C, 0x1FF9, + 0x1FE8, 0x014D, 0x0464, 0x026A, 0x1FFD, + 0x1FE8, 0x0134, 0x0459, 0x0288, 0x0003, + 0x1FE9, 0x011B, 0x044D, 0x02A6, 0x0009, + 0x1FE9, 0x0104, 0x0440, 0x02C3, 0x0010, + 0x1FEA, 0x00ED, 0x0430, 0x02E1, 0x0018, + 0x1FEB, 0x00D7, 0x0420, 0x02FD, 0x0021, + 0x1FED, 0x00C2, 0x040D, 0x0319, 0x002B, + 0x1FEE, 0x00AE, 0x03F9, 0x0336, 0x0035, + 0x1FF0, 0x009C, 0x03E3, 0x0350, 0x0041, + 0x1FF1, 0x008A, 0x03CD, 0x036B, 0x004D, + 0x1FF3, 0x0079, 0x03B5, 0x0384, 0x005B, + 0x0069, 0x0397, 0x0397, 0x0069, 0x0000, + 0x005B, 0x0384, 0x03B5, 0x0079, 0x1FF3, + 0x004D, 0x036B, 0x03CD, 0x008A, 0x1FF1, + 0x0041, 0x0350, 0x03E3, 0x009C, 0x1FF0, + 0x0035, 0x0336, 0x03F9, 0x00AE, 0x1FEE, + 0x002B, 0x0319, 0x040D, 0x00C2, 0x1FED, + 0x0021, 0x02FD, 0x0420, 0x00D7, 0x1FEB, + 0x0018, 0x02E1, 0x0430, 0x00ED, 0x1FEA, + 0x0010, 0x02C3, 0x0440, 0x0104, 0x1FE9, + 0x0009, 0x02A6, 0x044D, 0x011B, 0x1FE9, + 0x0003, 0x0288, 0x0459, 0x0134, 0x1FE8, + 0x1FFD, 0x026A, 0x0464, 0x014D, 0x1FE8, + 0x1FF9, 0x024C, 0x046C, 0x0167, 0x1FE8, + 0x1FF5, 0x022E, 0x0473, 0x0182, 0x1FE8, + 0x1FF1, 0x0211, 0x0478, 0x019D, 0x1FE9, + 0x1FEE, 0x01F3, 0x047B, 0x01BA, 0x1FEA, + /* Chroma */ + 0x1FEC, 0x01D6, 0x047C, 0x01D6, 0x1FEC, + 0x1FEA, 0x01BA, 0x047B, 0x01F3, 0x1FEE, + 0x1FE9, 0x019D, 0x0478, 0x0211, 0x1FF1, + 0x1FE8, 0x0182, 0x0473, 0x022E, 0x1FF5, + 0x1FE8, 0x0167, 0x046C, 0x024C, 0x1FF9, + 0x1FE8, 0x014D, 0x0464, 0x026A, 0x1FFD, + 0x1FE8, 0x0134, 0x0459, 0x0288, 0x0003, + 0x1FE9, 0x011B, 0x044D, 0x02A6, 0x0009, + 0x1FE9, 0x0104, 0x0440, 0x02C3, 0x0010, + 0x1FEA, 0x00ED, 0x0430, 0x02E1, 0x0018, + 0x1FEB, 0x00D7, 0x0420, 0x02FD, 0x0021, + 0x1FED, 0x00C2, 0x040D, 0x0319, 0x002B, + 0x1FEE, 0x00AE, 0x03F9, 0x0336, 0x0035, + 0x1FF0, 0x009C, 0x03E3, 0x0350, 0x0041, + 0x1FF1, 0x008A, 0x03CD, 0x036B, 0x004D, + 0x1FF3, 0x0079, 0x03B5, 0x0384, 0x005B, + 0x0069, 0x0397, 0x0397, 0x0069, 0x0000, + 0x005B, 0x0384, 0x03B5, 0x0079, 0x1FF3, + 0x004D, 0x036B, 0x03CD, 0x008A, 0x1FF1, + 0x0041, 0x0350, 0x03E3, 0x009C, 0x1FF0, + 0x0035, 0x0336, 0x03F9, 0x00AE, 0x1FEE, + 0x002B, 0x0319, 0x040D, 0x00C2, 0x1FED, + 0x0021, 0x02FD, 0x0420, 0x00D7, 0x1FEB, + 0x0018, 0x02E1, 0x0430, 0x00ED, 0x1FEA, + 0x0010, 0x02C3, 0x0440, 0x0104, 0x1FE9, + 0x0009, 0x02A6, 0x044D, 0x011B, 0x1FE9, + 0x0003, 0x0288, 0x0459, 0x0134, 0x1FE8, + 0x1FFD, 0x026A, 0x0464, 0x014D, 0x1FE8, + 0x1FF9, 0x024C, 0x046C, 0x0167, 0x1FE8, + 0x1FF5, 0x022E, 0x0473, 0x0182, 0x1FE8, + 0x1FF1, 0x0211, 0x0478, 0x019D, 0x1FE9, + 0x1FEE, 0x01F3, 0x047B, 0x01BA, 0x1FEA, + }, + [VS_LT_12_16_SCALE] = { + /* Luma */ + 0x1FD8, 0x01BC, 0x04D8, 0x01BC, 0x1FD8, + 0x1FD8, 0x019C, 0x04D8, 0x01DC, 0x1FD8, + 0x1FD8, 0x017D, 0x04D4, 0x01FE, 0x1FD9, + 0x1FD9, 0x015E, 0x04CF, 0x0220, 0x1FDA, + 0x1FDB, 0x0141, 0x04C7, 0x0241, 0x1FDC, + 0x1FDC, 0x0125, 0x04BC, 0x0264, 0x1FDF, + 0x1FDE, 0x0109, 0x04B0, 0x0286, 0x1FE3, + 0x1FE0, 0x00EF, 0x04A1, 0x02A9, 0x1FE7, + 0x1FE2, 0x00D6, 0x0491, 0x02CB, 0x1FEC, + 0x1FE4, 0x00BE, 0x047E, 0x02EE, 0x1FF2, + 0x1FE6, 0x00A7, 0x046A, 0x030F, 0x1FFA, + 0x1FE9, 0x0092, 0x0453, 0x0330, 0x0002, + 0x1FEB, 0x007E, 0x043B, 0x0351, 0x000B, + 0x1FED, 0x006B, 0x0421, 0x0372, 0x0015, + 0x1FEF, 0x005A, 0x0406, 0x0391, 0x0020, + 0x1FF1, 0x0049, 0x03EA, 0x03AF, 0x002D, + 0x003A, 0x03C6, 0x03C6, 0x003A, 0x0000, + 0x002D, 0x03AF, 0x03EA, 0x0049, 0x1FF1, + 0x0020, 0x0391, 0x0406, 0x005A, 0x1FEF, + 0x0015, 0x0372, 0x0421, 0x006B, 0x1FED, + 0x000B, 0x0351, 0x043B, 0x007E, 0x1FEB, + 0x0002, 0x0330, 0x0453, 0x0092, 0x1FE9, + 0x1FFA, 0x030F, 0x046A, 0x00A7, 0x1FE6, + 0x1FF2, 0x02EE, 0x047E, 0x00BE, 0x1FE4, + 0x1FEC, 0x02CB, 0x0491, 0x00D6, 0x1FE2, + 0x1FE7, 0x02A9, 0x04A1, 0x00EF, 0x1FE0, + 0x1FE3, 0x0286, 0x04B0, 0x0109, 0x1FDE, + 0x1FDF, 0x0264, 0x04BC, 0x0125, 0x1FDC, + 0x1FDC, 0x0241, 0x04C7, 0x0141, 0x1FDB, + 0x1FDA, 0x0220, 0x04CF, 0x015E, 0x1FD9, + 0x1FD9, 0x01FE, 0x04D4, 0x017D, 0x1FD8, + 0x1FD8, 0x01DC, 0x04D8, 0x019C, 0x1FD8, + /* Chroma */ + 0x1FD8, 0x01BC, 0x04D8, 0x01BC, 0x1FD8, + 0x1FD8, 0x019C, 0x04D8, 0x01DC, 0x1FD8, + 0x1FD8, 0x017D, 0x04D4, 0x01FE, 0x1FD9, + 0x1FD9, 0x015E, 0x04CF, 0x0220, 0x1FDA, + 0x1FDB, 0x0141, 0x04C7, 0x0241, 0x1FDC, + 0x1FDC, 0x0125, 0x04BC, 0x0264, 0x1FDF, + 0x1FDE, 0x0109, 0x04B0, 0x0286, 0x1FE3, + 0x1FE0, 0x00EF, 0x04A1, 0x02A9, 0x1FE7, + 0x1FE2, 0x00D6, 0x0491, 0x02CB, 0x1FEC, + 0x1FE4, 0x00BE, 0x047E, 0x02EE, 0x1FF2, + 0x1FE6, 0x00A7, 0x046A, 0x030F, 0x1FFA, + 0x1FE9, 0x0092, 0x0453, 0x0330, 0x0002, + 0x1FEB, 0x007E, 0x043B, 0x0351, 0x000B, + 0x1FED, 0x006B, 0x0421, 0x0372, 0x0015, + 0x1FEF, 0x005A, 0x0406, 0x0391, 0x0020, + 0x1FF1, 0x0049, 0x03EA, 0x03AF, 0x002D, + 0x003A, 0x03C6, 0x03C6, 0x003A, 0x0000, + 0x002D, 0x03AF, 0x03EA, 0x0049, 0x1FF1, + 0x0020, 0x0391, 0x0406, 0x005A, 0x1FEF, + 0x0015, 0x0372, 0x0421, 0x006B, 0x1FED, + 0x000B, 0x0351, 0x043B, 0x007E, 0x1FEB, + 0x0002, 0x0330, 0x0453, 0x0092, 0x1FE9, + 0x1FFA, 0x030F, 0x046A, 0x00A7, 0x1FE6, + 0x1FF2, 0x02EE, 0x047E, 0x00BE, 0x1FE4, + 0x1FEC, 0x02CB, 0x0491, 0x00D6, 0x1FE2, + 0x1FE7, 0x02A9, 0x04A1, 0x00EF, 0x1FE0, + 0x1FE3, 0x0286, 0x04B0, 0x0109, 0x1FDE, + 0x1FDF, 0x0264, 0x04BC, 0x0125, 0x1FDC, + 0x1FDC, 0x0241, 0x04C7, 0x0141, 0x1FDB, + 0x1FDA, 0x0220, 0x04CF, 0x015E, 0x1FD9, + 0x1FD9, 0x01FE, 0x04D4, 0x017D, 0x1FD8, + 0x1FD8, 0x01DC, 0x04D8, 0x019C, 0x1FD8, + }, + [VS_LT_13_16_SCALE] = { + /* Luma */ + 0x1FC8, 0x0199, 0x053E, 0x0199, 0x1FC8, + 0x1FCA, 0x0175, 0x053E, 0x01BD, 0x1FC6, + 0x1FCD, 0x0153, 0x0539, 0x01E2, 0x1FC5, + 0x1FCF, 0x0132, 0x0532, 0x0209, 0x1FC4, + 0x1FD2, 0x0112, 0x0529, 0x022F, 0x1FC4, + 0x1FD5, 0x00F4, 0x051C, 0x0256, 0x1FC5, + 0x1FD8, 0x00D7, 0x050D, 0x027E, 0x1FC6, + 0x1FDC, 0x00BB, 0x04FB, 0x02A6, 0x1FC8, + 0x1FDF, 0x00A1, 0x04E7, 0x02CE, 0x1FCB, + 0x1FE2, 0x0089, 0x04D1, 0x02F5, 0x1FCF, + 0x1FE5, 0x0072, 0x04B8, 0x031D, 0x1FD4, + 0x1FE8, 0x005D, 0x049E, 0x0344, 0x1FD9, + 0x1FEB, 0x0049, 0x0480, 0x036B, 0x1FE1, + 0x1FEE, 0x0037, 0x0462, 0x0390, 0x1FE9, + 0x1FF0, 0x0026, 0x0442, 0x03B6, 0x1FF2, + 0x1FF2, 0x0017, 0x0420, 0x03DA, 0x1FFD, + 0x0009, 0x03F7, 0x03F7, 0x0009, 0x0000, + 0x1FFD, 0x03DA, 0x0420, 0x0017, 0x1FF2, + 0x1FF2, 0x03B6, 0x0442, 0x0026, 0x1FF0, + 0x1FE9, 0x0390, 0x0462, 0x0037, 0x1FEE, + 0x1FE1, 0x036B, 0x0480, 0x0049, 0x1FEB, + 0x1FD9, 0x0344, 0x049E, 0x005D, 0x1FE8, + 0x1FD4, 0x031D, 0x04B8, 0x0072, 0x1FE5, + 0x1FCF, 0x02F5, 0x04D1, 0x0089, 0x1FE2, + 0x1FCB, 0x02CE, 0x04E7, 0x00A1, 0x1FDF, + 0x1FC8, 0x02A6, 0x04FB, 0x00BB, 0x1FDC, + 0x1FC6, 0x027E, 0x050D, 0x00D7, 0x1FD8, + 0x1FC5, 0x0256, 0x051C, 0x00F4, 0x1FD5, + 0x1FC4, 0x022F, 0x0529, 0x0112, 0x1FD2, + 0x1FC4, 0x0209, 0x0532, 0x0132, 0x1FCF, + 0x1FC5, 0x01E2, 0x0539, 0x0153, 0x1FCD, + 0x1FC6, 0x01BD, 0x053E, 0x0175, 0x1FCA, + /* Chroma */ + 0x1FC8, 0x0199, 0x053E, 0x0199, 0x1FC8, + 0x1FCA, 0x0175, 0x053E, 0x01BD, 0x1FC6, + 0x1FCD, 0x0153, 0x0539, 0x01E2, 0x1FC5, + 0x1FCF, 0x0132, 0x0532, 0x0209, 0x1FC4, + 0x1FD2, 0x0112, 0x0529, 0x022F, 0x1FC4, + 0x1FD5, 0x00F4, 0x051C, 0x0256, 0x1FC5, + 0x1FD8, 0x00D7, 0x050D, 0x027E, 0x1FC6, + 0x1FDC, 0x00BB, 0x04FB, 0x02A6, 0x1FC8, + 0x1FDF, 0x00A1, 0x04E7, 0x02CE, 0x1FCB, + 0x1FE2, 0x0089, 0x04D1, 0x02F5, 0x1FCF, + 0x1FE5, 0x0072, 0x04B8, 0x031D, 0x1FD4, + 0x1FE8, 0x005D, 0x049E, 0x0344, 0x1FD9, + 0x1FEB, 0x0049, 0x0480, 0x036B, 0x1FE1, + 0x1FEE, 0x0037, 0x0462, 0x0390, 0x1FE9, + 0x1FF0, 0x0026, 0x0442, 0x03B6, 0x1FF2, + 0x1FF2, 0x0017, 0x0420, 0x03DA, 0x1FFD, + 0x0009, 0x03F7, 0x03F7, 0x0009, 0x0000, + 0x1FFD, 0x03DA, 0x0420, 0x0017, 0x1FF2, + 0x1FF2, 0x03B6, 0x0442, 0x0026, 0x1FF0, + 0x1FE9, 0x0390, 0x0462, 0x0037, 0x1FEE, + 0x1FE1, 0x036B, 0x0480, 0x0049, 0x1FEB, + 0x1FD9, 0x0344, 0x049E, 0x005D, 0x1FE8, + 0x1FD4, 0x031D, 0x04B8, 0x0072, 0x1FE5, + 0x1FCF, 0x02F5, 0x04D1, 0x0089, 0x1FE2, + 0x1FCB, 0x02CE, 0x04E7, 0x00A1, 0x1FDF, + 0x1FC8, 0x02A6, 0x04FB, 0x00BB, 0x1FDC, + 0x1FC6, 0x027E, 0x050D, 0x00D7, 0x1FD8, + 0x1FC5, 0x0256, 0x051C, 0x00F4, 0x1FD5, + 0x1FC4, 0x022F, 0x0529, 0x0112, 0x1FD2, + 0x1FC4, 0x0209, 0x0532, 0x0132, 0x1FCF, + 0x1FC5, 0x01E2, 0x0539, 0x0153, 0x1FCD, + 0x1FC6, 0x01BD, 0x053E, 0x0175, 0x1FCA, + }, + [VS_LT_14_16_SCALE] = { + /* Luma */ + 0x1FBF, 0x016C, 0x05AA, 0x016C, 0x1FBF, + 0x1FC3, 0x0146, 0x05A8, 0x0194, 0x1FBB, + 0x1FC7, 0x0121, 0x05A3, 0x01BD, 0x1FB8, + 0x1FCB, 0x00FD, 0x059B, 0x01E8, 0x1FB5, + 0x1FD0, 0x00DC, 0x058F, 0x0213, 0x1FB2, + 0x1FD4, 0x00BC, 0x0580, 0x0240, 0x1FB0, + 0x1FD8, 0x009E, 0x056E, 0x026D, 0x1FAF, + 0x1FDC, 0x0082, 0x055A, 0x029A, 0x1FAE, + 0x1FE0, 0x0067, 0x0542, 0x02C9, 0x1FAE, + 0x1FE4, 0x004F, 0x0528, 0x02F6, 0x1FAF, + 0x1FE8, 0x0038, 0x050A, 0x0325, 0x1FB1, + 0x1FEB, 0x0024, 0x04EB, 0x0352, 0x1FB4, + 0x1FEE, 0x0011, 0x04C8, 0x0380, 0x1FB9, + 0x1FF1, 0x0000, 0x04A4, 0x03AC, 0x1FBF, + 0x1FF4, 0x1FF1, 0x047D, 0x03D8, 0x1FC6, + 0x1FF6, 0x1FE4, 0x0455, 0x0403, 0x1FCE, + 0x1FD8, 0x0428, 0x0428, 0x1FD8, 0x0000, + 0x1FCE, 0x0403, 0x0455, 0x1FE4, 0x1FF6, + 0x1FC6, 0x03D8, 0x047D, 0x1FF1, 0x1FF4, + 0x1FBF, 0x03AC, 0x04A4, 0x0000, 0x1FF1, + 0x1FB9, 0x0380, 0x04C8, 0x0011, 0x1FEE, + 0x1FB4, 0x0352, 0x04EB, 0x0024, 0x1FEB, + 0x1FB1, 0x0325, 0x050A, 0x0038, 0x1FE8, + 0x1FAF, 0x02F6, 0x0528, 0x004F, 0x1FE4, + 0x1FAE, 0x02C9, 0x0542, 0x0067, 0x1FE0, + 0x1FAE, 0x029A, 0x055A, 0x0082, 0x1FDC, + 0x1FAF, 0x026D, 0x056E, 0x009E, 0x1FD8, + 0x1FB0, 0x0240, 0x0580, 0x00BC, 0x1FD4, + 0x1FB2, 0x0213, 0x058F, 0x00DC, 0x1FD0, + 0x1FB5, 0x01E8, 0x059B, 0x00FD, 0x1FCB, + 0x1FB8, 0x01BD, 0x05A3, 0x0121, 0x1FC7, + 0x1FBB, 0x0194, 0x05A8, 0x0146, 0x1FC3, + /* Chroma */ + 0x1FBF, 0x016C, 0x05AA, 0x016C, 0x1FBF, + 0x1FC3, 0x0146, 0x05A8, 0x0194, 0x1FBB, + 0x1FC7, 0x0121, 0x05A3, 0x01BD, 0x1FB8, + 0x1FCB, 0x00FD, 0x059B, 0x01E8, 0x1FB5, + 0x1FD0, 0x00DC, 0x058F, 0x0213, 0x1FB2, + 0x1FD4, 0x00BC, 0x0580, 0x0240, 0x1FB0, + 0x1FD8, 0x009E, 0x056E, 0x026D, 0x1FAF, + 0x1FDC, 0x0082, 0x055A, 0x029A, 0x1FAE, + 0x1FE0, 0x0067, 0x0542, 0x02C9, 0x1FAE, + 0x1FE4, 0x004F, 0x0528, 0x02F6, 0x1FAF, + 0x1FE8, 0x0038, 0x050A, 0x0325, 0x1FB1, + 0x1FEB, 0x0024, 0x04EB, 0x0352, 0x1FB4, + 0x1FEE, 0x0011, 0x04C8, 0x0380, 0x1FB9, + 0x1FF1, 0x0000, 0x04A4, 0x03AC, 0x1FBF, + 0x1FF4, 0x1FF1, 0x047D, 0x03D8, 0x1FC6, + 0x1FF6, 0x1FE4, 0x0455, 0x0403, 0x1FCE, + 0x1FD8, 0x0428, 0x0428, 0x1FD8, 0x0000, + 0x1FCE, 0x0403, 0x0455, 0x1FE4, 0x1FF6, + 0x1FC6, 0x03D8, 0x047D, 0x1FF1, 0x1FF4, + 0x1FBF, 0x03AC, 0x04A4, 0x0000, 0x1FF1, + 0x1FB9, 0x0380, 0x04C8, 0x0011, 0x1FEE, + 0x1FB4, 0x0352, 0x04EB, 0x0024, 0x1FEB, + 0x1FB1, 0x0325, 0x050A, 0x0038, 0x1FE8, + 0x1FAF, 0x02F6, 0x0528, 0x004F, 0x1FE4, + 0x1FAE, 0x02C9, 0x0542, 0x0067, 0x1FE0, + 0x1FAE, 0x029A, 0x055A, 0x0082, 0x1FDC, + 0x1FAF, 0x026D, 0x056E, 0x009E, 0x1FD8, + 0x1FB0, 0x0240, 0x0580, 0x00BC, 0x1FD4, + 0x1FB2, 0x0213, 0x058F, 0x00DC, 0x1FD0, + 0x1FB5, 0x01E8, 0x059B, 0x00FD, 0x1FCB, + 0x1FB8, 0x01BD, 0x05A3, 0x0121, 0x1FC7, + 0x1FBB, 0x0194, 0x05A8, 0x0146, 0x1FC3, + }, + [VS_LT_15_16_SCALE] = { + /* Luma */ + 0x1FBD, 0x0136, 0x061A, 0x0136, 0x1FBD, + 0x1FC3, 0x010D, 0x0617, 0x0161, 0x1FB8, + 0x1FC9, 0x00E6, 0x0611, 0x018E, 0x1FB2, + 0x1FCE, 0x00C1, 0x0607, 0x01BD, 0x1FAD, + 0x1FD4, 0x009E, 0x05F9, 0x01ED, 0x1FA8, + 0x1FD9, 0x007D, 0x05E8, 0x021F, 0x1FA3, + 0x1FDE, 0x005E, 0x05D3, 0x0252, 0x1F9F, + 0x1FE2, 0x0042, 0x05BC, 0x0285, 0x1F9B, + 0x1FE7, 0x0029, 0x059F, 0x02B9, 0x1F98, + 0x1FEA, 0x0011, 0x0580, 0x02EF, 0x1F96, + 0x1FEE, 0x1FFC, 0x055D, 0x0324, 0x1F95, + 0x1FF1, 0x1FE9, 0x0538, 0x0359, 0x1F95, + 0x1FF4, 0x1FD8, 0x0510, 0x038E, 0x1F96, + 0x1FF7, 0x1FC9, 0x04E5, 0x03C2, 0x1F99, + 0x1FF9, 0x1FBD, 0x04B8, 0x03F5, 0x1F9D, + 0x1FFB, 0x1FB2, 0x0489, 0x0428, 0x1FA2, + 0x1FAA, 0x0456, 0x0456, 0x1FAA, 0x0000, + 0x1FA2, 0x0428, 0x0489, 0x1FB2, 0x1FFB, + 0x1F9D, 0x03F5, 0x04B8, 0x1FBD, 0x1FF9, + 0x1F99, 0x03C2, 0x04E5, 0x1FC9, 0x1FF7, + 0x1F96, 0x038E, 0x0510, 0x1FD8, 0x1FF4, + 0x1F95, 0x0359, 0x0538, 0x1FE9, 0x1FF1, + 0x1F95, 0x0324, 0x055D, 0x1FFC, 0x1FEE, + 0x1F96, 0x02EF, 0x0580, 0x0011, 0x1FEA, + 0x1F98, 0x02B9, 0x059F, 0x0029, 0x1FE7, + 0x1F9B, 0x0285, 0x05BC, 0x0042, 0x1FE2, + 0x1F9F, 0x0252, 0x05D3, 0x005E, 0x1FDE, + 0x1FA3, 0x021F, 0x05E8, 0x007D, 0x1FD9, + 0x1FA8, 0x01ED, 0x05F9, 0x009E, 0x1FD4, + 0x1FAD, 0x01BD, 0x0607, 0x00C1, 0x1FCE, + 0x1FB2, 0x018E, 0x0611, 0x00E6, 0x1FC9, + 0x1FB8, 0x0161, 0x0617, 0x010D, 0x1FC3, + /* Chroma */ + 0x1FBD, 0x0136, 0x061A, 0x0136, 0x1FBD, + 0x1FC3, 0x010D, 0x0617, 0x0161, 0x1FB8, + 0x1FC9, 0x00E6, 0x0611, 0x018E, 0x1FB2, + 0x1FCE, 0x00C1, 0x0607, 0x01BD, 0x1FAD, + 0x1FD4, 0x009E, 0x05F9, 0x01ED, 0x1FA8, + 0x1FD9, 0x007D, 0x05E8, 0x021F, 0x1FA3, + 0x1FDE, 0x005E, 0x05D3, 0x0252, 0x1F9F, + 0x1FE2, 0x0042, 0x05BC, 0x0285, 0x1F9B, + 0x1FE7, 0x0029, 0x059F, 0x02B9, 0x1F98, + 0x1FEA, 0x0011, 0x0580, 0x02EF, 0x1F96, + 0x1FEE, 0x1FFC, 0x055D, 0x0324, 0x1F95, + 0x1FF1, 0x1FE9, 0x0538, 0x0359, 0x1F95, + 0x1FF4, 0x1FD8, 0x0510, 0x038E, 0x1F96, + 0x1FF7, 0x1FC9, 0x04E5, 0x03C2, 0x1F99, + 0x1FF9, 0x1FBD, 0x04B8, 0x03F5, 0x1F9D, + 0x1FFB, 0x1FB2, 0x0489, 0x0428, 0x1FA2, + 0x1FAA, 0x0456, 0x0456, 0x1FAA, 0x0000, + 0x1FA2, 0x0428, 0x0489, 0x1FB2, 0x1FFB, + 0x1F9D, 0x03F5, 0x04B8, 0x1FBD, 0x1FF9, + 0x1F99, 0x03C2, 0x04E5, 0x1FC9, 0x1FF7, + 0x1F96, 0x038E, 0x0510, 0x1FD8, 0x1FF4, + 0x1F95, 0x0359, 0x0538, 0x1FE9, 0x1FF1, + 0x1F95, 0x0324, 0x055D, 0x1FFC, 0x1FEE, + 0x1F96, 0x02EF, 0x0580, 0x0011, 0x1FEA, + 0x1F98, 0x02B9, 0x059F, 0x0029, 0x1FE7, + 0x1F9B, 0x0285, 0x05BC, 0x0042, 0x1FE2, + 0x1F9F, 0x0252, 0x05D3, 0x005E, 0x1FDE, + 0x1FA3, 0x021F, 0x05E8, 0x007D, 0x1FD9, + 0x1FA8, 0x01ED, 0x05F9, 0x009E, 0x1FD4, + 0x1FAD, 0x01BD, 0x0607, 0x00C1, 0x1FCE, + 0x1FB2, 0x018E, 0x0611, 0x00E6, 0x1FC9, + 0x1FB8, 0x0161, 0x0617, 0x010D, 0x1FC3, + }, + [VS_LT_16_16_SCALE] = { + /* Luma */ + 0x1FC3, 0x00F8, 0x068A, 0x00F8, 0x1FC3, + 0x1FCA, 0x00CC, 0x0689, 0x0125, 0x1FBC, + 0x1FD1, 0x00A3, 0x0681, 0x0156, 0x1FB5, + 0x1FD7, 0x007D, 0x0676, 0x0188, 0x1FAE, + 0x1FDD, 0x005A, 0x0666, 0x01BD, 0x1FA6, + 0x1FE3, 0x0039, 0x0652, 0x01F3, 0x1F9F, + 0x1FE8, 0x001B, 0x0639, 0x022C, 0x1F98, + 0x1FEC, 0x0000, 0x061D, 0x0265, 0x1F92, + 0x1FF0, 0x1FE8, 0x05FC, 0x02A0, 0x1F8C, + 0x1FF4, 0x1FD2, 0x05D7, 0x02DC, 0x1F87, + 0x1FF7, 0x1FBF, 0x05AF, 0x0319, 0x1F82, + 0x1FFA, 0x1FAF, 0x0583, 0x0356, 0x1F7E, + 0x1FFC, 0x1FA1, 0x0554, 0x0393, 0x1F7C, + 0x1FFE, 0x1F95, 0x0523, 0x03CF, 0x1F7B, + 0x0000, 0x1F8C, 0x04EE, 0x040B, 0x1F7B, + 0x0001, 0x1F85, 0x04B8, 0x0446, 0x1F7C, + 0x1F80, 0x0480, 0x0480, 0x1F80, 0x0000, + 0x1F7C, 0x0446, 0x04B8, 0x1F85, 0x0001, + 0x1F7B, 0x040B, 0x04EE, 0x1F8C, 0x0000, + 0x1F7B, 0x03CF, 0x0523, 0x1F95, 0x1FFE, + 0x1F7C, 0x0393, 0x0554, 0x1FA1, 0x1FFC, + 0x1F7E, 0x0356, 0x0583, 0x1FAF, 0x1FFA, + 0x1F82, 0x0319, 0x05AF, 0x1FBF, 0x1FF7, + 0x1F87, 0x02DC, 0x05D7, 0x1FD2, 0x1FF4, + 0x1F8C, 0x02A0, 0x05FC, 0x1FE8, 0x1FF0, + 0x1F92, 0x0265, 0x061D, 0x0000, 0x1FEC, + 0x1F98, 0x022C, 0x0639, 0x001B, 0x1FE8, + 0x1F9F, 0x01F3, 0x0652, 0x0039, 0x1FE3, + 0x1FA6, 0x01BD, 0x0666, 0x005A, 0x1FDD, + 0x1FAE, 0x0188, 0x0676, 0x007D, 0x1FD7, + 0x1FB5, 0x0156, 0x0681, 0x00A3, 0x1FD1, + 0x1FBC, 0x0125, 0x0689, 0x00CC, 0x1FCA, + /* Chroma */ + 0x1FC3, 0x00F8, 0x068A, 0x00F8, 0x1FC3, + 0x1FCA, 0x00CC, 0x0689, 0x0125, 0x1FBC, + 0x1FD1, 0x00A3, 0x0681, 0x0156, 0x1FB5, + 0x1FD7, 0x007D, 0x0676, 0x0188, 0x1FAE, + 0x1FDD, 0x005A, 0x0666, 0x01BD, 0x1FA6, + 0x1FE3, 0x0039, 0x0652, 0x01F3, 0x1F9F, + 0x1FE8, 0x001B, 0x0639, 0x022C, 0x1F98, + 0x1FEC, 0x0000, 0x061D, 0x0265, 0x1F92, + 0x1FF0, 0x1FE8, 0x05FC, 0x02A0, 0x1F8C, + 0x1FF4, 0x1FD2, 0x05D7, 0x02DC, 0x1F87, + 0x1FF7, 0x1FBF, 0x05AF, 0x0319, 0x1F82, + 0x1FFA, 0x1FAF, 0x0583, 0x0356, 0x1F7E, + 0x1FFC, 0x1FA1, 0x0554, 0x0393, 0x1F7C, + 0x1FFE, 0x1F95, 0x0523, 0x03CF, 0x1F7B, + 0x0000, 0x1F8C, 0x04EE, 0x040B, 0x1F7B, + 0x0001, 0x1F85, 0x04B8, 0x0446, 0x1F7C, + 0x1F80, 0x0480, 0x0480, 0x1F80, 0x0000, + 0x1F7C, 0x0446, 0x04B8, 0x1F85, 0x0001, + 0x1F7B, 0x040B, 0x04EE, 0x1F8C, 0x0000, + 0x1F7B, 0x03CF, 0x0523, 0x1F95, 0x1FFE, + 0x1F7C, 0x0393, 0x0554, 0x1FA1, 0x1FFC, + 0x1F7E, 0x0356, 0x0583, 0x1FAF, 0x1FFA, + 0x1F82, 0x0319, 0x05AF, 0x1FBF, 0x1FF7, + 0x1F87, 0x02DC, 0x05D7, 0x1FD2, 0x1FF4, + 0x1F8C, 0x02A0, 0x05FC, 0x1FE8, 0x1FF0, + 0x1F92, 0x0265, 0x061D, 0x0000, 0x1FEC, + 0x1F98, 0x022C, 0x0639, 0x001B, 0x1FE8, + 0x1F9F, 0x01F3, 0x0652, 0x0039, 0x1FE3, + 0x1FA6, 0x01BD, 0x0666, 0x005A, 0x1FDD, + 0x1FAE, 0x0188, 0x0676, 0x007D, 0x1FD7, + 0x1FB5, 0x0156, 0x0681, 0x00A3, 0x1FD1, + 0x1FBC, 0x0125, 0x0689, 0x00CC, 0x1FCA, + }, + [VS_1_TO_1_SCALE] = { + /* Luma */ + 0x0000, 0x0000, 0x0800, 0x0000, 0x0000, + 0x1FD8, 0x0085, 0x06F9, 0x00E1, 0x1FC9, + 0x1FDF, 0x005B, 0x06F2, 0x0114, 0x1FC0, + 0x1FE5, 0x0035, 0x06E5, 0x014A, 0x1FB7, + 0x1FEB, 0x0012, 0x06D3, 0x0182, 0x1FAE, + 0x1FF1, 0x1FF3, 0x06BA, 0x01BD, 0x1FA5, + 0x1FF5, 0x1FD7, 0x069D, 0x01FB, 0x1F9C, + 0x1FF9, 0x1FBE, 0x067C, 0x023A, 0x1F93, + 0x1FFD, 0x1FA8, 0x0656, 0x027B, 0x1F8A, + 0x0000, 0x1F95, 0x062B, 0x02BF, 0x1F81, + 0x0002, 0x1F86, 0x05FC, 0x0303, 0x1F79, + 0x0004, 0x1F79, 0x05CA, 0x0347, 0x1F72, + 0x0005, 0x1F6F, 0x0594, 0x038D, 0x1F6B, + 0x0006, 0x1F67, 0x055B, 0x03D2, 0x1F66, + 0x0007, 0x1F62, 0x051E, 0x0417, 0x1F62, + 0x0007, 0x1F5F, 0x04DF, 0x045C, 0x1F5F, + 0x1F5E, 0x04A2, 0x04A2, 0x1F5E, 0x0000, + 0x1F5F, 0x045C, 0x04DF, 0x1F5F, 0x0007, + 0x1F62, 0x0417, 0x051E, 0x1F62, 0x0007, + 0x1F66, 0x03D2, 0x055B, 0x1F67, 0x0006, + 0x1F6B, 0x038D, 0x0594, 0x1F6F, 0x0005, + 0x1F72, 0x0347, 0x05CA, 0x1F79, 0x0004, + 0x1F79, 0x0303, 0x05FC, 0x1F86, 0x0002, + 0x1F81, 0x02BF, 0x062B, 0x1F95, 0x0000, + 0x1F8A, 0x027B, 0x0656, 0x1FA8, 0x1FFD, + 0x1F93, 0x023A, 0x067C, 0x1FBE, 0x1FF9, + 0x1F9C, 0x01FB, 0x069D, 0x1FD7, 0x1FF5, + 0x1FA5, 0x01BD, 0x06BA, 0x1FF3, 0x1FF1, + 0x1FAE, 0x0182, 0x06D3, 0x0012, 0x1FEB, + 0x1FB7, 0x014A, 0x06E5, 0x0035, 0x1FE5, + 0x1FC0, 0x0114, 0x06F2, 0x005B, 0x1FDF, + 0x1FC9, 0x00E1, 0x06F9, 0x0085, 0x1FD8, + /* Chroma */ + 0x0000, 0x0000, 0x0800, 0x0000, 0x0000, + 0x1FD8, 0x0085, 0x06F9, 0x00E1, 0x1FC9, + 0x1FDF, 0x005B, 0x06F2, 0x0114, 0x1FC0, + 0x1FE5, 0x0035, 0x06E5, 0x014A, 0x1FB7, + 0x1FEB, 0x0012, 0x06D3, 0x0182, 0x1FAE, + 0x1FF1, 0x1FF3, 0x06BA, 0x01BD, 0x1FA5, + 0x1FF5, 0x1FD7, 0x069D, 0x01FB, 0x1F9C, + 0x1FF9, 0x1FBE, 0x067C, 0x023A, 0x1F93, + 0x1FFD, 0x1FA8, 0x0656, 0x027B, 0x1F8A, + 0x0000, 0x1F95, 0x062B, 0x02BF, 0x1F81, + 0x0002, 0x1F86, 0x05FC, 0x0303, 0x1F79, + 0x0004, 0x1F79, 0x05CA, 0x0347, 0x1F72, + 0x0005, 0x1F6F, 0x0594, 0x038D, 0x1F6B, + 0x0006, 0x1F67, 0x055B, 0x03D2, 0x1F66, + 0x0007, 0x1F62, 0x051E, 0x0417, 0x1F62, + 0x0007, 0x1F5F, 0x04DF, 0x045C, 0x1F5F, + 0x1F5E, 0x04A2, 0x04A2, 0x1F5E, 0x0000, + 0x1F5F, 0x045C, 0x04DF, 0x1F5F, 0x0007, + 0x1F62, 0x0417, 0x051E, 0x1F62, 0x0007, + 0x1F66, 0x03D2, 0x055B, 0x1F67, 0x0006, + 0x1F6B, 0x038D, 0x0594, 0x1F6F, 0x0005, + 0x1F72, 0x0347, 0x05CA, 0x1F79, 0x0004, + 0x1F79, 0x0303, 0x05FC, 0x1F86, 0x0002, + 0x1F81, 0x02BF, 0x062B, 0x1F95, 0x0000, + 0x1F8A, 0x027B, 0x0656, 0x1FA8, 0x1FFD, + 0x1F93, 0x023A, 0x067C, 0x1FBE, 0x1FF9, + 0x1F9C, 0x01FB, 0x069D, 0x1FD7, 0x1FF5, + 0x1FA5, 0x01BD, 0x06BA, 0x1FF3, 0x1FF1, + 0x1FAE, 0x0182, 0x06D3, 0x0012, 0x1FEB, + 0x1FB7, 0x014A, 0x06E5, 0x0035, 0x1FE5, + 0x1FC0, 0x0114, 0x06F2, 0x005B, 0x1FDF, + 0x1FC9, 0x00E1, 0x06F9, 0x0085, 0x1FD8, + }, +}; +#endif diff --git a/drivers/media/platform/ti-vpe/vpdma.c b/drivers/media/platform/ti-vpe/vpdma.c index fcbe48a09cf8..e8175e7938ed 100644 --- a/drivers/media/platform/ti-vpe/vpdma.c +++ b/drivers/media/platform/ti-vpe/vpdma.c @@ -30,38 +30,47 @@ const struct vpdma_data_format vpdma_yuv_fmts[] = { [VPDMA_DATA_FMT_Y444] = { + .type = VPDMA_DATA_FMT_TYPE_YUV, .data_type = DATA_TYPE_Y444, .depth = 8, }, [VPDMA_DATA_FMT_Y422] = { + .type = VPDMA_DATA_FMT_TYPE_YUV, .data_type = DATA_TYPE_Y422, .depth = 8, }, [VPDMA_DATA_FMT_Y420] = { + .type = VPDMA_DATA_FMT_TYPE_YUV, .data_type = DATA_TYPE_Y420, .depth = 8, }, [VPDMA_DATA_FMT_C444] = { + .type = VPDMA_DATA_FMT_TYPE_YUV, .data_type = DATA_TYPE_C444, .depth = 8, }, [VPDMA_DATA_FMT_C422] = { + .type = VPDMA_DATA_FMT_TYPE_YUV, .data_type = DATA_TYPE_C422, .depth = 8, }, [VPDMA_DATA_FMT_C420] = { + .type = VPDMA_DATA_FMT_TYPE_YUV, .data_type = DATA_TYPE_C420, .depth = 4, }, [VPDMA_DATA_FMT_YC422] = { + .type = VPDMA_DATA_FMT_TYPE_YUV, .data_type = DATA_TYPE_YC422, .depth = 16, }, [VPDMA_DATA_FMT_YC444] = { + .type = VPDMA_DATA_FMT_TYPE_YUV, .data_type = DATA_TYPE_YC444, .depth = 24, }, [VPDMA_DATA_FMT_CY422] = { + .type = VPDMA_DATA_FMT_TYPE_YUV, .data_type = DATA_TYPE_CY422, .depth = 16, }, @@ -69,82 +78,102 @@ const struct vpdma_data_format vpdma_yuv_fmts[] = { const struct vpdma_data_format vpdma_rgb_fmts[] = { [VPDMA_DATA_FMT_RGB565] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_RGB16_565, .depth = 16, }, [VPDMA_DATA_FMT_ARGB16_1555] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_ARGB_1555, .depth = 16, }, [VPDMA_DATA_FMT_ARGB16] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_ARGB_4444, .depth = 16, }, [VPDMA_DATA_FMT_RGBA16_5551] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_RGBA_5551, .depth = 16, }, [VPDMA_DATA_FMT_RGBA16] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_RGBA_4444, .depth = 16, }, [VPDMA_DATA_FMT_ARGB24] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_ARGB24_6666, .depth = 24, }, [VPDMA_DATA_FMT_RGB24] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_RGB24_888, .depth = 24, }, [VPDMA_DATA_FMT_ARGB32] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_ARGB32_8888, .depth = 32, }, [VPDMA_DATA_FMT_RGBA24] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_RGBA24_6666, .depth = 24, }, [VPDMA_DATA_FMT_RGBA32] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_RGBA32_8888, .depth = 32, }, [VPDMA_DATA_FMT_BGR565] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_BGR16_565, .depth = 16, }, [VPDMA_DATA_FMT_ABGR16_1555] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_ABGR_1555, .depth = 16, }, [VPDMA_DATA_FMT_ABGR16] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_ABGR_4444, .depth = 16, }, [VPDMA_DATA_FMT_BGRA16_5551] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_BGRA_5551, .depth = 16, }, [VPDMA_DATA_FMT_BGRA16] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_BGRA_4444, .depth = 16, }, [VPDMA_DATA_FMT_ABGR24] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_ABGR24_6666, .depth = 24, }, [VPDMA_DATA_FMT_BGR24] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_BGR24_888, .depth = 24, }, [VPDMA_DATA_FMT_ABGR32] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_ABGR32_8888, .depth = 32, }, [VPDMA_DATA_FMT_BGRA24] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_BGRA24_6666, .depth = 24, }, [VPDMA_DATA_FMT_BGRA32] = { + .type = VPDMA_DATA_FMT_TYPE_RGB, .data_type = DATA_TYPE_BGRA32_8888, .depth = 32, }, @@ -152,6 +181,7 @@ const struct vpdma_data_format vpdma_rgb_fmts[] = { const struct vpdma_data_format vpdma_misc_fmts[] = { [VPDMA_DATA_FMT_MV] = { + .type = VPDMA_DATA_FMT_TYPE_MISC, .data_type = DATA_TYPE_MV, .depth = 4, }, @@ -599,10 +629,11 @@ void vpdma_add_out_dtd(struct vpdma_desc_list *list, struct v4l2_rect *c_rect, channel = next_chan = chan_info[chan].num; - if (fmt->data_type == DATA_TYPE_C420) + if (fmt->type == VPDMA_DATA_FMT_TYPE_YUV && + fmt->data_type == DATA_TYPE_C420) depth = 8; - stride = (depth * c_rect->width) >> 3; + stride = ALIGN((depth * c_rect->width) >> 3, VPDMA_STRIDE_ALIGN); dma_addr += (c_rect->left * depth) >> 3; dtd = list->next; @@ -649,13 +680,14 @@ void vpdma_add_in_dtd(struct vpdma_desc_list *list, int frame_width, channel = next_chan = chan_info[chan].num; - if (fmt->data_type == DATA_TYPE_C420) { + if (fmt->type == VPDMA_DATA_FMT_TYPE_YUV && + fmt->data_type == DATA_TYPE_C420) { height >>= 1; frame_height >>= 1; depth = 8; } - stride = (depth * c_rect->width) >> 3; + stride = ALIGN((depth * c_rect->width) >> 3, VPDMA_STRIDE_ALIGN); dma_addr += (c_rect->left * depth) >> 3; dtd = list->next; diff --git a/drivers/media/platform/ti-vpe/vpdma.h b/drivers/media/platform/ti-vpe/vpdma.h index eaa2a71a5db9..cf40f11b3c8f 100644 --- a/drivers/media/platform/ti-vpe/vpdma.h +++ b/drivers/media/platform/ti-vpe/vpdma.h @@ -39,13 +39,23 @@ struct vpdma_data { bool ready; }; +enum vpdma_data_format_type { + VPDMA_DATA_FMT_TYPE_YUV, + VPDMA_DATA_FMT_TYPE_RGB, + VPDMA_DATA_FMT_TYPE_MISC, +}; + struct vpdma_data_format { + enum vpdma_data_format_type type; int data_type; u8 depth; }; #define VPDMA_DESC_ALIGN 16 /* 16-byte descriptor alignment */ - +#define VPDMA_STRIDE_ALIGN 16 /* + * line stride of source and dest + * buffers should be 16 byte aligned + */ #define VPDMA_DTD_DESC_SIZE 32 /* 8 words */ #define VPDMA_CFD_CTD_DESC_SIZE 16 /* 4 words */ diff --git a/drivers/media/platform/ti-vpe/vpdma_priv.h b/drivers/media/platform/ti-vpe/vpdma_priv.h index f0e9a8038c1b..c1a6ce1884f3 100644 --- a/drivers/media/platform/ti-vpe/vpdma_priv.h +++ b/drivers/media/platform/ti-vpe/vpdma_priv.h @@ -78,7 +78,7 @@ #define DATA_TYPE_C420 0x6 #define DATA_TYPE_YC422 0x7 #define DATA_TYPE_YC444 0x8 -#define DATA_TYPE_CY422 0x23 +#define DATA_TYPE_CY422 0x27 #define DATA_TYPE_RGB16_565 0x0 #define DATA_TYPE_ARGB_1555 0x1 diff --git a/drivers/media/platform/ti-vpe/vpe.c b/drivers/media/platform/ti-vpe/vpe.c index 4e58069e24ff..1296c5386231 100644 --- a/drivers/media/platform/ti-vpe/vpe.c +++ b/drivers/media/platform/ti-vpe/vpe.c @@ -30,6 +30,7 @@ #include <linux/sched.h> #include <linux/slab.h> #include <linux/videodev2.h> +#include <linux/log2.h> #include <media/v4l2-common.h> #include <media/v4l2-ctrls.h> @@ -42,6 +43,8 @@ #include "vpdma.h" #include "vpe_regs.h" +#include "sc.h" +#include "csc.h" #define VPE_MODULE_NAME "vpe" @@ -54,10 +57,6 @@ /* required alignments */ #define S_ALIGN 0 /* multiple of 1 */ #define H_ALIGN 1 /* multiple of 2 */ -#define W_ALIGN 1 /* multiple of 2 */ - -/* multiple of 128 bits, line stride, 16 bytes */ -#define L_ALIGN 4 /* flags that indicate a format can be used for capture/output */ #define VPE_FMT_TYPE_CAPTURE (1 << 0) @@ -268,6 +267,38 @@ static struct vpe_fmt vpe_formats[] = { .vpdma_fmt = { &vpdma_yuv_fmts[VPDMA_DATA_FMT_CY422], }, }, + { + .name = "RGB888 packed", + .fourcc = V4L2_PIX_FMT_RGB24, + .types = VPE_FMT_TYPE_CAPTURE, + .coplanar = 0, + .vpdma_fmt = { &vpdma_rgb_fmts[VPDMA_DATA_FMT_RGB24], + }, + }, + { + .name = "ARGB32", + .fourcc = V4L2_PIX_FMT_RGB32, + .types = VPE_FMT_TYPE_CAPTURE, + .coplanar = 0, + .vpdma_fmt = { &vpdma_rgb_fmts[VPDMA_DATA_FMT_ARGB32], + }, + }, + { + .name = "BGR888 packed", + .fourcc = V4L2_PIX_FMT_BGR24, + .types = VPE_FMT_TYPE_CAPTURE, + .coplanar = 0, + .vpdma_fmt = { &vpdma_rgb_fmts[VPDMA_DATA_FMT_BGR24], + }, + }, + { + .name = "ABGR32", + .fourcc = V4L2_PIX_FMT_BGR32, + .types = VPE_FMT_TYPE_CAPTURE, + .coplanar = 0, + .vpdma_fmt = { &vpdma_rgb_fmts[VPDMA_DATA_FMT_ABGR32], + }, + }, }; /* @@ -327,9 +358,12 @@ struct vpe_dev { int irq; void __iomem *base; + struct resource *res; struct vb2_alloc_ctx *alloc_ctx; struct vpdma_data *vpdma; /* vpdma data handle */ + struct sc_data *sc; /* scaler data handle */ + struct csc_data *csc; /* csc data handle */ }; /* @@ -356,6 +390,8 @@ struct vpe_ctx { void *mv_buf[2]; /* virtual addrs of motion vector bufs */ size_t mv_buf_size; /* current motion vector buffer size */ struct vpdma_buf mmr_adb; /* shadow reg addr/data block */ + struct vpdma_buf sc_coeff_h; /* h coeff buffer */ + struct vpdma_buf sc_coeff_v; /* v coeff buffer */ struct vpdma_desc_list desc_list; /* DMA descriptor list */ bool deinterlacing; /* using de-interlacer */ @@ -438,14 +474,23 @@ struct vpe_mmr_adb { u32 us3_regs[8]; struct vpdma_adb_hdr dei_hdr; u32 dei_regs[8]; - struct vpdma_adb_hdr sc_hdr; - u32 sc_regs[1]; - u32 sc_pad[3]; + struct vpdma_adb_hdr sc_hdr0; + u32 sc_regs0[7]; + u32 sc_pad0[1]; + struct vpdma_adb_hdr sc_hdr8; + u32 sc_regs8[6]; + u32 sc_pad8[2]; + struct vpdma_adb_hdr sc_hdr17; + u32 sc_regs17[9]; + u32 sc_pad17[3]; struct vpdma_adb_hdr csc_hdr; u32 csc_regs[6]; u32 csc_pad[2]; }; +#define GET_OFFSET_TOP(ctx, obj, reg) \ + ((obj)->res->start - ctx->dev->res->start + reg) + #define VPE_SET_MMR_ADB_HDR(ctx, hdr, regs, offset_a) \ VPDMA_SET_MMR_ADB_HDR(ctx->mmr_adb, vpe_mmr_adb, hdr, regs, offset_a) /* @@ -458,8 +503,14 @@ static void init_adb_hdrs(struct vpe_ctx *ctx) VPE_SET_MMR_ADB_HDR(ctx, us2_hdr, us2_regs, VPE_US2_R0); VPE_SET_MMR_ADB_HDR(ctx, us3_hdr, us3_regs, VPE_US3_R0); VPE_SET_MMR_ADB_HDR(ctx, dei_hdr, dei_regs, VPE_DEI_FRAME_SIZE); - VPE_SET_MMR_ADB_HDR(ctx, sc_hdr, sc_regs, VPE_SC_MP_SC0); - VPE_SET_MMR_ADB_HDR(ctx, csc_hdr, csc_regs, VPE_CSC_CSC00); + VPE_SET_MMR_ADB_HDR(ctx, sc_hdr0, sc_regs0, + GET_OFFSET_TOP(ctx, ctx->dev->sc, CFG_SC0)); + VPE_SET_MMR_ADB_HDR(ctx, sc_hdr8, sc_regs8, + GET_OFFSET_TOP(ctx, ctx->dev->sc, CFG_SC8)); + VPE_SET_MMR_ADB_HDR(ctx, sc_hdr17, sc_regs17, + GET_OFFSET_TOP(ctx, ctx->dev->sc, CFG_SC17)); + VPE_SET_MMR_ADB_HDR(ctx, csc_hdr, csc_regs, + GET_OFFSET_TOP(ctx, ctx->dev->csc, CSC_CSC00)); }; /* @@ -670,17 +721,20 @@ static void set_src_registers(struct vpe_ctx *ctx) static void set_dst_registers(struct vpe_ctx *ctx) { struct vpe_mmr_adb *mmr_adb = ctx->mmr_adb.addr; + enum v4l2_colorspace clrspc = ctx->q_data[Q_DATA_DST].colorspace; struct vpe_fmt *fmt = ctx->q_data[Q_DATA_DST].fmt; u32 val = 0; - /* select RGB path when color space conversion is supported in future */ - if (fmt->fourcc == V4L2_PIX_FMT_RGB24) - val |= VPE_RGB_OUT_SELECT | VPE_CSC_SRC_DEI_SCALER; + if (clrspc == V4L2_COLORSPACE_SRGB) + val |= VPE_RGB_OUT_SELECT; else if (fmt->fourcc == V4L2_PIX_FMT_NV16) val |= VPE_COLOR_SEPARATE_422; - /* The source of CHR_DS is always the scaler, whether it's used or not */ - val |= VPE_DS_SRC_DEI_SCALER; + /* + * the source of CHR_DS and CSC is always the scaler, irrespective of + * whether it's used or not + */ + val |= VPE_DS_SRC_DEI_SCALER | VPE_CSC_SRC_DEI_SCALER; if (fmt->fourcc != V4L2_PIX_FMT_NV12) val |= VPE_DS_BYPASS; @@ -742,28 +796,6 @@ static void set_dei_shadow_registers(struct vpe_ctx *ctx) ctx->load_mmrs = true; } -static void set_csc_coeff_bypass(struct vpe_ctx *ctx) -{ - struct vpe_mmr_adb *mmr_adb = ctx->mmr_adb.addr; - u32 *shadow_csc_reg5 = &mmr_adb->csc_regs[5]; - - *shadow_csc_reg5 |= VPE_CSC_BYPASS; - - ctx->load_mmrs = true; -} - -static void set_sc_regs_bypass(struct vpe_ctx *ctx) -{ - struct vpe_mmr_adb *mmr_adb = ctx->mmr_adb.addr; - u32 *sc_reg0 = &mmr_adb->sc_regs[0]; - u32 val = 0; - - val |= VPE_SC_BYPASS; - *sc_reg0 = val; - - ctx->load_mmrs = true; -} - /* * Set the shadow registers whose values are modified when either the * source or destination format is changed. @@ -772,6 +804,11 @@ static int set_srcdst_params(struct vpe_ctx *ctx) { struct vpe_q_data *s_q_data = &ctx->q_data[Q_DATA_SRC]; struct vpe_q_data *d_q_data = &ctx->q_data[Q_DATA_DST]; + struct vpe_mmr_adb *mmr_adb = ctx->mmr_adb.addr; + unsigned int src_w = s_q_data->c_rect.width; + unsigned int src_h = s_q_data->c_rect.height; + unsigned int dst_w = d_q_data->c_rect.width; + unsigned int dst_h = d_q_data->c_rect.height; size_t mv_buf_size; int ret; @@ -780,12 +817,23 @@ static int set_srcdst_params(struct vpe_ctx *ctx) if ((s_q_data->flags & Q_DATA_INTERLACED) && !(d_q_data->flags & Q_DATA_INTERLACED)) { + int bytes_per_line; const struct vpdma_data_format *mv = &vpdma_misc_fmts[VPDMA_DATA_FMT_MV]; + /* + * we make sure that the source image has a 16 byte aligned + * stride, we need to do the same for the motion vector buffer + * by aligning it's stride to the next 16 byte boundry. this + * extra space will not be used by the de-interlacer, but will + * ensure that vpdma operates correctly + */ + bytes_per_line = ALIGN((s_q_data->width * mv->depth) >> 3, + VPDMA_STRIDE_ALIGN); + mv_buf_size = bytes_per_line * s_q_data->height; + ctx->deinterlacing = 1; - mv_buf_size = - (s_q_data->width * s_q_data->height * mv->depth) >> 3; + src_h <<= 1; } else { ctx->deinterlacing = 0; mv_buf_size = 0; @@ -799,8 +847,16 @@ static int set_srcdst_params(struct vpe_ctx *ctx) set_cfg_and_line_modes(ctx); set_dei_regs(ctx); - set_csc_coeff_bypass(ctx); - set_sc_regs_bypass(ctx); + + csc_set_coeff(ctx->dev->csc, &mmr_adb->csc_regs[0], + s_q_data->colorspace, d_q_data->colorspace); + + sc_set_hs_coeffs(ctx->dev->sc, ctx->sc_coeff_h.addr, src_w, dst_w); + sc_set_vs_coeffs(ctx->dev->sc, ctx->sc_coeff_v.addr, src_h, dst_h); + + sc_config_scaler(ctx->dev->sc, &mmr_adb->sc_regs0[0], + &mmr_adb->sc_regs8[0], &mmr_adb->sc_regs17[0], + src_w, src_h, dst_w, dst_h); return 0; } @@ -916,35 +972,10 @@ static void vpe_dump_regs(struct vpe_dev *dev) DUMPREG(DEI_FMD_STATUS_R0); DUMPREG(DEI_FMD_STATUS_R1); DUMPREG(DEI_FMD_STATUS_R2); - DUMPREG(SC_MP_SC0); - DUMPREG(SC_MP_SC1); - DUMPREG(SC_MP_SC2); - DUMPREG(SC_MP_SC3); - DUMPREG(SC_MP_SC4); - DUMPREG(SC_MP_SC5); - DUMPREG(SC_MP_SC6); - DUMPREG(SC_MP_SC8); - DUMPREG(SC_MP_SC9); - DUMPREG(SC_MP_SC10); - DUMPREG(SC_MP_SC11); - DUMPREG(SC_MP_SC12); - DUMPREG(SC_MP_SC13); - DUMPREG(SC_MP_SC17); - DUMPREG(SC_MP_SC18); - DUMPREG(SC_MP_SC19); - DUMPREG(SC_MP_SC20); - DUMPREG(SC_MP_SC21); - DUMPREG(SC_MP_SC22); - DUMPREG(SC_MP_SC23); - DUMPREG(SC_MP_SC24); - DUMPREG(SC_MP_SC25); - DUMPREG(CSC_CSC00); - DUMPREG(CSC_CSC01); - DUMPREG(CSC_CSC02); - DUMPREG(CSC_CSC03); - DUMPREG(CSC_CSC04); - DUMPREG(CSC_CSC05); #undef DUMPREG + + sc_dump_regs(dev->sc); + csc_dump_regs(dev->csc); } static void add_out_dtd(struct vpe_ctx *ctx, int port) @@ -1053,6 +1084,7 @@ static void disable_irqs(struct vpe_ctx *ctx) static void device_run(void *priv) { struct vpe_ctx *ctx = priv; + struct sc_data *sc = ctx->dev->sc; struct vpe_q_data *d_q_data = &ctx->q_data[Q_DATA_DST]; if (ctx->deinterlacing && ctx->src_vbs[2] == NULL) { @@ -1075,13 +1107,37 @@ static void device_run(void *priv) ctx->load_mmrs = false; } + if (sc->loaded_coeff_h != ctx->sc_coeff_h.dma_addr || + sc->load_coeff_h) { + vpdma_map_desc_buf(ctx->dev->vpdma, &ctx->sc_coeff_h); + vpdma_add_cfd_block(&ctx->desc_list, CFD_SC_CLIENT, + &ctx->sc_coeff_h, 0); + + sc->loaded_coeff_h = ctx->sc_coeff_h.dma_addr; + sc->load_coeff_h = false; + } + + if (sc->loaded_coeff_v != ctx->sc_coeff_v.dma_addr || + sc->load_coeff_v) { + vpdma_map_desc_buf(ctx->dev->vpdma, &ctx->sc_coeff_v); + vpdma_add_cfd_block(&ctx->desc_list, CFD_SC_CLIENT, + &ctx->sc_coeff_v, SC_COEF_SRAM_SIZE >> 4); + + sc->loaded_coeff_v = ctx->sc_coeff_v.dma_addr; + sc->load_coeff_v = false; + } + /* output data descriptors */ if (ctx->deinterlacing) add_out_dtd(ctx, VPE_PORT_MV_OUT); - add_out_dtd(ctx, VPE_PORT_LUMA_OUT); - if (d_q_data->fmt->coplanar) - add_out_dtd(ctx, VPE_PORT_CHROMA_OUT); + if (d_q_data->colorspace == V4L2_COLORSPACE_SRGB) { + add_out_dtd(ctx, VPE_PORT_RGB_OUT); + } else { + add_out_dtd(ctx, VPE_PORT_LUMA_OUT); + if (d_q_data->fmt->coplanar) + add_out_dtd(ctx, VPE_PORT_CHROMA_OUT); + } /* input data descriptors */ if (ctx->deinterlacing) { @@ -1117,9 +1173,16 @@ static void device_run(void *priv) } /* sync on channel control descriptors for output ports */ - vpdma_add_sync_on_channel_ctd(&ctx->desc_list, VPE_CHAN_LUMA_OUT); - if (d_q_data->fmt->coplanar) - vpdma_add_sync_on_channel_ctd(&ctx->desc_list, VPE_CHAN_CHROMA_OUT); + if (d_q_data->colorspace == V4L2_COLORSPACE_SRGB) { + vpdma_add_sync_on_channel_ctd(&ctx->desc_list, + VPE_CHAN_RGB_OUT); + } else { + vpdma_add_sync_on_channel_ctd(&ctx->desc_list, + VPE_CHAN_LUMA_OUT); + if (d_q_data->fmt->coplanar) + vpdma_add_sync_on_channel_ctd(&ctx->desc_list, + VPE_CHAN_CHROMA_OUT); + } if (ctx->deinterlacing) vpdma_add_sync_on_channel_ctd(&ctx->desc_list, VPE_CHAN_MV_OUT); @@ -1198,6 +1261,8 @@ static irqreturn_t vpe_irq(int irq_vpe, void *data) vpdma_unmap_desc_buf(dev->vpdma, &ctx->desc_list.buf); vpdma_unmap_desc_buf(dev->vpdma, &ctx->mmr_adb); + vpdma_unmap_desc_buf(dev->vpdma, &ctx->sc_coeff_h); + vpdma_unmap_desc_buf(dev->vpdma, &ctx->sc_coeff_v); vpdma_reset_desc_list(&ctx->desc_list); @@ -1352,7 +1417,8 @@ static int __vpe_try_fmt(struct vpe_ctx *ctx, struct v4l2_format *f, { struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; struct v4l2_plane_pix_format *plane_fmt; - int i; + unsigned int w_align; + int i, depth, depth_bytes; if (!fmt || !(fmt->types & type)) { vpe_err(ctx->dev, "Fourcc format (0x%08x) invalid.\n", @@ -1363,35 +1429,57 @@ static int __vpe_try_fmt(struct vpe_ctx *ctx, struct v4l2_format *f, if (pix->field != V4L2_FIELD_NONE && pix->field != V4L2_FIELD_ALTERNATE) pix->field = V4L2_FIELD_NONE; - v4l_bound_align_image(&pix->width, MIN_W, MAX_W, W_ALIGN, + depth = fmt->vpdma_fmt[VPE_LUMA]->depth; + + /* + * the line stride should 16 byte aligned for VPDMA to work, based on + * the bytes per pixel, figure out how much the width should be aligned + * to make sure line stride is 16 byte aligned + */ + depth_bytes = depth >> 3; + + if (depth_bytes == 3) + /* + * if bpp is 3(as in some RGB formats), the pixel width doesn't + * really help in ensuring line stride is 16 byte aligned + */ + w_align = 4; + else + /* + * for the remainder bpp(4, 2 and 1), the pixel width alignment + * can ensure a line stride alignment of 16 bytes. For example, + * if bpp is 2, then the line stride can be 16 byte aligned if + * the width is 8 byte aligned + */ + w_align = order_base_2(VPDMA_DESC_ALIGN / depth_bytes); + + v4l_bound_align_image(&pix->width, MIN_W, MAX_W, w_align, &pix->height, MIN_H, MAX_H, H_ALIGN, S_ALIGN); pix->num_planes = fmt->coplanar ? 2 : 1; pix->pixelformat = fmt->fourcc; - if (type == VPE_FMT_TYPE_CAPTURE) { - struct vpe_q_data *s_q_data; - - /* get colorspace from the source queue */ - s_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); - - pix->colorspace = s_q_data->colorspace; - } else { - if (!pix->colorspace) - pix->colorspace = V4L2_COLORSPACE_SMPTE240M; + if (!pix->colorspace) { + if (fmt->fourcc == V4L2_PIX_FMT_RGB24 || + fmt->fourcc == V4L2_PIX_FMT_BGR24 || + fmt->fourcc == V4L2_PIX_FMT_RGB32 || + fmt->fourcc == V4L2_PIX_FMT_BGR32) { + pix->colorspace = V4L2_COLORSPACE_SRGB; + } else { + if (pix->height > 1280) /* HD */ + pix->colorspace = V4L2_COLORSPACE_REC709; + else /* SD */ + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + } } for (i = 0; i < pix->num_planes; i++) { - int depth; - plane_fmt = &pix->plane_fmt[i]; depth = fmt->vpdma_fmt[i]->depth; if (i == VPE_LUMA) - plane_fmt->bytesperline = - round_up((pix->width * depth) >> 3, - 1 << L_ALIGN); + plane_fmt->bytesperline = (pix->width * depth) >> 3; else plane_fmt->bytesperline = pix->width; @@ -1749,6 +1837,14 @@ static int vpe_open(struct file *file) if (ret != 0) goto free_desc_list; + ret = vpdma_alloc_desc_buf(&ctx->sc_coeff_h, SC_COEF_SRAM_SIZE); + if (ret != 0) + goto free_mmr_adb; + + ret = vpdma_alloc_desc_buf(&ctx->sc_coeff_v, SC_COEF_SRAM_SIZE); + if (ret != 0) + goto free_sc_h; + init_adb_hdrs(ctx); v4l2_fh_init(&ctx->fh, video_devdata(file)); @@ -1770,7 +1866,7 @@ static int vpe_open(struct file *file) s_q_data->height = 1080; s_q_data->sizeimage[VPE_LUMA] = (s_q_data->width * s_q_data->height * s_q_data->fmt->vpdma_fmt[VPE_LUMA]->depth) >> 3; - s_q_data->colorspace = V4L2_COLORSPACE_SMPTE240M; + s_q_data->colorspace = V4L2_COLORSPACE_SMPTE170M; s_q_data->field = V4L2_FIELD_NONE; s_q_data->c_rect.left = 0; s_q_data->c_rect.top = 0; @@ -1817,6 +1913,10 @@ static int vpe_open(struct file *file) exit_fh: v4l2_ctrl_handler_free(hdl); v4l2_fh_exit(&ctx->fh); + vpdma_free_desc_buf(&ctx->sc_coeff_v); +free_sc_h: + vpdma_free_desc_buf(&ctx->sc_coeff_h); +free_mmr_adb: vpdma_free_desc_buf(&ctx->mmr_adb); free_desc_list: vpdma_free_desc_list(&ctx->desc_list); @@ -1938,12 +2038,11 @@ static int vpe_probe(struct platform_device *pdev) { struct vpe_dev *dev; struct video_device *vfd; - struct resource *res; int ret, irq, func; dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); - if (IS_ERR(dev)) - return PTR_ERR(dev); + if (!dev) + return -ENOMEM; spin_lock_init(&dev->lock); @@ -1954,16 +2053,17 @@ static int vpe_probe(struct platform_device *pdev) atomic_set(&dev->num_instances, 0); mutex_init(&dev->dev_mutex); - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vpe_top"); + dev->res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "vpe_top"); /* * HACK: we get resource info from device tree in the form of a list of * VPE sub blocks, the driver currently uses only the base of vpe_top * for register access, the driver should be changed later to access * registers based on the sub block base addresses */ - dev->base = devm_ioremap(&pdev->dev, res->start, SZ_32K); - if (IS_ERR(dev->base)) { - ret = PTR_ERR(dev->base); + dev->base = devm_ioremap(&pdev->dev, dev->res->start, SZ_32K); + if (!dev->base) { + ret = -ENOMEM; goto v4l2_dev_unreg; } @@ -2006,9 +2106,23 @@ static int vpe_probe(struct platform_device *pdev) vpe_top_vpdma_reset(dev); + dev->sc = sc_create(pdev); + if (IS_ERR(dev->sc)) { + ret = PTR_ERR(dev->sc); + goto runtime_put; + } + + dev->csc = csc_create(pdev); + if (IS_ERR(dev->csc)) { + ret = PTR_ERR(dev->csc); + goto runtime_put; + } + dev->vpdma = vpdma_create(pdev); - if (IS_ERR(dev->vpdma)) + if (IS_ERR(dev->vpdma)) { + ret = PTR_ERR(dev->vpdma); goto runtime_put; + } vfd = &dev->vfd; *vfd = vpe_videodev; @@ -2081,18 +2195,7 @@ static struct platform_driver vpe_pdrv = { }, }; -static void __exit vpe_exit(void) -{ - platform_driver_unregister(&vpe_pdrv); -} - -static int __init vpe_init(void) -{ - return platform_driver_register(&vpe_pdrv); -} - -module_init(vpe_init); -module_exit(vpe_exit); +module_platform_driver(vpe_pdrv); MODULE_DESCRIPTION("TI VPE driver"); MODULE_AUTHOR("Dale Farnsworth, <dale@farnsworth.org>"); diff --git a/drivers/media/platform/ti-vpe/vpe_regs.h b/drivers/media/platform/ti-vpe/vpe_regs.h index ed214e828398..74283d79eae1 100644 --- a/drivers/media/platform/ti-vpe/vpe_regs.h +++ b/drivers/media/platform/ti-vpe/vpe_regs.h @@ -306,191 +306,4 @@ #define VPE_FMD_FRAME_DIFF_MASK 0x000fffff #define VPE_FMD_FRAME_DIFF_SHIFT 0 -/* VPE scaler regs */ -#define VPE_SC_MP_SC0 0x0700 -#define VPE_INTERLACE_O (1 << 0) -#define VPE_LINEAR (1 << 1) -#define VPE_SC_BYPASS (1 << 2) -#define VPE_INVT_FID (1 << 3) -#define VPE_USE_RAV (1 << 4) -#define VPE_ENABLE_EV (1 << 5) -#define VPE_AUTO_HS (1 << 6) -#define VPE_DCM_2X (1 << 7) -#define VPE_DCM_4X (1 << 8) -#define VPE_HP_BYPASS (1 << 9) -#define VPE_INTERLACE_I (1 << 10) -#define VPE_ENABLE_SIN2_VER_INTP (1 << 11) -#define VPE_Y_PK_EN (1 << 14) -#define VPE_TRIM (1 << 15) -#define VPE_SELFGEN_FID (1 << 16) - -#define VPE_SC_MP_SC1 0x0704 -#define VPE_ROW_ACC_INC_MASK 0x07ffffff -#define VPE_ROW_ACC_INC_SHIFT 0 - -#define VPE_SC_MP_SC2 0x0708 -#define VPE_ROW_ACC_OFFSET_MASK 0x0fffffff -#define VPE_ROW_ACC_OFFSET_SHIFT 0 - -#define VPE_SC_MP_SC3 0x070c -#define VPE_ROW_ACC_OFFSET_B_MASK 0x0fffffff -#define VPE_ROW_ACC_OFFSET_B_SHIFT 0 - -#define VPE_SC_MP_SC4 0x0710 -#define VPE_TAR_H_MASK 0x07ff -#define VPE_TAR_H_SHIFT 0 -#define VPE_TAR_W_MASK 0x07ff -#define VPE_TAR_W_SHIFT 12 -#define VPE_LIN_ACC_INC_U_MASK 0x07 -#define VPE_LIN_ACC_INC_U_SHIFT 24 -#define VPE_NLIN_ACC_INIT_U_MASK 0x07 -#define VPE_NLIN_ACC_INIT_U_SHIFT 28 - -#define VPE_SC_MP_SC5 0x0714 -#define VPE_SRC_H_MASK 0x07ff -#define VPE_SRC_H_SHIFT 0 -#define VPE_SRC_W_MASK 0x07ff -#define VPE_SRC_W_SHIFT 12 -#define VPE_NLIN_ACC_INC_U_MASK 0x07 -#define VPE_NLIN_ACC_INC_U_SHIFT 24 - -#define VPE_SC_MP_SC6 0x0718 -#define VPE_ROW_ACC_INIT_RAV_MASK 0x03ff -#define VPE_ROW_ACC_INIT_RAV_SHIFT 0 -#define VPE_ROW_ACC_INIT_RAV_B_MASK 0x03ff -#define VPE_ROW_ACC_INIT_RAV_B_SHIFT 10 - -#define VPE_SC_MP_SC8 0x0720 -#define VPE_NLIN_LEFT_MASK 0x07ff -#define VPE_NLIN_LEFT_SHIFT 0 -#define VPE_NLIN_RIGHT_MASK 0x07ff -#define VPE_NLIN_RIGHT_SHIFT 12 - -#define VPE_SC_MP_SC9 0x0724 -#define VPE_LIN_ACC_INC VPE_SC_MP_SC9 - -#define VPE_SC_MP_SC10 0x0728 -#define VPE_NLIN_ACC_INIT VPE_SC_MP_SC10 - -#define VPE_SC_MP_SC11 0x072c -#define VPE_NLIN_ACC_INC VPE_SC_MP_SC11 - -#define VPE_SC_MP_SC12 0x0730 -#define VPE_COL_ACC_OFFSET_MASK 0x01ffffff -#define VPE_COL_ACC_OFFSET_SHIFT 0 - -#define VPE_SC_MP_SC13 0x0734 -#define VPE_SC_FACTOR_RAV_MASK 0x03ff -#define VPE_SC_FACTOR_RAV_SHIFT 0 -#define VPE_CHROMA_INTP_THR_MASK 0x03ff -#define VPE_CHROMA_INTP_THR_SHIFT 12 -#define VPE_DELTA_CHROMA_THR_MASK 0x0f -#define VPE_DELTA_CHROMA_THR_SHIFT 24 - -#define VPE_SC_MP_SC17 0x0744 -#define VPE_EV_THR_MASK 0x03ff -#define VPE_EV_THR_SHIFT 12 -#define VPE_DELTA_LUMA_THR_MASK 0x0f -#define VPE_DELTA_LUMA_THR_SHIFT 24 -#define VPE_DELTA_EV_THR_MASK 0x0f -#define VPE_DELTA_EV_THR_SHIFT 28 - -#define VPE_SC_MP_SC18 0x0748 -#define VPE_HS_FACTOR_MASK 0x03ff -#define VPE_HS_FACTOR_SHIFT 0 -#define VPE_CONF_DEFAULT_MASK 0x01ff -#define VPE_CONF_DEFAULT_SHIFT 16 - -#define VPE_SC_MP_SC19 0x074c -#define VPE_HPF_COEFF0_MASK 0xff -#define VPE_HPF_COEFF0_SHIFT 0 -#define VPE_HPF_COEFF1_MASK 0xff -#define VPE_HPF_COEFF1_SHIFT 8 -#define VPE_HPF_COEFF2_MASK 0xff -#define VPE_HPF_COEFF2_SHIFT 16 -#define VPE_HPF_COEFF3_MASK 0xff -#define VPE_HPF_COEFF3_SHIFT 23 - -#define VPE_SC_MP_SC20 0x0750 -#define VPE_HPF_COEFF4_MASK 0xff -#define VPE_HPF_COEFF4_SHIFT 0 -#define VPE_HPF_COEFF5_MASK 0xff -#define VPE_HPF_COEFF5_SHIFT 8 -#define VPE_HPF_NORM_SHIFT_MASK 0x07 -#define VPE_HPF_NORM_SHIFT_SHIFT 16 -#define VPE_NL_LIMIT_MASK 0x1ff -#define VPE_NL_LIMIT_SHIFT 20 - -#define VPE_SC_MP_SC21 0x0754 -#define VPE_NL_LO_THR_MASK 0x01ff -#define VPE_NL_LO_THR_SHIFT 0 -#define VPE_NL_LO_SLOPE_MASK 0xff -#define VPE_NL_LO_SLOPE_SHIFT 16 - -#define VPE_SC_MP_SC22 0x0758 -#define VPE_NL_HI_THR_MASK 0x01ff -#define VPE_NL_HI_THR_SHIFT 0 -#define VPE_NL_HI_SLOPE_SH_MASK 0x07 -#define VPE_NL_HI_SLOPE_SH_SHIFT 16 - -#define VPE_SC_MP_SC23 0x075c -#define VPE_GRADIENT_THR_MASK 0x07ff -#define VPE_GRADIENT_THR_SHIFT 0 -#define VPE_GRADIENT_THR_RANGE_MASK 0x0f -#define VPE_GRADIENT_THR_RANGE_SHIFT 12 -#define VPE_MIN_GY_THR_MASK 0xff -#define VPE_MIN_GY_THR_SHIFT 16 -#define VPE_MIN_GY_THR_RANGE_MASK 0x0f -#define VPE_MIN_GY_THR_RANGE_SHIFT 28 - -#define VPE_SC_MP_SC24 0x0760 -#define VPE_ORG_H_MASK 0x07ff -#define VPE_ORG_H_SHIFT 0 -#define VPE_ORG_W_MASK 0x07ff -#define VPE_ORG_W_SHIFT 16 - -#define VPE_SC_MP_SC25 0x0764 -#define VPE_OFF_H_MASK 0x07ff -#define VPE_OFF_H_SHIFT 0 -#define VPE_OFF_W_MASK 0x07ff -#define VPE_OFF_W_SHIFT 16 - -/* VPE color space converter regs */ -#define VPE_CSC_CSC00 0x5700 -#define VPE_CSC_A0_MASK 0x1fff -#define VPE_CSC_A0_SHIFT 0 -#define VPE_CSC_B0_MASK 0x1fff -#define VPE_CSC_B0_SHIFT 16 - -#define VPE_CSC_CSC01 0x5704 -#define VPE_CSC_C0_MASK 0x1fff -#define VPE_CSC_C0_SHIFT 0 -#define VPE_CSC_A1_MASK 0x1fff -#define VPE_CSC_A1_SHIFT 16 - -#define VPE_CSC_CSC02 0x5708 -#define VPE_CSC_B1_MASK 0x1fff -#define VPE_CSC_B1_SHIFT 0 -#define VPE_CSC_C1_MASK 0x1fff -#define VPE_CSC_C1_SHIFT 16 - -#define VPE_CSC_CSC03 0x570c -#define VPE_CSC_A2_MASK 0x1fff -#define VPE_CSC_A2_SHIFT 0 -#define VPE_CSC_B2_MASK 0x1fff -#define VPE_CSC_B2_SHIFT 16 - -#define VPE_CSC_CSC04 0x5710 -#define VPE_CSC_C2_MASK 0x1fff -#define VPE_CSC_C2_SHIFT 0 -#define VPE_CSC_D0_MASK 0x0fff -#define VPE_CSC_D0_SHIFT 16 - -#define VPE_CSC_CSC05 0x5714 -#define VPE_CSC_D1_MASK 0x0fff -#define VPE_CSC_D1_SHIFT 0 -#define VPE_CSC_D2_MASK 0x0fff -#define VPE_CSC_D2_SHIFT 16 -#define VPE_CSC_BYPASS (1 << 28) - #endif diff --git a/drivers/media/platform/vsp1/Makefile b/drivers/media/platform/vsp1/Makefile index 4da226169e15..151cecd0ea25 100644 --- a/drivers/media/platform/vsp1/Makefile +++ b/drivers/media/platform/vsp1/Makefile @@ -1,5 +1,6 @@ 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 +vsp1-y += vsp1_hsit.o vsp1_lif.o vsp1_lut.o +vsp1-y += vsp1_sru.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 index d6c6ecd039ff..94d1b02680c5 100644 --- a/drivers/media/platform/vsp1/vsp1.h +++ b/drivers/media/platform/vsp1/vsp1.h @@ -28,8 +28,11 @@ struct clk; struct device; struct vsp1_platform_data; +struct vsp1_hsit; struct vsp1_lif; +struct vsp1_lut; struct vsp1_rwpf; +struct vsp1_sru; struct vsp1_uds; #define VPS1_MAX_RPF 5 @@ -47,8 +50,12 @@ struct vsp1_device { struct mutex lock; int ref_count; + struct vsp1_hsit *hsi; + struct vsp1_hsit *hst; struct vsp1_lif *lif; + struct vsp1_lut *lut; struct vsp1_rwpf *rpf[VPS1_MAX_RPF]; + struct vsp1_sru *sru; struct vsp1_uds *uds[VPS1_MAX_UDS]; struct vsp1_rwpf *wpf[VPS1_MAX_WPF]; diff --git a/drivers/media/platform/vsp1/vsp1_drv.c b/drivers/media/platform/vsp1/vsp1_drv.c index d16bf0f41e24..0df0a994e575 100644 --- a/drivers/media/platform/vsp1/vsp1_drv.c +++ b/drivers/media/platform/vsp1/vsp1_drv.c @@ -20,8 +20,11 @@ #include <linux/videodev2.h> #include "vsp1.h" +#include "vsp1_hsit.h" #include "vsp1_lif.h" +#include "vsp1_lut.h" #include "vsp1_rwpf.h" +#include "vsp1_sru.h" #include "vsp1_uds.h" /* ----------------------------------------------------------------------------- @@ -152,6 +155,22 @@ static int vsp1_create_entities(struct vsp1_device *vsp1) } /* Instantiate all the entities. */ + vsp1->hsi = vsp1_hsit_create(vsp1, true); + if (IS_ERR(vsp1->hsi)) { + ret = PTR_ERR(vsp1->hsi); + goto done; + } + + list_add_tail(&vsp1->hsi->entity.list_dev, &vsp1->entities); + + vsp1->hst = vsp1_hsit_create(vsp1, false); + if (IS_ERR(vsp1->hst)) { + ret = PTR_ERR(vsp1->hst); + goto done; + } + + list_add_tail(&vsp1->hst->entity.list_dev, &vsp1->entities); + if (vsp1->pdata->features & VSP1_HAS_LIF) { vsp1->lif = vsp1_lif_create(vsp1); if (IS_ERR(vsp1->lif)) { @@ -162,6 +181,16 @@ static int vsp1_create_entities(struct vsp1_device *vsp1) list_add_tail(&vsp1->lif->entity.list_dev, &vsp1->entities); } + if (vsp1->pdata->features & VSP1_HAS_LUT) { + vsp1->lut = vsp1_lut_create(vsp1); + if (IS_ERR(vsp1->lut)) { + ret = PTR_ERR(vsp1->lut); + goto done; + } + + list_add_tail(&vsp1->lut->entity.list_dev, &vsp1->entities); + } + for (i = 0; i < vsp1->pdata->rpf_count; ++i) { struct vsp1_rwpf *rpf; @@ -175,6 +204,16 @@ static int vsp1_create_entities(struct vsp1_device *vsp1) list_add_tail(&rpf->entity.list_dev, &vsp1->entities); } + if (vsp1->pdata->features & VSP1_HAS_SRU) { + vsp1->sru = vsp1_sru_create(vsp1); + if (IS_ERR(vsp1->sru)) { + ret = PTR_ERR(vsp1->sru); + goto done; + } + + list_add_tail(&vsp1->sru->entity.list_dev, &vsp1->entities); + } + for (i = 0; i < vsp1->pdata->uds_count; ++i) { struct vsp1_uds *uds; diff --git a/drivers/media/platform/vsp1/vsp1_entity.c b/drivers/media/platform/vsp1/vsp1_entity.c index 9028f9d524f4..0226e47df6d9 100644 --- a/drivers/media/platform/vsp1/vsp1_entity.c +++ b/drivers/media/platform/vsp1/vsp1_entity.c @@ -15,6 +15,7 @@ #include <linux/gfp.h> #include <media/media-entity.h> +#include <media/v4l2-ctrls.h> #include <media/v4l2-subdev.h> #include "vsp1.h" @@ -122,12 +123,16 @@ int vsp1_entity_init(struct vsp1_device *vsp1, struct vsp1_entity *entity, unsigned int id; unsigned int reg; } routes[] = { + { VI6_DPR_NODE_HSI, VI6_DPR_HSI_ROUTE }, + { VI6_DPR_NODE_HST, VI6_DPR_HST_ROUTE }, { VI6_DPR_NODE_LIF, 0 }, + { VI6_DPR_NODE_LUT, VI6_DPR_LUT_ROUTE }, { 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_SRU, VI6_DPR_SRU_ROUTE }, { 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) }, @@ -177,5 +182,7 @@ int vsp1_entity_init(struct vsp1_device *vsp1, struct vsp1_entity *entity, void vsp1_entity_destroy(struct vsp1_entity *entity) { + if (entity->subdev.ctrl_handler) + v4l2_ctrl_handler_free(entity->subdev.ctrl_handler); media_entity_cleanup(&entity->subdev.entity); } diff --git a/drivers/media/platform/vsp1/vsp1_entity.h b/drivers/media/platform/vsp1/vsp1_entity.h index c4feab2cbb81..e152798d7f38 100644 --- a/drivers/media/platform/vsp1/vsp1_entity.h +++ b/drivers/media/platform/vsp1/vsp1_entity.h @@ -20,8 +20,12 @@ struct vsp1_device; enum vsp1_entity_type { + VSP1_ENTITY_HSI, + VSP1_ENTITY_HST, VSP1_ENTITY_LIF, + VSP1_ENTITY_LUT, VSP1_ENTITY_RPF, + VSP1_ENTITY_SRU, VSP1_ENTITY_UDS, VSP1_ENTITY_WPF, }; diff --git a/drivers/media/platform/vsp1/vsp1_hsit.c b/drivers/media/platform/vsp1/vsp1_hsit.c new file mode 100644 index 000000000000..285485350d82 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_hsit.c @@ -0,0 +1,222 @@ +/* + * vsp1_hsit.c -- R-Car VSP1 Hue Saturation value (Inverse) Transform + * + * 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_hsit.h" + +#define HSIT_MIN_SIZE 4U +#define HSIT_MAX_SIZE 8190U + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline u32 vsp1_hsit_read(struct vsp1_hsit *hsit, u32 reg) +{ + return vsp1_read(hsit->entity.vsp1, reg); +} + +static inline void vsp1_hsit_write(struct vsp1_hsit *hsit, u32 reg, u32 data) +{ + vsp1_write(hsit->entity.vsp1, reg, data); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Core Operations + */ + +static int hsit_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct vsp1_hsit *hsit = to_hsit(subdev); + + if (!enable) + return 0; + + if (hsit->inverse) + vsp1_hsit_write(hsit, VI6_HSI_CTRL, VI6_HSI_CTRL_EN); + else + vsp1_hsit_write(hsit, VI6_HST_CTRL, VI6_HST_CTRL_EN); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Pad Operations + */ + +static int hsit_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct vsp1_hsit *hsit = to_hsit(subdev); + + if (code->index > 0) + return -EINVAL; + + if ((code->pad == HSIT_PAD_SINK && !hsit->inverse) | + (code->pad == HSIT_PAD_SOURCE && hsit->inverse)) + code->code = V4L2_MBUS_FMT_ARGB8888_1X32; + else + code->code = V4L2_MBUS_FMT_AHSV8888_1X32; + + return 0; +} + +static int hsit_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, fse->pad); + + if (fse->index || fse->code != format->code) + return -EINVAL; + + if (fse->pad == HSIT_PAD_SINK) { + fse->min_width = HSIT_MIN_SIZE; + fse->max_width = HSIT_MAX_SIZE; + fse->min_height = HSIT_MIN_SIZE; + fse->max_height = HSIT_MAX_SIZE; + } 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; +} + +static int hsit_get_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_hsit *hsit = to_hsit(subdev); + + fmt->format = *vsp1_entity_get_pad_format(&hsit->entity, fh, fmt->pad, + fmt->which); + + return 0; +} + +static int hsit_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_hsit *hsit = to_hsit(subdev); + struct v4l2_mbus_framefmt *format; + + format = vsp1_entity_get_pad_format(&hsit->entity, fh, fmt->pad, + fmt->which); + + if (fmt->pad == HSIT_PAD_SOURCE) { + /* The HST and HSI output format code and resolution can't be + * modified. + */ + fmt->format = *format; + return 0; + } + + format->code = hsit->inverse ? V4L2_MBUS_FMT_AHSV8888_1X32 + : V4L2_MBUS_FMT_ARGB8888_1X32; + format->width = clamp_t(unsigned int, fmt->format.width, + HSIT_MIN_SIZE, HSIT_MAX_SIZE); + format->height = clamp_t(unsigned int, fmt->format.height, + HSIT_MIN_SIZE, HSIT_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(&hsit->entity, fh, HSIT_PAD_SOURCE, + fmt->which); + *format = fmt->format; + format->code = hsit->inverse ? V4L2_MBUS_FMT_ARGB8888_1X32 + : V4L2_MBUS_FMT_AHSV8888_1X32; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static struct v4l2_subdev_video_ops hsit_video_ops = { + .s_stream = hsit_s_stream, +}; + +static struct v4l2_subdev_pad_ops hsit_pad_ops = { + .enum_mbus_code = hsit_enum_mbus_code, + .enum_frame_size = hsit_enum_frame_size, + .get_fmt = hsit_get_format, + .set_fmt = hsit_set_format, +}; + +static struct v4l2_subdev_ops hsit_ops = { + .video = &hsit_video_ops, + .pad = &hsit_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_hsit *vsp1_hsit_create(struct vsp1_device *vsp1, bool inverse) +{ + struct v4l2_subdev *subdev; + struct vsp1_hsit *hsit; + int ret; + + hsit = devm_kzalloc(vsp1->dev, sizeof(*hsit), GFP_KERNEL); + if (hsit == NULL) + return ERR_PTR(-ENOMEM); + + hsit->inverse = inverse; + + if (inverse) { + hsit->entity.type = VSP1_ENTITY_HSI; + hsit->entity.id = VI6_DPR_NODE_HSI; + } else { + hsit->entity.type = VSP1_ENTITY_HST; + hsit->entity.id = VI6_DPR_NODE_HST; + } + + ret = vsp1_entity_init(vsp1, &hsit->entity, 2); + if (ret < 0) + return ERR_PTR(ret); + + /* Initialize the V4L2 subdev. */ + subdev = &hsit->entity.subdev; + v4l2_subdev_init(subdev, &hsit_ops); + + subdev->entity.ops = &vsp1_media_ops; + subdev->internal_ops = &vsp1_subdev_internal_ops; + snprintf(subdev->name, sizeof(subdev->name), "%s %s", + dev_name(vsp1->dev), inverse ? "hsi" : "hst"); + v4l2_set_subdevdata(subdev, hsit); + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + vsp1_entity_init_formats(subdev, NULL); + + return hsit; +} diff --git a/drivers/media/platform/vsp1/vsp1_hsit.h b/drivers/media/platform/vsp1/vsp1_hsit.h new file mode 100644 index 000000000000..82f1c8426900 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_hsit.h @@ -0,0 +1,38 @@ +/* + * vsp1_hsit.h -- R-Car VSP1 Hue Saturation value (Inverse) Transform + * + * 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_HSIT_H__ +#define __VSP1_HSIT_H__ + +#include <media/media-entity.h> +#include <media/v4l2-subdev.h> + +#include "vsp1_entity.h" + +struct vsp1_device; + +#define HSIT_PAD_SINK 0 +#define HSIT_PAD_SOURCE 1 + +struct vsp1_hsit { + struct vsp1_entity entity; + bool inverse; +}; + +static inline struct vsp1_hsit *to_hsit(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_hsit, entity.subdev); +} + +struct vsp1_hsit *vsp1_hsit_create(struct vsp1_device *vsp1, bool inverse); + +#endif /* __VSP1_HSIT_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_lut.c b/drivers/media/platform/vsp1/vsp1_lut.c new file mode 100644 index 000000000000..4e9dc7c86ef8 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_lut.c @@ -0,0 +1,252 @@ +/* + * vsp1_lut.c -- R-Car VSP1 Look-Up Table + * + * 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 <linux/vsp1.h> + +#include <media/v4l2-subdev.h> + +#include "vsp1.h" +#include "vsp1_lut.h" + +#define LUT_MIN_SIZE 4U +#define LUT_MAX_SIZE 8190U + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline u32 vsp1_lut_read(struct vsp1_lut *lut, u32 reg) +{ + return vsp1_read(lut->entity.vsp1, reg); +} + +static inline void vsp1_lut_write(struct vsp1_lut *lut, u32 reg, u32 data) +{ + vsp1_write(lut->entity.vsp1, reg, data); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Core Operations + */ + +static void lut_configure(struct vsp1_lut *lut, struct vsp1_lut_config *config) +{ + memcpy_toio(lut->entity.vsp1->mmio + VI6_LUT_TABLE, config->lut, + sizeof(config->lut)); +} + +static long lut_ioctl(struct v4l2_subdev *subdev, unsigned int cmd, void *arg) +{ + struct vsp1_lut *lut = to_lut(subdev); + + switch (cmd) { + case VIDIOC_VSP1_LUT_CONFIG: + lut_configure(lut, arg); + return 0; + + default: + return -ENOIOCTLCMD; + } +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Video Operations + */ + +static int lut_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct vsp1_lut *lut = to_lut(subdev); + + if (!enable) + return 0; + + vsp1_lut_write(lut, VI6_LUT_CTRL, VI6_LUT_CTRL_EN); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Pad Operations + */ + +static int lut_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_AHSV8888_1X32, + V4L2_MBUS_FMT_AYUV8_1X32, + }; + struct v4l2_mbus_framefmt *format; + + if (code->pad == LUT_PAD_SINK) { + if (code->index >= ARRAY_SIZE(codes)) + return -EINVAL; + + code->code = codes[code->index]; + } else { + /* The LUT 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, LUT_PAD_SINK); + code->code = format->code; + } + + return 0; +} + +static int lut_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, fse->pad); + + if (fse->index || fse->code != format->code) + return -EINVAL; + + if (fse->pad == LUT_PAD_SINK) { + fse->min_width = LUT_MIN_SIZE; + fse->max_width = LUT_MAX_SIZE; + fse->min_height = LUT_MIN_SIZE; + fse->max_height = LUT_MAX_SIZE; + } 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; +} + +static int lut_get_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_lut *lut = to_lut(subdev); + + fmt->format = *vsp1_entity_get_pad_format(&lut->entity, fh, fmt->pad, + fmt->which); + + return 0; +} + +static int lut_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_lut *lut = to_lut(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_AHSV8888_1X32 && + fmt->format.code != V4L2_MBUS_FMT_AYUV8_1X32) + fmt->format.code = V4L2_MBUS_FMT_AYUV8_1X32; + + format = vsp1_entity_get_pad_format(&lut->entity, fh, fmt->pad, + fmt->which); + + if (fmt->pad == LUT_PAD_SOURCE) { + /* The LUT output format can't be modified. */ + fmt->format = *format; + return 0; + } + + format->width = clamp_t(unsigned int, fmt->format.width, + LUT_MIN_SIZE, LUT_MAX_SIZE); + format->height = clamp_t(unsigned int, fmt->format.height, + LUT_MIN_SIZE, LUT_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(&lut->entity, fh, LUT_PAD_SOURCE, + fmt->which); + *format = fmt->format; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static struct v4l2_subdev_core_ops lut_core_ops = { + .ioctl = lut_ioctl, +}; + +static struct v4l2_subdev_video_ops lut_video_ops = { + .s_stream = lut_s_stream, +}; + +static struct v4l2_subdev_pad_ops lut_pad_ops = { + .enum_mbus_code = lut_enum_mbus_code, + .enum_frame_size = lut_enum_frame_size, + .get_fmt = lut_get_format, + .set_fmt = lut_set_format, +}; + +static struct v4l2_subdev_ops lut_ops = { + .core = &lut_core_ops, + .video = &lut_video_ops, + .pad = &lut_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_lut *vsp1_lut_create(struct vsp1_device *vsp1) +{ + struct v4l2_subdev *subdev; + struct vsp1_lut *lut; + int ret; + + lut = devm_kzalloc(vsp1->dev, sizeof(*lut), GFP_KERNEL); + if (lut == NULL) + return ERR_PTR(-ENOMEM); + + lut->entity.type = VSP1_ENTITY_LUT; + lut->entity.id = VI6_DPR_NODE_LUT; + + ret = vsp1_entity_init(vsp1, &lut->entity, 2); + if (ret < 0) + return ERR_PTR(ret); + + /* Initialize the V4L2 subdev. */ + subdev = &lut->entity.subdev; + v4l2_subdev_init(subdev, &lut_ops); + + subdev->entity.ops = &vsp1_media_ops; + subdev->internal_ops = &vsp1_subdev_internal_ops; + snprintf(subdev->name, sizeof(subdev->name), "%s lut", + dev_name(vsp1->dev)); + v4l2_set_subdevdata(subdev, lut); + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + vsp1_entity_init_formats(subdev, NULL); + + return lut; +} diff --git a/drivers/media/platform/vsp1/vsp1_lut.h b/drivers/media/platform/vsp1/vsp1_lut.h new file mode 100644 index 000000000000..f92ffb867350 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_lut.h @@ -0,0 +1,38 @@ +/* + * vsp1_lut.h -- R-Car VSP1 Look-Up Table + * + * 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_LUT_H__ +#define __VSP1_LUT_H__ + +#include <media/media-entity.h> +#include <media/v4l2-subdev.h> + +#include "vsp1_entity.h" + +struct vsp1_device; + +#define LUT_PAD_SINK 0 +#define LUT_PAD_SOURCE 1 + +struct vsp1_lut { + struct vsp1_entity entity; + u32 lut[256]; +}; + +static inline struct vsp1_lut *to_lut(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_lut, entity.subdev); +} + +struct vsp1_lut *vsp1_lut_create(struct vsp1_device *vsp1); + +#endif /* __VSP1_LUT_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_regs.h b/drivers/media/platform/vsp1/vsp1_regs.h index 1d3304f1365b..28650806c20f 100644 --- a/drivers/media/platform/vsp1/vsp1_regs.h +++ b/drivers/media/platform/vsp1/vsp1_regs.h @@ -336,8 +336,21 @@ */ #define VI6_SRU_CTRL0 0x2200 +#define VI6_SRU_CTRL0_PARAM0_SHIFT 16 +#define VI6_SRU_CTRL0_PARAM1_SHIFT 8 +#define VI6_SRU_CTRL0_MODE_UPSCALE (4 << 4) +#define VI6_SRU_CTRL0_PARAM2 (1 << 3) +#define VI6_SRU_CTRL0_PARAM3 (1 << 2) +#define VI6_SRU_CTRL0_PARAM4 (1 << 1) +#define VI6_SRU_CTRL0_EN (1 << 0) + #define VI6_SRU_CTRL1 0x2204 +#define VI6_SRU_CTRL1_PARAM5 0x7ff + #define VI6_SRU_CTRL2 0x2208 +#define VI6_SRU_CTRL2_PARAM6_SHIFT 16 +#define VI6_SRU_CTRL2_PARAM7_SHIFT 8 +#define VI6_SRU_CTRL2_PARAM8_SHIFT 0 /* ----------------------------------------------------------------------------- * UDS Control Registers @@ -412,6 +425,7 @@ */ #define VI6_LUT_CTRL 0x2800 +#define VI6_LUT_CTRL_EN (1 << 0) /* ----------------------------------------------------------------------------- * CLU Control Registers @@ -424,12 +438,14 @@ */ #define VI6_HST_CTRL 0x2a00 +#define VI6_HST_CTRL_EN (1 << 0) /* ----------------------------------------------------------------------------- * HSI Control Registers */ #define VI6_HSI_CTRL 0x2b00 +#define VI6_HSI_CTRL_EN (1 << 0) /* ----------------------------------------------------------------------------- * BRU Control Registers diff --git a/drivers/media/platform/vsp1/vsp1_rpf.c b/drivers/media/platform/vsp1/vsp1_rpf.c index 254871d3423e..bce2be5466b9 100644 --- a/drivers/media/platform/vsp1/vsp1_rpf.c +++ b/drivers/media/platform/vsp1/vsp1_rpf.c @@ -47,25 +47,36 @@ 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; + const struct v4l2_rect *crop = &rpf->crop; u32 pstride; u32 infmt; if (!enable) return 0; - /* Source size and stride. Cropping isn't supported yet. */ + /* Source size, stride and crop offsets. + * + * The crop offsets correspond to the location of the crop rectangle top + * left corner in the plane buffer. Only two offsets are needed, as + * planes 2 and 3 always have identical strides. + */ vsp1_rpf_write(rpf, VI6_RPF_SRC_BSIZE, - (format->width << VI6_RPF_SRC_BSIZE_BHSIZE_SHIFT) | - (format->height << VI6_RPF_SRC_BSIZE_BVSIZE_SHIFT)); + (crop->width << VI6_RPF_SRC_BSIZE_BHSIZE_SHIFT) | + (crop->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)); + (crop->width << VI6_RPF_SRC_ESIZE_EHSIZE_SHIFT) | + (crop->height << VI6_RPF_SRC_ESIZE_EVSIZE_SHIFT)); + rpf->offsets[0] = crop->top * format->plane_fmt[0].bytesperline + + crop->left * fmtinfo->bpp[0] / 8; pstride = format->plane_fmt[0].bytesperline << VI6_RPF_SRCM_PSTRIDE_Y_SHIFT; - if (format->num_planes > 1) + if (format->num_planes > 1) { + rpf->offsets[1] = crop->top * format->plane_fmt[1].bytesperline + + crop->left * fmtinfo->bpp[1] / 8; pstride |= format->plane_fmt[1].bytesperline << VI6_RPF_SRCM_PSTRIDE_C_SHIFT; + } vsp1_rpf_write(rpf, VI6_RPF_SRCM_PSTRIDE, pstride); @@ -113,6 +124,8 @@ static struct v4l2_subdev_pad_ops rpf_pad_ops = { .enum_frame_size = vsp1_rwpf_enum_frame_size, .get_fmt = vsp1_rwpf_get_format, .set_fmt = vsp1_rwpf_set_format, + .get_selection = vsp1_rwpf_get_selection, + .set_selection = vsp1_rwpf_set_selection, }; static struct v4l2_subdev_ops rpf_ops = { @@ -129,11 +142,14 @@ static void rpf_vdev_queue(struct vsp1_video *video, { struct vsp1_rwpf *rpf = container_of(video, struct vsp1_rwpf, video); - vsp1_rpf_write(rpf, VI6_RPF_SRCM_ADDR_Y, buf->addr[0]); + vsp1_rpf_write(rpf, VI6_RPF_SRCM_ADDR_Y, + buf->addr[0] + rpf->offsets[0]); if (buf->buf.num_planes > 1) - vsp1_rpf_write(rpf, VI6_RPF_SRCM_ADDR_C0, buf->addr[1]); + vsp1_rpf_write(rpf, VI6_RPF_SRCM_ADDR_C0, + buf->addr[1] + rpf->offsets[1]); if (buf->buf.num_planes > 2) - vsp1_rpf_write(rpf, VI6_RPF_SRCM_ADDR_C1, buf->addr[2]); + vsp1_rpf_write(rpf, VI6_RPF_SRCM_ADDR_C1, + buf->addr[2] + rpf->offsets[1]); } static const struct vsp1_video_operations rpf_vdev_ops = { diff --git a/drivers/media/platform/vsp1/vsp1_rwpf.c b/drivers/media/platform/vsp1/vsp1_rwpf.c index 9752d5516ceb..782f770daee5 100644 --- a/drivers/media/platform/vsp1/vsp1_rwpf.c +++ b/drivers/media/platform/vsp1/vsp1_rwpf.c @@ -71,6 +71,19 @@ int vsp1_rwpf_enum_frame_size(struct v4l2_subdev *subdev, return 0; } +static struct v4l2_rect * +vsp1_rwpf_get_crop(struct vsp1_rwpf *rwpf, struct v4l2_subdev_fh *fh, u32 which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(fh, RWPF_PAD_SINK); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &rwpf->crop; + default: + return NULL; + } +} + int vsp1_rwpf_get_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, struct v4l2_subdev_format *fmt) { @@ -87,6 +100,7 @@ int vsp1_rwpf_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, { struct vsp1_rwpf *rwpf = to_rwpf(subdev); struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; /* Default to YUV if the requested format is not supported. */ if (fmt->format.code != V4L2_MBUS_FMT_ARGB8888_1X32 && @@ -115,6 +129,13 @@ int vsp1_rwpf_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, fmt->format = *format; + /* Update the sink crop rectangle. */ + crop = vsp1_rwpf_get_crop(rwpf, fh, fmt->which); + crop->left = 0; + crop->top = 0; + crop->width = fmt->format.width; + crop->height = fmt->format.height; + /* Propagate the format to the source pad. */ format = vsp1_entity_get_pad_format(&rwpf->entity, fh, RWPF_PAD_SOURCE, fmt->which); @@ -122,3 +143,78 @@ int vsp1_rwpf_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, return 0; } + +int vsp1_rwpf_get_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_selection *sel) +{ + struct vsp1_rwpf *rwpf = to_rwpf(subdev); + struct v4l2_mbus_framefmt *format; + + /* Cropping is implemented on the sink pad. */ + if (sel->pad != RWPF_PAD_SINK) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r = *vsp1_rwpf_get_crop(rwpf, fh, sel->which); + break; + + case V4L2_SEL_TGT_CROP_BOUNDS: + format = vsp1_entity_get_pad_format(&rwpf->entity, fh, + RWPF_PAD_SINK, sel->which); + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = format->width; + sel->r.height = format->height; + break; + + default: + return -EINVAL; + } + + return 0; +} + +int vsp1_rwpf_set_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_selection *sel) +{ + struct vsp1_rwpf *rwpf = to_rwpf(subdev); + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + + /* Cropping is implemented on the sink pad. */ + if (sel->pad != RWPF_PAD_SINK) + return -EINVAL; + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + /* Make sure the crop rectangle is entirely contained in the image. The + * WPF top and left offsets are limited to 255. + */ + format = vsp1_entity_get_pad_format(&rwpf->entity, fh, RWPF_PAD_SINK, + sel->which); + sel->r.left = min_t(unsigned int, sel->r.left, format->width - 2); + sel->r.top = min_t(unsigned int, sel->r.top, format->height - 2); + if (rwpf->entity.type == VSP1_ENTITY_WPF) { + sel->r.left = min_t(unsigned int, sel->r.left, 255); + sel->r.top = min_t(unsigned int, sel->r.top, 255); + } + sel->r.width = min_t(unsigned int, sel->r.width, + format->width - sel->r.left); + sel->r.height = min_t(unsigned int, sel->r.height, + format->height - sel->r.top); + + crop = vsp1_rwpf_get_crop(rwpf, fh, sel->which); + *crop = sel->r; + + /* Propagate the format to the source pad. */ + format = vsp1_entity_get_pad_format(&rwpf->entity, fh, RWPF_PAD_SOURCE, + sel->which); + format->width = crop->width; + format->height = crop->height; + + return 0; +} diff --git a/drivers/media/platform/vsp1/vsp1_rwpf.h b/drivers/media/platform/vsp1/vsp1_rwpf.h index c182d85f36b3..6cbdb547470b 100644 --- a/drivers/media/platform/vsp1/vsp1_rwpf.h +++ b/drivers/media/platform/vsp1/vsp1_rwpf.h @@ -29,6 +29,10 @@ struct vsp1_rwpf { unsigned int max_width; unsigned int max_height; + + struct v4l2_rect crop; + + unsigned int offsets[2]; }; static inline struct vsp1_rwpf *to_rwpf(struct v4l2_subdev *subdev) @@ -49,5 +53,11 @@ 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); +int vsp1_rwpf_get_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_selection *sel); +int vsp1_rwpf_set_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_selection *sel); #endif /* __VSP1_RWPF_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_sru.c b/drivers/media/platform/vsp1/vsp1_sru.c new file mode 100644 index 000000000000..7ab1a0b2d656 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_sru.c @@ -0,0 +1,356 @@ +/* + * vsp1_sru.c -- R-Car VSP1 Super Resolution Unit + * + * 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_sru.h" + +#define SRU_MIN_SIZE 4U +#define SRU_MAX_SIZE 8190U + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline u32 vsp1_sru_read(struct vsp1_sru *sru, u32 reg) +{ + return vsp1_read(sru->entity.vsp1, reg); +} + +static inline void vsp1_sru_write(struct vsp1_sru *sru, u32 reg, u32 data) +{ + vsp1_write(sru->entity.vsp1, reg, data); +} + +/* ----------------------------------------------------------------------------- + * Controls + */ + +#define V4L2_CID_VSP1_SRU_INTENSITY (V4L2_CID_USER_BASE + 1) + +static int sru_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct vsp1_sru *sru = + container_of(ctrl->handler, struct vsp1_sru, ctrls); + + switch (ctrl->id) { + case V4L2_CID_VSP1_SRU_INTENSITY: + sru->intensity = ctrl->val; + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops sru_ctrl_ops = { + .s_ctrl = sru_s_ctrl, +}; + +static const struct v4l2_ctrl_config sru_intensity_control = { + .ops = &sru_ctrl_ops, + .id = V4L2_CID_VSP1_SRU_INTENSITY, + .name = "Intensity", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 1, + .max = 6, + .step = 1, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Core Operations + */ + +struct vsp1_sru_param { + u32 ctrl0; + u32 ctrl2; +}; + +#define VI6_SRU_CTRL0_PARAMS(p0, p1) \ + (((p0) << VI6_SRU_CTRL0_PARAM0_SHIFT) | \ + ((p1) << VI6_SRU_CTRL0_PARAM1_SHIFT)) + +#define VI6_SRU_CTRL2_PARAMS(p6, p7, p8) \ + (((p6) << VI6_SRU_CTRL2_PARAM6_SHIFT) | \ + ((p7) << VI6_SRU_CTRL2_PARAM7_SHIFT) | \ + ((p8) << VI6_SRU_CTRL2_PARAM8_SHIFT)) + +static const struct vsp1_sru_param vsp1_sru_params[] = { + { + .ctrl0 = VI6_SRU_CTRL0_PARAMS(256, 4) | VI6_SRU_CTRL0_EN, + .ctrl2 = VI6_SRU_CTRL2_PARAMS(24, 40, 255), + }, { + .ctrl0 = VI6_SRU_CTRL0_PARAMS(256, 4) | VI6_SRU_CTRL0_EN, + .ctrl2 = VI6_SRU_CTRL2_PARAMS(8, 16, 255), + }, { + .ctrl0 = VI6_SRU_CTRL0_PARAMS(384, 5) | VI6_SRU_CTRL0_EN, + .ctrl2 = VI6_SRU_CTRL2_PARAMS(36, 60, 255), + }, { + .ctrl0 = VI6_SRU_CTRL0_PARAMS(384, 5) | VI6_SRU_CTRL0_EN, + .ctrl2 = VI6_SRU_CTRL2_PARAMS(12, 27, 255), + }, { + .ctrl0 = VI6_SRU_CTRL0_PARAMS(511, 6) | VI6_SRU_CTRL0_EN, + .ctrl2 = VI6_SRU_CTRL2_PARAMS(48, 80, 255), + }, { + .ctrl0 = VI6_SRU_CTRL0_PARAMS(511, 6) | VI6_SRU_CTRL0_EN, + .ctrl2 = VI6_SRU_CTRL2_PARAMS(16, 36, 255), + }, +}; + +static int sru_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct vsp1_sru *sru = to_sru(subdev); + const struct vsp1_sru_param *param; + struct v4l2_mbus_framefmt *input; + struct v4l2_mbus_framefmt *output; + bool upscale; + u32 ctrl0; + + if (!enable) + return 0; + + input = &sru->entity.formats[SRU_PAD_SINK]; + output = &sru->entity.formats[SRU_PAD_SOURCE]; + upscale = input->width != output->width; + param = &vsp1_sru_params[sru->intensity]; + + if (input->code == V4L2_MBUS_FMT_ARGB8888_1X32) + ctrl0 = VI6_SRU_CTRL0_PARAM2 | VI6_SRU_CTRL0_PARAM3 + | VI6_SRU_CTRL0_PARAM4; + else + ctrl0 = VI6_SRU_CTRL0_PARAM3; + + vsp1_sru_write(sru, VI6_SRU_CTRL0, param->ctrl0 | ctrl0 | + (upscale ? VI6_SRU_CTRL0_MODE_UPSCALE : 0)); + vsp1_sru_write(sru, VI6_SRU_CTRL1, VI6_SRU_CTRL1_PARAM5); + vsp1_sru_write(sru, VI6_SRU_CTRL2, param->ctrl2); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Pad Operations + */ + +static int sru_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, + }; + struct v4l2_mbus_framefmt *format; + + if (code->pad == SRU_PAD_SINK) { + if (code->index >= ARRAY_SIZE(codes)) + return -EINVAL; + + code->code = codes[code->index]; + } else { + /* The SRU 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, SRU_PAD_SINK); + code->code = format->code; + } + + return 0; +} + +static int sru_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, SRU_PAD_SINK); + + if (fse->index || fse->code != format->code) + return -EINVAL; + + if (fse->pad == SRU_PAD_SINK) { + fse->min_width = SRU_MIN_SIZE; + fse->max_width = SRU_MAX_SIZE; + fse->min_height = SRU_MIN_SIZE; + fse->max_height = SRU_MAX_SIZE; + } else { + fse->min_width = format->width; + fse->min_height = format->height; + if (format->width <= SRU_MAX_SIZE / 2 && + format->height <= SRU_MAX_SIZE / 2) { + fse->max_width = format->width * 2; + fse->max_height = format->height * 2; + } else { + fse->max_width = format->width; + fse->max_height = format->height; + } + } + + return 0; +} + +static int sru_get_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_sru *sru = to_sru(subdev); + + fmt->format = *vsp1_entity_get_pad_format(&sru->entity, fh, fmt->pad, + fmt->which); + + return 0; +} + +static void sru_try_format(struct vsp1_sru *sru, 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 input_area; + unsigned int output_area; + + switch (pad) { + case SRU_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, SRU_MIN_SIZE, SRU_MAX_SIZE); + fmt->height = clamp(fmt->height, SRU_MIN_SIZE, SRU_MAX_SIZE); + break; + + case SRU_PAD_SOURCE: + /* The SRU can't perform format conversion. */ + format = vsp1_entity_get_pad_format(&sru->entity, fh, + SRU_PAD_SINK, which); + fmt->code = format->code; + + /* We can upscale by 2 in both direction, but not independently. + * Compare the input and output rectangles areas (avoiding + * integer overflows on the output): if the requested output + * area is larger than 1.5^2 the input area upscale by two, + * otherwise don't scale. + */ + input_area = format->width * format->height; + output_area = min(fmt->width, SRU_MAX_SIZE) + * min(fmt->height, SRU_MAX_SIZE); + + if (fmt->width <= SRU_MAX_SIZE / 2 && + fmt->height <= SRU_MAX_SIZE / 2 && + output_area > input_area * 9 / 4) { + fmt->width = format->width * 2; + fmt->height = format->height * 2; + } else { + fmt->width = format->width; + fmt->height = format->height; + } + break; + } + + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; +} + +static int sru_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_sru *sru = to_sru(subdev); + struct v4l2_mbus_framefmt *format; + + sru_try_format(sru, fh, fmt->pad, &fmt->format, fmt->which); + + format = vsp1_entity_get_pad_format(&sru->entity, fh, fmt->pad, + fmt->which); + *format = fmt->format; + + if (fmt->pad == SRU_PAD_SINK) { + /* Propagate the format to the source pad. */ + format = vsp1_entity_get_pad_format(&sru->entity, fh, + SRU_PAD_SOURCE, fmt->which); + *format = fmt->format; + + sru_try_format(sru, fh, SRU_PAD_SOURCE, format, fmt->which); + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static struct v4l2_subdev_video_ops sru_video_ops = { + .s_stream = sru_s_stream, +}; + +static struct v4l2_subdev_pad_ops sru_pad_ops = { + .enum_mbus_code = sru_enum_mbus_code, + .enum_frame_size = sru_enum_frame_size, + .get_fmt = sru_get_format, + .set_fmt = sru_set_format, +}; + +static struct v4l2_subdev_ops sru_ops = { + .video = &sru_video_ops, + .pad = &sru_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_sru *vsp1_sru_create(struct vsp1_device *vsp1) +{ + struct v4l2_subdev *subdev; + struct vsp1_sru *sru; + int ret; + + sru = devm_kzalloc(vsp1->dev, sizeof(*sru), GFP_KERNEL); + if (sru == NULL) + return ERR_PTR(-ENOMEM); + + sru->entity.type = VSP1_ENTITY_SRU; + sru->entity.id = VI6_DPR_NODE_SRU; + + ret = vsp1_entity_init(vsp1, &sru->entity, 2); + if (ret < 0) + return ERR_PTR(ret); + + /* Initialize the V4L2 subdev. */ + subdev = &sru->entity.subdev; + v4l2_subdev_init(subdev, &sru_ops); + + subdev->entity.ops = &vsp1_media_ops; + subdev->internal_ops = &vsp1_subdev_internal_ops; + snprintf(subdev->name, sizeof(subdev->name), "%s sru", + dev_name(vsp1->dev)); + v4l2_set_subdevdata(subdev, sru); + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + vsp1_entity_init_formats(subdev, NULL); + + /* Initialize the control handler. */ + v4l2_ctrl_handler_init(&sru->ctrls, 1); + v4l2_ctrl_new_custom(&sru->ctrls, &sru_intensity_control, NULL); + v4l2_ctrl_handler_setup(&sru->ctrls); + sru->entity.subdev.ctrl_handler = &sru->ctrls; + + return sru; +} diff --git a/drivers/media/platform/vsp1/vsp1_sru.h b/drivers/media/platform/vsp1/vsp1_sru.h new file mode 100644 index 000000000000..381870b74780 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_sru.h @@ -0,0 +1,41 @@ +/* + * vsp1_sru.h -- R-Car VSP1 Super Resolution Unit + * + * 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_SRU_H__ +#define __VSP1_SRU_H__ + +#include <media/media-entity.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-subdev.h> + +#include "vsp1_entity.h" + +struct vsp1_device; + +#define SRU_PAD_SINK 0 +#define SRU_PAD_SOURCE 1 + +struct vsp1_sru { + struct vsp1_entity entity; + + struct v4l2_ctrl_handler ctrls; + unsigned int intensity; +}; + +static inline struct vsp1_sru *to_sru(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_sru, entity.subdev); +} + +struct vsp1_sru *vsp1_sru_create(struct vsp1_device *vsp1); + +#endif /* __VSP1_SRU_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_video.c b/drivers/media/platform/vsp1/vsp1_video.c index 4b0ac07af662..b4687a834f85 100644 --- a/drivers/media/platform/vsp1/vsp1_video.c +++ b/drivers/media/platform/vsp1/vsp1_video.c @@ -488,11 +488,17 @@ static bool vsp1_pipeline_ready(struct vsp1_pipeline *pipe) * This function completes the current buffer by filling its sequence number, * time stamp and payload size, and hands it back to the videobuf core. * + * When operating in DU output mode (deep pipeline to the DU through the LIF), + * the VSP1 needs to constantly supply frames to the display. In that case, if + * no other buffer is queued, reuse the one that has just been processed instead + * of handing 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_pipeline *pipe = to_vsp1_pipeline(&video->video.entity); struct vsp1_video_buffer *next = NULL; struct vsp1_video_buffer *done; unsigned long flags; @@ -507,6 +513,13 @@ vsp1_video_complete_buffer(struct vsp1_video *video) done = list_first_entry(&video->irqqueue, struct vsp1_video_buffer, queue); + + /* In DU output mode reuse the buffer if the list is singular. */ + if (pipe->lif && list_is_singular(&video->irqqueue)) { + spin_unlock_irqrestore(&video->irqlock, flags); + return done; + } + list_del(&done->queue); if (!list_empty(&video->irqqueue)) diff --git a/drivers/media/platform/vsp1/vsp1_wpf.c b/drivers/media/platform/vsp1/vsp1_wpf.c index db4b85ee05fc..7baed81ff005 100644 --- a/drivers/media/platform/vsp1/vsp1_wpf.c +++ b/drivers/media/platform/vsp1/vsp1_wpf.c @@ -48,8 +48,7 @@ static int wpf_s_stream(struct v4l2_subdev *subdev, int enable) 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]; + const struct v4l2_rect *crop = &wpf->crop; unsigned int i; u32 srcrpf = 0; u32 outfmt = 0; @@ -68,7 +67,7 @@ static int wpf_s_stream(struct v4l2_subdev *subdev, int enable) vsp1_wpf_write(wpf, VI6_WPF_SRCRPF, srcrpf); - /* Destination stride. Cropping isn't supported yet. */ + /* Destination stride. */ if (!pipe->lif) { struct v4l2_pix_format_mplane *format = &wpf->video.format; @@ -79,10 +78,12 @@ static int wpf_s_stream(struct v4l2_subdev *subdev, int enable) 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); + vsp1_wpf_write(wpf, VI6_WPF_HSZCLIP, VI6_WPF_SZCLIP_EN | + (crop->left << VI6_WPF_SZCLIP_OFST_SHIFT) | + (crop->width << VI6_WPF_SZCLIP_SIZE_SHIFT)); + vsp1_wpf_write(wpf, VI6_WPF_VSZCLIP, VI6_WPF_SZCLIP_EN | + (crop->top << VI6_WPF_SZCLIP_OFST_SHIFT) | + (crop->height << VI6_WPF_SZCLIP_SIZE_SHIFT)); /* Format */ if (!pipe->lif) { @@ -130,6 +131,8 @@ static struct v4l2_subdev_pad_ops wpf_pad_ops = { .enum_frame_size = vsp1_rwpf_enum_frame_size, .get_fmt = vsp1_rwpf_get_format, .set_fmt = vsp1_rwpf_set_format, + .get_selection = vsp1_rwpf_get_selection, + .set_selection = vsp1_rwpf_set_selection, }; static struct v4l2_subdev_ops wpf_ops = { diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index 6ecdc39bb366..192f36f2f4aa 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -21,6 +21,12 @@ config RADIO_SI470X source "drivers/media/radio/si470x/Kconfig" +config RADIO_SI4713 + tristate "Silicon Labs Si4713 FM Radio with RDS Transmitter support" + depends on VIDEO_V4L2 + +source "drivers/media/radio/si4713/Kconfig" + config RADIO_SI476X tristate "Silicon Laboratories Si476x I2C FM Radio" depends on I2C && VIDEO_V4L2 @@ -113,29 +119,6 @@ config RADIO_SHARK2 To compile this driver as a module, choose M here: the module will be called radio-shark2. -config I2C_SI4713 - tristate "I2C driver for Silicon Labs Si4713 device" - depends on I2C && VIDEO_V4L2 - ---help--- - Say Y here if you want support to Si4713 I2C device. - This device driver supports only i2c bus. - - To compile this driver as a module, choose M here: the - module will be called si4713. - -config RADIO_SI4713 - tristate "Silicon Labs Si4713 FM Radio Transmitter support" - depends on I2C && VIDEO_V4L2 - select I2C_SI4713 - ---help--- - Say Y here if you want support to Si4713 FM Radio Transmitter. - This device can transmit audio through FM. It can transmit - RDS and RBDS signals as well. This module is the v4l2 radio - interface for the i2c driver of this device. - - To compile this driver as a module, choose M here: the - module will be called radio-si4713. - config USB_KEENE tristate "Keene FM Transmitter USB support" depends on USB && VIDEO_V4L2 @@ -146,6 +129,20 @@ config USB_KEENE To compile this driver as a module, choose M here: the module will be called radio-keene. +config USB_RAREMONO + tristate "Thanko's Raremono AM/FM/SW radio support" + depends on USB && VIDEO_V4L2 + ---help--- + The 'Thanko's Raremono' device contains the Si4734 chip from Silicon Labs Inc. + It is one of the very few or perhaps the only consumer USB radio device + to receive the AM/FM/SW bands. + + Say Y here if you want to connect this type of AM/FM/SW receiver + to your computer's USB port. + + To compile this driver as a module, choose M here: the + module will be called radio-raremono. + config USB_MA901 tristate "Masterkit MA901 USB FM radio support" depends on USB && VIDEO_V4L2 diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index 3b645601800d..120e791199b2 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile @@ -17,12 +17,11 @@ obj-$(CONFIG_RADIO_RTRACK) += radio-aimslab.o obj-$(CONFIG_RADIO_ZOLTRIX) += radio-zoltrix.o obj-$(CONFIG_RADIO_GEMTEK) += radio-gemtek.o obj-$(CONFIG_RADIO_TRUST) += radio-trust.o -obj-$(CONFIG_I2C_SI4713) += si4713-i2c.o -obj-$(CONFIG_RADIO_SI4713) += radio-si4713.o obj-$(CONFIG_RADIO_SI476X) += radio-si476x.o obj-$(CONFIG_RADIO_MIROPCM20) += radio-miropcm20.o obj-$(CONFIG_USB_DSBR) += dsbr100.o obj-$(CONFIG_RADIO_SI470X) += si470x/ +obj-$(CONFIG_RADIO_SI4713) += si4713/ obj-$(CONFIG_USB_MR800) += radio-mr800.o obj-$(CONFIG_USB_KEENE) += radio-keene.o obj-$(CONFIG_USB_MA901) += radio-ma901.o @@ -33,6 +32,7 @@ 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 +obj-$(CONFIG_USB_RAREMONO) += radio-raremono.o shark2-objs := radio-shark2.o radio-tea5777.o diff --git a/drivers/media/radio/radio-raremono.c b/drivers/media/radio/radio-raremono.c new file mode 100644 index 000000000000..7b3bdbb1be73 --- /dev/null +++ b/drivers/media/radio/radio-raremono.c @@ -0,0 +1,387 @@ +/* + * 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/init.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/usb.h> +#include <linux/hid.h> +#include <linux/mutex.h> +#include <linux/videodev2.h> +#include <asm/unaligned.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> + +/* + * 'Thanko's Raremono' is a Japanese si4734-based AM/FM/SW USB receiver: + * + * http://www.raremono.jp/product/484.html/ + * + * The USB protocol has been reversed engineered using wireshark, initially + * by Dinesh Ram <dinesh.ram@cern.ch> and finished by Hans Verkuil + * <hverkuil@xs4all.nl>. + * + * Sadly the firmware used in this product hides lots of goodies since the + * si4734 has more features than are supported by the firmware. Oh well... + */ + +/* driver and module definitions */ +MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>"); +MODULE_DESCRIPTION("Thanko's Raremono AM/FM/SW Receiver USB driver"); +MODULE_LICENSE("GPL v2"); + +/* + * The Device announces itself as Cygnal Integrated Products, Inc. + * + * The vendor and product IDs (and in fact all other lsusb information as + * well) are identical to the si470x Silicon Labs USB FM Radio Reference + * Design board, even though this card has a si4734 device. Clearly the + * designer of this product never bothered to change the USB IDs. + */ + +/* USB Device ID List */ +static struct usb_device_id usb_raremono_device_table[] = { + {USB_DEVICE_AND_INTERFACE_INFO(0x10c4, 0x818a, USB_CLASS_HID, 0, 0) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, usb_raremono_device_table); + +#define BUFFER_LENGTH 64 + +/* Timeout is set to a high value, could probably be reduced. Need more tests */ +#define USB_TIMEOUT 10000 + +/* Frequency limits in KHz */ +#define FM_FREQ_RANGE_LOW 64000 +#define FM_FREQ_RANGE_HIGH 108000 + +#define AM_FREQ_RANGE_LOW 520 +#define AM_FREQ_RANGE_HIGH 1710 + +#define SW_FREQ_RANGE_LOW 2300 +#define SW_FREQ_RANGE_HIGH 26100 + +enum { BAND_FM, BAND_AM, BAND_SW }; + +static const struct v4l2_frequency_band bands[] = { + /* Band FM */ + { + .type = V4L2_TUNER_RADIO, + .index = 0, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = FM_FREQ_RANGE_LOW * 16, + .rangehigh = FM_FREQ_RANGE_HIGH * 16, + .modulation = V4L2_BAND_MODULATION_FM, + }, + /* Band AM */ + { + .type = V4L2_TUNER_RADIO, + .index = 1, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = AM_FREQ_RANGE_LOW * 16, + .rangehigh = AM_FREQ_RANGE_HIGH * 16, + .modulation = V4L2_BAND_MODULATION_AM, + }, + /* Band SW */ + { + .type = V4L2_TUNER_RADIO, + .index = 2, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = SW_FREQ_RANGE_LOW * 16, + .rangehigh = SW_FREQ_RANGE_HIGH * 16, + .modulation = V4L2_BAND_MODULATION_AM, + }, +}; + +struct raremono_device { + struct usb_device *usbdev; + struct usb_interface *intf; + struct video_device vdev; + struct v4l2_device v4l2_dev; + struct mutex lock; + + u8 *buffer; + u32 band; + unsigned curfreq; +}; + +static inline struct raremono_device *to_raremono_dev(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct raremono_device, v4l2_dev); +} + +/* Set frequency. */ +static int raremono_cmd_main(struct raremono_device *radio, unsigned band, unsigned freq) +{ + unsigned band_offset; + int ret; + + switch (band) { + case BAND_FM: + band_offset = 1; + freq /= 10; + break; + case BAND_AM: + band_offset = 0; + break; + default: + band_offset = 2; + break; + } + radio->buffer[0] = 0x04 + band_offset; + radio->buffer[1] = freq >> 8; + radio->buffer[2] = freq & 0xff; + + ret = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), + HID_REQ_SET_REPORT, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + 0x0300 + radio->buffer[0], 2, + radio->buffer, 3, USB_TIMEOUT); + + if (ret < 0) { + dev_warn(radio->v4l2_dev.dev, "%s failed (%d)\n", __func__, ret); + return ret; + } + radio->curfreq = (band == BAND_FM) ? freq * 10 : freq; + return 0; +} + +/* Handle unplugging the device. + * We call video_unregister_device in any case. + * The last function called in this procedure is + * usb_raremono_device_release. + */ +static void usb_raremono_disconnect(struct usb_interface *intf) +{ + struct raremono_device *radio = to_raremono_dev(usb_get_intfdata(intf)); + + dev_info(&intf->dev, "Thanko's Raremono disconnected\n"); + + mutex_lock(&radio->lock); + usb_set_intfdata(intf, NULL); + video_unregister_device(&radio->vdev); + v4l2_device_disconnect(&radio->v4l2_dev); + mutex_unlock(&radio->lock); + v4l2_device_put(&radio->v4l2_dev); +} + +/* + * Linux Video interface + */ +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *v) +{ + struct raremono_device *radio = video_drvdata(file); + + strlcpy(v->driver, "radio-raremono", sizeof(v->driver)); + strlcpy(v->card, "Thanko's Raremono", sizeof(v->card)); + usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info)); + v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + 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) +{ + if (band->tuner != 0) + return -EINVAL; + + if (band->index >= ARRAY_SIZE(bands)) + return -EINVAL; + + *band = bands[band->index]; + + return 0; +} + +static int vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *v) +{ + struct raremono_device *radio = video_drvdata(file); + int ret; + + if (v->index > 0) + return -EINVAL; + + strlcpy(v->name, "AM/FM/SW", sizeof(v->name)); + v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_FREQ_BANDS; + v->rangelow = AM_FREQ_RANGE_LOW * 16; + v->rangehigh = FM_FREQ_RANGE_HIGH * 16; + v->rxsubchans = V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_MONO; + v->audmode = (radio->curfreq < FM_FREQ_RANGE_LOW) ? + V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO; + memset(radio->buffer, 1, BUFFER_LENGTH); + ret = usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), + 1, 0xa1, 0x030d, 2, radio->buffer, BUFFER_LENGTH, USB_TIMEOUT); + + if (ret < 0) { + dev_warn(radio->v4l2_dev.dev, "%s failed (%d)\n", __func__, ret); + return ret; + } + v->signal = ((radio->buffer[1] & 0xf) << 8 | radio->buffer[2]) << 4; + return 0; +} + +static int vidioc_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *v) +{ + return v->index ? -EINVAL : 0; +} + +static int vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct raremono_device *radio = video_drvdata(file); + u32 freq = f->frequency; + unsigned band; + + if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + if (f->frequency >= (FM_FREQ_RANGE_LOW + SW_FREQ_RANGE_HIGH) * 8) + band = BAND_FM; + else if (f->frequency <= (AM_FREQ_RANGE_HIGH + SW_FREQ_RANGE_LOW) * 8) + band = BAND_AM; + else + band = BAND_SW; + + freq = clamp_t(u32, f->frequency, bands[band].rangelow, bands[band].rangehigh); + return raremono_cmd_main(radio, band, freq / 16); +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct raremono_device *radio = video_drvdata(file); + + if (f->tuner != 0) + return -EINVAL; + f->type = V4L2_TUNER_RADIO; + f->frequency = radio->curfreq * 16; + return 0; +} + +/* File system interface */ +static const struct v4l2_file_operations usb_raremono_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops usb_raremono_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_enum_freq_bands = vidioc_enum_freq_bands, +}; + +/* check if the device is present and register with v4l and usb if it is */ +static int usb_raremono_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct raremono_device *radio; + int retval = 0; + + radio = devm_kzalloc(&intf->dev, sizeof(struct raremono_device), GFP_KERNEL); + if (radio) + radio->buffer = devm_kmalloc(&intf->dev, BUFFER_LENGTH, GFP_KERNEL); + + if (!radio || !radio->buffer) + return -ENOMEM; + + radio->usbdev = interface_to_usbdev(intf); + radio->intf = intf; + + /* + * This device uses the same USB IDs as the si470x SiLabs reference + * design. So do an additional check: attempt to read the device ID + * from the si470x: the lower 12 bits are 0x0242 for the si470x. The + * Raremono always returns 0x0800 (the meaning of that is unknown, but + * at least it works). + * + * We use this check to determine which device we are dealing with. + */ + msleep(20); + retval = usb_control_msg(radio->usbdev, + usb_rcvctrlpipe(radio->usbdev, 0), + HID_REQ_GET_REPORT, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + 1, 2, + radio->buffer, 3, 500); + if (retval != 3 || + (get_unaligned_be16(&radio->buffer[1]) & 0xfff) == 0x0242) { + dev_info(&intf->dev, "this is not Thanko's Raremono.\n"); + return -ENODEV; + } + + dev_info(&intf->dev, "Thanko's Raremono connected: (%04X:%04X)\n", + id->idVendor, id->idProduct); + + retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev); + if (retval < 0) { + dev_err(&intf->dev, "couldn't register v4l2_device\n"); + return retval; + } + + mutex_init(&radio->lock); + + strlcpy(radio->vdev.name, radio->v4l2_dev.name, + sizeof(radio->vdev.name)); + radio->vdev.v4l2_dev = &radio->v4l2_dev; + radio->vdev.fops = &usb_raremono_fops; + radio->vdev.ioctl_ops = &usb_raremono_ioctl_ops; + radio->vdev.lock = &radio->lock; + radio->vdev.release = video_device_release_empty; + + usb_set_intfdata(intf, &radio->v4l2_dev); + + video_set_drvdata(&radio->vdev, radio); + set_bit(V4L2_FL_USE_FH_PRIO, &radio->vdev.flags); + + raremono_cmd_main(radio, BAND_FM, 95160); + + retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO, -1); + if (retval == 0) { + dev_info(&intf->dev, "V4L2 device registered as %s\n", + video_device_node_name(&radio->vdev)); + return 0; + } + dev_err(&intf->dev, "could not register video device\n"); + v4l2_device_unregister(&radio->v4l2_dev); + return retval; +} + +/* USB subsystem interface */ +static struct usb_driver usb_raremono_driver = { + .name = "radio-raremono", + .probe = usb_raremono_probe, + .disconnect = usb_raremono_disconnect, + .id_table = usb_raremono_device_table, +}; + +module_usb_driver(usb_raremono_driver); diff --git a/drivers/media/radio/si470x/radio-si470x-usb.c b/drivers/media/radio/si470x/radio-si470x-usb.c index d6d4d60261d5..07ef40595efd 100644 --- a/drivers/media/radio/si470x/radio-si470x-usb.c +++ b/drivers/media/radio/si470x/radio-si470x-usb.c @@ -137,6 +137,8 @@ MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*"); /* interrupt out endpoint 2 every 1 millisecond */ #define UNUSED_REPORT 23 +#define MAX_REPORT_SIZE 64 + /************************************************************************** @@ -208,7 +210,7 @@ MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*"); */ static int si470x_get_report(struct si470x_device *radio, void *buf, int size) { - unsigned char *report = (unsigned char *) buf; + unsigned char *report = buf; int retval; retval = usb_control_msg(radio->usbdev, @@ -231,7 +233,7 @@ static int si470x_get_report(struct si470x_device *radio, void *buf, int size) */ static int si470x_set_report(struct si470x_device *radio, void *buf, int size) { - unsigned char *report = (unsigned char *) buf; + unsigned char *report = buf; int retval; retval = usb_control_msg(radio->usbdev, @@ -254,15 +256,14 @@ static int si470x_set_report(struct si470x_device *radio, void *buf, int size) */ int si470x_get_register(struct si470x_device *radio, int regnr) { - unsigned char buf[REGISTER_REPORT_SIZE]; int retval; - buf[0] = REGISTER_REPORT(regnr); + radio->usb_buf[0] = REGISTER_REPORT(regnr); - retval = si470x_get_report(radio, (void *) &buf, sizeof(buf)); + retval = si470x_get_report(radio, radio->usb_buf, REGISTER_REPORT_SIZE); if (retval >= 0) - radio->registers[regnr] = get_unaligned_be16(&buf[1]); + radio->registers[regnr] = get_unaligned_be16(&radio->usb_buf[1]); return (retval < 0) ? -EINVAL : 0; } @@ -273,13 +274,12 @@ int si470x_get_register(struct si470x_device *radio, int regnr) */ int si470x_set_register(struct si470x_device *radio, int regnr) { - unsigned char buf[REGISTER_REPORT_SIZE]; int retval; - buf[0] = REGISTER_REPORT(regnr); - put_unaligned_be16(radio->registers[regnr], &buf[1]); + radio->usb_buf[0] = REGISTER_REPORT(regnr); + put_unaligned_be16(radio->registers[regnr], &radio->usb_buf[1]); - retval = si470x_set_report(radio, (void *) &buf, sizeof(buf)); + retval = si470x_set_report(radio, radio->usb_buf, REGISTER_REPORT_SIZE); return (retval < 0) ? -EINVAL : 0; } @@ -295,18 +295,17 @@ int si470x_set_register(struct si470x_device *radio, int regnr) */ static int si470x_get_all_registers(struct si470x_device *radio) { - unsigned char buf[ENTIRE_REPORT_SIZE]; int retval; unsigned char regnr; - buf[0] = ENTIRE_REPORT; + radio->usb_buf[0] = ENTIRE_REPORT; - retval = si470x_get_report(radio, (void *) &buf, sizeof(buf)); + retval = si470x_get_report(radio, radio->usb_buf, ENTIRE_REPORT_SIZE); if (retval >= 0) for (regnr = 0; regnr < RADIO_REGISTER_NUM; regnr++) radio->registers[regnr] = get_unaligned_be16( - &buf[regnr * RADIO_REGISTER_SIZE + 1]); + &radio->usb_buf[regnr * RADIO_REGISTER_SIZE + 1]); return (retval < 0) ? -EINVAL : 0; } @@ -323,14 +322,13 @@ static int si470x_get_all_registers(struct si470x_device *radio) static int si470x_set_led_state(struct si470x_device *radio, unsigned char led_state) { - unsigned char buf[LED_REPORT_SIZE]; int retval; - buf[0] = LED_REPORT; - buf[1] = LED_COMMAND; - buf[2] = led_state; + radio->usb_buf[0] = LED_REPORT; + radio->usb_buf[1] = LED_COMMAND; + radio->usb_buf[2] = led_state; - retval = si470x_set_report(radio, (void *) &buf, sizeof(buf)); + retval = si470x_set_report(radio, radio->usb_buf, LED_REPORT_SIZE); return (retval < 0) ? -EINVAL : 0; } @@ -346,19 +344,18 @@ static int si470x_set_led_state(struct si470x_device *radio, */ static int si470x_get_scratch_page_versions(struct si470x_device *radio) { - unsigned char buf[SCRATCH_REPORT_SIZE]; int retval; - buf[0] = SCRATCH_REPORT; + radio->usb_buf[0] = SCRATCH_REPORT; - retval = si470x_get_report(radio, (void *) &buf, sizeof(buf)); + retval = si470x_get_report(radio, radio->usb_buf, SCRATCH_REPORT_SIZE); if (retval < 0) dev_warn(&radio->intf->dev, "si470x_get_scratch: " "si470x_get_report returned %d\n", retval); else { - radio->software_version = buf[1]; - radio->hardware_version = buf[2]; + radio->software_version = radio->usb_buf[1]; + radio->hardware_version = radio->usb_buf[2]; } return (retval < 0) ? -EINVAL : 0; @@ -509,6 +506,7 @@ static void si470x_usb_release(struct v4l2_device *v4l2_dev) v4l2_device_unregister(&radio->v4l2_dev); kfree(radio->int_in_buffer); kfree(radio->buffer); + kfree(radio->usb_buf); kfree(radio); } @@ -593,6 +591,11 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, retval = -ENOMEM; goto err_initial; } + radio->usb_buf = kmalloc(MAX_REPORT_SIZE, GFP_KERNEL); + if (radio->usb_buf == NULL) { + retval = -ENOMEM; + goto err_radio; + } radio->usbdev = interface_to_usbdev(intf); radio->intf = intf; radio->band = 1; /* Default to 76 - 108 MHz */ @@ -612,7 +615,7 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, if (!radio->int_in_endpoint) { dev_info(&intf->dev, "could not find interrupt in endpoint\n"); retval = -EIO; - goto err_radio; + goto err_usbbuf; } int_end_size = le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize); @@ -621,7 +624,7 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, if (!radio->int_in_buffer) { dev_info(&intf->dev, "could not allocate int_in_buffer"); retval = -ENOMEM; - goto err_radio; + goto err_usbbuf; } radio->int_in_urb = usb_alloc_urb(0, GFP_KERNEL); @@ -632,6 +635,30 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, } radio->v4l2_dev.release = si470x_usb_release; + + /* + * The si470x SiLabs reference design uses the same USB IDs as + * 'Thanko's Raremono' si4734 based receiver. So check here which we + * have: attempt to read the device ID from the si470x: the lower 12 + * bits should be 0x0242 for the si470x. + * + * We use this check to determine which device we are dealing with. + */ + if (id->idVendor == 0x10c4 && id->idProduct == 0x818a) { + retval = usb_control_msg(radio->usbdev, + usb_rcvctrlpipe(radio->usbdev, 0), + HID_REQ_GET_REPORT, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + 1, 2, + radio->usb_buf, 3, 500); + if (retval != 3 || + (get_unaligned_be16(&radio->usb_buf[1]) & 0xfff) != 0x0242) { + dev_info(&intf->dev, "this is not a si470x device.\n"); + retval = -ENODEV; + goto err_urb; + } + } + retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev); if (retval < 0) { dev_err(&intf->dev, "couldn't register v4l2_device\n"); @@ -743,6 +770,8 @@ err_urb: usb_free_urb(radio->int_in_urb); err_intbuffer: kfree(radio->int_in_buffer); +err_usbbuf: + kfree(radio->usb_buf); err_radio: kfree(radio); err_initial: diff --git a/drivers/media/radio/si470x/radio-si470x.h b/drivers/media/radio/si470x/radio-si470x.h index 467e95575488..4b7660470e2f 100644 --- a/drivers/media/radio/si470x/radio-si470x.h +++ b/drivers/media/radio/si470x/radio-si470x.h @@ -167,6 +167,7 @@ struct si470x_device { /* reference to USB and video device */ struct usb_device *usbdev; struct usb_interface *intf; + char *usb_buf; /* Interrupt endpoint handling */ char *int_in_buffer; diff --git a/drivers/media/radio/si4713/Kconfig b/drivers/media/radio/si4713/Kconfig new file mode 100644 index 000000000000..a7c3ba85d12b --- /dev/null +++ b/drivers/media/radio/si4713/Kconfig @@ -0,0 +1,40 @@ +config USB_SI4713 + tristate "Silicon Labs Si4713 FM Radio Transmitter support with USB" + depends on USB && RADIO_SI4713 + select SI4713 + ---help--- + This is a driver for USB devices with the Silicon Labs SI4713 + chip. Currently these devices are known to work. + - 10c4:8244: Silicon Labs FM Transmitter USB device. + + Say Y here if you want to connect this type of radio to your + computer's USB port. + + To compile this driver as a module, choose M here: the + module will be called radio-usb-si4713. + +config PLATFORM_SI4713 + tristate "Silicon Labs Si4713 FM Radio Transmitter support with I2C" + depends on I2C && RADIO_SI4713 + select SI4713 + ---help--- + This is a driver for I2C devices with the Silicon Labs SI4713 + chip. + + Say Y here if you want to connect this type of radio to your + computer's I2C port. + + To compile this driver as a module, choose M here: the + module will be called radio-platform-si4713. + +config I2C_SI4713 + tristate "Silicon Labs Si4713 FM Radio Transmitter support" + depends on I2C && RADIO_SI4713 + ---help--- + Say Y here if you want support to Si4713 FM Radio Transmitter. + This device can transmit audio through FM. It can transmit + RDS and RBDS signals as well. This module is the v4l2 radio + interface for the i2c driver of this device. + + To compile this driver as a module, choose M here: the + module will be called si4713. diff --git a/drivers/media/radio/si4713/Makefile b/drivers/media/radio/si4713/Makefile new file mode 100644 index 000000000000..ddaaf925e883 --- /dev/null +++ b/drivers/media/radio/si4713/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for radios with Silicon Labs Si4713 FM Radio Transmitters +# + +obj-$(CONFIG_I2C_SI4713) += si4713.o +obj-$(CONFIG_USB_SI4713) += radio-usb-si4713.o +obj-$(CONFIG_PLATFORM_SI4713) += radio-platform-si4713.o diff --git a/drivers/media/radio/radio-si4713.c b/drivers/media/radio/si4713/radio-platform-si4713.c index ba4cfc946868..ba4cfc946868 100644 --- a/drivers/media/radio/radio-si4713.c +++ b/drivers/media/radio/si4713/radio-platform-si4713.c diff --git a/drivers/media/radio/si4713/radio-usb-si4713.c b/drivers/media/radio/si4713/radio-usb-si4713.c new file mode 100644 index 000000000000..779855b74bcd --- /dev/null +++ b/drivers/media/radio/si4713/radio-usb-si4713.c @@ -0,0 +1,540 @@ +/* + * 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. + */ + +/* kernel includes */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/mutex.h> +#include <linux/i2c.h> +/* V4l includes */ +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-event.h> +#include <media/si4713.h> + +#include "si4713.h" + +/* driver and module definitions */ +MODULE_AUTHOR("Dinesh Ram <dinesh.ram@cern.ch>"); +MODULE_DESCRIPTION("Si4713 FM Transmitter USB driver"); +MODULE_LICENSE("GPL v2"); + +/* The Device announces itself as Cygnal Integrated Products, Inc. */ +#define USB_SI4713_VENDOR 0x10c4 +#define USB_SI4713_PRODUCT 0x8244 + +#define BUFFER_LENGTH 64 +#define USB_TIMEOUT 1000 +#define USB_RESP_TIMEOUT 50000 + +/* USB Device ID List */ +static struct usb_device_id usb_si4713_usb_device_table[] = { + {USB_DEVICE_AND_INTERFACE_INFO(USB_SI4713_VENDOR, USB_SI4713_PRODUCT, + USB_CLASS_HID, 0, 0) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, usb_si4713_usb_device_table); + +struct si4713_usb_device { + struct usb_device *usbdev; + struct usb_interface *intf; + struct video_device vdev; + struct v4l2_device v4l2_dev; + struct v4l2_subdev *v4l2_subdev; + struct mutex lock; + struct i2c_adapter i2c_adapter; + + u8 *buffer; +}; + +static inline struct si4713_usb_device *to_si4713_dev(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct si4713_usb_device, v4l2_dev); +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *v) +{ + struct si4713_usb_device *radio = video_drvdata(file); + + strlcpy(v->driver, "radio-usb-si4713", sizeof(v->driver)); + strlcpy(v->card, "Si4713 FM Transmitter", sizeof(v->card)); + usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info)); + v->device_caps = V4L2_CAP_MODULATOR | V4L2_CAP_RDS_OUTPUT; + v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +static int vidioc_g_modulator(struct file *file, void *priv, + struct v4l2_modulator *vm) +{ + struct si4713_usb_device *radio = video_drvdata(file); + + return v4l2_subdev_call(radio->v4l2_subdev, tuner, g_modulator, vm); +} + +static int vidioc_s_modulator(struct file *file, void *priv, + const struct v4l2_modulator *vm) +{ + struct si4713_usb_device *radio = video_drvdata(file); + + return v4l2_subdev_call(radio->v4l2_subdev, tuner, s_modulator, vm); +} + +static int vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *vf) +{ + struct si4713_usb_device *radio = video_drvdata(file); + + return v4l2_subdev_call(radio->v4l2_subdev, tuner, s_frequency, vf); +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *vf) +{ + struct si4713_usb_device *radio = video_drvdata(file); + + return v4l2_subdev_call(radio->v4l2_subdev, tuner, g_frequency, vf); +} + +static const struct v4l2_ioctl_ops usb_si4713_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_g_modulator = vidioc_g_modulator, + .vidioc_s_modulator = vidioc_s_modulator, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* File system interface */ +static const struct v4l2_file_operations usb_si4713_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .poll = v4l2_ctrl_poll, + .unlocked_ioctl = video_ioctl2, +}; + +static void usb_si4713_video_device_release(struct v4l2_device *v4l2_dev) +{ + struct si4713_usb_device *radio = to_si4713_dev(v4l2_dev); + struct i2c_adapter *adapter = &radio->i2c_adapter; + + i2c_del_adapter(adapter); + v4l2_device_unregister(&radio->v4l2_dev); + kfree(radio->buffer); + kfree(radio); +} + +/* + * This command sequence emulates the behaviour of the Windows driver. + * The structure of these commands was determined by sniffing the + * usb traffic of the device during startup. + * Most likely, these commands make some queries to the device. + * Commands are sent to enquire parameters like the bus mode, + * component revision, boot mode, the device serial number etc. + * + * These commands are necessary to be sent in this order during startup. + * The device fails to powerup if these commands are not sent. + * + * The complete list of startup commands is given in the start_seq table below. + */ +static int si4713_send_startup_command(struct si4713_usb_device *radio) +{ + unsigned long until_jiffies = jiffies + usecs_to_jiffies(USB_RESP_TIMEOUT) + 1; + u8 *buffer = radio->buffer; + int retval; + + /* send the command */ + retval = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), + 0x09, 0x21, 0x033f, 0, radio->buffer, + BUFFER_LENGTH, USB_TIMEOUT); + if (retval < 0) + return retval; + + for (;;) { + /* receive the response */ + retval = usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), + 0x01, 0xa1, 0x033f, 0, radio->buffer, + BUFFER_LENGTH, USB_TIMEOUT); + if (retval < 0) + return retval; + if (!radio->buffer[1]) { + /* USB traffic sniffing showed that some commands require + * additional checks. */ + switch (buffer[1]) { + case 0x32: + if (radio->buffer[2] == 0) + return 0; + break; + case 0x14: + case 0x12: + if (radio->buffer[2] & SI4713_CTS) + return 0; + break; + case 0x06: + if ((radio->buffer[2] & SI4713_CTS) && radio->buffer[9] == 0x08) + return 0; + break; + default: + return 0; + } + } + if (time_is_before_jiffies(until_jiffies)) + return -EIO; + msleep(3); + } + + return retval; +} + +struct si4713_start_seq_table { + int len; + u8 payload[8]; +}; + +/* + * Some of the startup commands that could be recognized are : + * (0x03): Get serial number of the board (Response : CB000-00-00) + * (0x06, 0x03, 0x03, 0x08, 0x01, 0x0f) : Get Component revision + */ +static struct si4713_start_seq_table start_seq[] = { + + { 1, { 0x03 } }, + { 2, { 0x32, 0x7f } }, + { 6, { 0x06, 0x03, 0x03, 0x08, 0x01, 0x0f } }, + { 2, { 0x14, 0x02 } }, + { 2, { 0x09, 0x90 } }, + { 3, { 0x08, 0x90, 0xfa } }, + { 2, { 0x36, 0x01 } }, + { 2, { 0x05, 0x03 } }, + { 7, { 0x06, 0x00, 0x06, 0x0e, 0x01, 0x0f, 0x05 } }, + { 1, { 0x12 } }, + /* Commands that are sent after pressing the 'Initialize' + button in the windows application */ + { 1, { 0x03 } }, + { 1, { 0x01 } }, + { 2, { 0x09, 0x90 } }, + { 3, { 0x08, 0x90, 0xfa } }, + { 1, { 0x34 } }, + { 2, { 0x35, 0x01 } }, + { 2, { 0x36, 0x01 } }, + { 2, { 0x30, 0x09 } }, + { 4, { 0x30, 0x06, 0x00, 0xe2 } }, + { 3, { 0x31, 0x01, 0x30 } }, + { 3, { 0x31, 0x04, 0x09 } }, + { 2, { 0x05, 0x02 } }, + { 6, { 0x06, 0x03, 0x03, 0x08, 0x01, 0x0f } }, +}; + +static int si4713_start_seq(struct si4713_usb_device *radio) +{ + int retval = 0; + int i; + + radio->buffer[0] = 0x3f; + + for (i = 0; i < ARRAY_SIZE(start_seq); i++) { + int len = start_seq[i].len; + u8 *payload = start_seq[i].payload; + + memcpy(radio->buffer + 1, payload, len); + memset(radio->buffer + len + 1, 0, BUFFER_LENGTH - 1 - len); + retval = si4713_send_startup_command(radio); + } + + return retval; +} + +static struct i2c_board_info si4713_board_info = { + I2C_BOARD_INFO("si4713", SI4713_I2C_ADDR_BUSEN_HIGH), +}; + +struct si4713_command_table { + int command_id; + u8 payload[8]; +}; + +/* + * Structure of a command : + * Byte 1 : 0x3f (always) + * Byte 2 : 0x06 (send a command) + * Byte 3 : Unknown + * Byte 4 : Number of arguments + 1 (for the command byte) + * Byte 5 : Number of response bytes + */ +static struct si4713_command_table command_table[] = { + + { SI4713_CMD_POWER_UP, { 0x00, SI4713_PWUP_NARGS + 1, SI4713_PWUP_NRESP} }, + { SI4713_CMD_GET_REV, { 0x03, 0x01, SI4713_GETREV_NRESP } }, + { SI4713_CMD_POWER_DOWN, { 0x00, 0x01, SI4713_PWDN_NRESP} }, + { SI4713_CMD_SET_PROPERTY, { 0x00, SI4713_SET_PROP_NARGS + 1, SI4713_SET_PROP_NRESP } }, + { SI4713_CMD_GET_PROPERTY, { 0x00, SI4713_GET_PROP_NARGS + 1, SI4713_GET_PROP_NRESP } }, + { SI4713_CMD_TX_TUNE_FREQ, { 0x03, SI4713_TXFREQ_NARGS + 1, SI4713_TXFREQ_NRESP } }, + { SI4713_CMD_TX_TUNE_POWER, { 0x03, SI4713_TXPWR_NARGS + 1, SI4713_TXPWR_NRESP } }, + { SI4713_CMD_TX_TUNE_MEASURE, { 0x03, SI4713_TXMEA_NARGS + 1, SI4713_TXMEA_NRESP } }, + { SI4713_CMD_TX_TUNE_STATUS, { 0x00, SI4713_TXSTATUS_NARGS + 1, SI4713_TXSTATUS_NRESP } }, + { SI4713_CMD_TX_ASQ_STATUS, { 0x03, SI4713_ASQSTATUS_NARGS + 1, SI4713_ASQSTATUS_NRESP } }, + { SI4713_CMD_GET_INT_STATUS, { 0x03, 0x01, SI4713_GET_STATUS_NRESP } }, + { SI4713_CMD_TX_RDS_BUFF, { 0x03, SI4713_RDSBUFF_NARGS + 1, SI4713_RDSBUFF_NRESP } }, + { SI4713_CMD_TX_RDS_PS, { 0x00, SI4713_RDSPS_NARGS + 1, SI4713_RDSPS_NRESP } }, +}; + +static int send_command(struct si4713_usb_device *radio, u8 *payload, char *data, int len) +{ + int retval; + + radio->buffer[0] = 0x3f; + radio->buffer[1] = 0x06; + + memcpy(radio->buffer + 2, payload, 3); + memcpy(radio->buffer + 5, data, len); + memset(radio->buffer + 5 + len, 0, BUFFER_LENGTH - 5 - len); + + /* send the command */ + retval = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), + 0x09, 0x21, 0x033f, 0, radio->buffer, + BUFFER_LENGTH, USB_TIMEOUT); + + return retval < 0 ? retval : 0; +} + +static int si4713_i2c_read(struct si4713_usb_device *radio, char *data, int len) +{ + unsigned long until_jiffies = jiffies + usecs_to_jiffies(USB_RESP_TIMEOUT) + 1; + int retval; + + /* receive the response */ + for (;;) { + retval = usb_control_msg(radio->usbdev, + usb_rcvctrlpipe(radio->usbdev, 0), + 0x01, 0xa1, 0x033f, 0, radio->buffer, + BUFFER_LENGTH, USB_TIMEOUT); + if (retval < 0) + return retval; + + /* + * Check that we get a valid reply back (buffer[1] == 0) and + * that CTS is set before returning, otherwise we wait and try + * again. The i2c driver also does the CTS check, but the timeouts + * used there are much too small for this USB driver, so we wait + * for it here. + */ + if (radio->buffer[1] == 0 && (radio->buffer[2] & SI4713_CTS)) { + memcpy(data, radio->buffer + 2, len); + return 0; + } + if (time_is_before_jiffies(until_jiffies)) { + /* Zero the status value, ensuring CTS isn't set */ + data[0] = 0; + return 0; + } + msleep(3); + } +} + +static int si4713_i2c_write(struct si4713_usb_device *radio, char *data, int len) +{ + int retval = -EINVAL; + int i; + + if (len > BUFFER_LENGTH - 5) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(command_table); i++) { + if (data[0] == command_table[i].command_id) + retval = send_command(radio, command_table[i].payload, + data, len); + } + + return retval < 0 ? retval : 0; +} + +static int si4713_transfer(struct i2c_adapter *i2c_adapter, + struct i2c_msg *msgs, int num) +{ + struct si4713_usb_device *radio = i2c_get_adapdata(i2c_adapter); + int retval = -EINVAL; + int i; + + if (num <= 0) + return 0; + + for (i = 0; i < num; i++) { + if (msgs[i].flags & I2C_M_RD) + retval = si4713_i2c_read(radio, msgs[i].buf, msgs[i].len); + else + retval = si4713_i2c_write(radio, msgs[i].buf, msgs[i].len); + if (retval) + break; + } + + return retval ? retval : num; +} + +static u32 si4713_functionality(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static struct i2c_algorithm si4713_algo = { + .master_xfer = si4713_transfer, + .functionality = si4713_functionality, +}; + +/* This name value shows up in the sysfs filename associated + with this I2C adapter */ +static struct i2c_adapter si4713_i2c_adapter_template = { + .name = "si4713-i2c", + .owner = THIS_MODULE, + .algo = &si4713_algo, +}; + +static int si4713_register_i2c_adapter(struct si4713_usb_device *radio) +{ + radio->i2c_adapter = si4713_i2c_adapter_template; + /* set up sysfs linkage to our parent device */ + radio->i2c_adapter.dev.parent = &radio->usbdev->dev; + i2c_set_adapdata(&radio->i2c_adapter, radio); + + return i2c_add_adapter(&radio->i2c_adapter); +} + +/* check if the device is present and register with v4l and usb if it is */ +static int usb_si4713_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct si4713_usb_device *radio; + struct i2c_adapter *adapter; + struct v4l2_subdev *sd; + int retval = -ENOMEM; + + dev_info(&intf->dev, "Si4713 development board discovered: (%04X:%04X)\n", + id->idVendor, id->idProduct); + + /* Initialize local device structure */ + radio = kzalloc(sizeof(struct si4713_usb_device), GFP_KERNEL); + if (radio) + radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL); + + if (!radio || !radio->buffer) { + dev_err(&intf->dev, "kmalloc for si4713_usb_device failed\n"); + kfree(radio); + return -ENOMEM; + } + + mutex_init(&radio->lock); + + radio->usbdev = interface_to_usbdev(intf); + radio->intf = intf; + usb_set_intfdata(intf, &radio->v4l2_dev); + + retval = si4713_start_seq(radio); + if (retval < 0) + goto err_v4l2; + + retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev); + if (retval < 0) { + dev_err(&intf->dev, "couldn't register v4l2_device\n"); + goto err_v4l2; + } + + retval = si4713_register_i2c_adapter(radio); + if (retval < 0) { + dev_err(&intf->dev, "could not register i2c device\n"); + goto err_i2cdev; + } + + adapter = &radio->i2c_adapter; + sd = v4l2_i2c_new_subdev_board(&radio->v4l2_dev, adapter, + &si4713_board_info, NULL); + radio->v4l2_subdev = sd; + if (!sd) { + dev_err(&intf->dev, "cannot get v4l2 subdevice\n"); + retval = -ENODEV; + goto del_adapter; + } + + radio->vdev.ctrl_handler = sd->ctrl_handler; + radio->v4l2_dev.release = usb_si4713_video_device_release; + strlcpy(radio->vdev.name, radio->v4l2_dev.name, + sizeof(radio->vdev.name)); + radio->vdev.v4l2_dev = &radio->v4l2_dev; + radio->vdev.fops = &usb_si4713_fops; + radio->vdev.ioctl_ops = &usb_si4713_ioctl_ops; + radio->vdev.lock = &radio->lock; + radio->vdev.release = video_device_release_empty; + radio->vdev.vfl_dir = VFL_DIR_TX; + + video_set_drvdata(&radio->vdev, radio); + set_bit(V4L2_FL_USE_FH_PRIO, &radio->vdev.flags); + + retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO, -1); + if (retval < 0) { + dev_err(&intf->dev, "could not register video device\n"); + goto del_adapter; + } + + dev_info(&intf->dev, "V4L2 device registered as %s\n", + video_device_node_name(&radio->vdev)); + + return 0; + +del_adapter: + i2c_del_adapter(adapter); +err_i2cdev: + v4l2_device_unregister(&radio->v4l2_dev); +err_v4l2: + kfree(radio->buffer); + kfree(radio); + return retval; +} + +static void usb_si4713_disconnect(struct usb_interface *intf) +{ + struct si4713_usb_device *radio = to_si4713_dev(usb_get_intfdata(intf)); + + dev_info(&intf->dev, "Si4713 development board now disconnected\n"); + + mutex_lock(&radio->lock); + usb_set_intfdata(intf, NULL); + video_unregister_device(&radio->vdev); + v4l2_device_disconnect(&radio->v4l2_dev); + mutex_unlock(&radio->lock); + v4l2_device_put(&radio->v4l2_dev); +} + +/* USB subsystem interface */ +static struct usb_driver usb_si4713_driver = { + .name = "radio-usb-si4713", + .probe = usb_si4713_probe, + .disconnect = usb_si4713_disconnect, + .id_table = usb_si4713_usb_device_table, +}; + +module_usb_driver(usb_si4713_driver); diff --git a/drivers/media/radio/si4713-i2c.c b/drivers/media/radio/si4713/si4713.c index 9ec48ccbcf0b..07d5153811e8 100644 --- a/drivers/media/radio/si4713-i2c.c +++ b/drivers/media/radio/si4713/si4713.c @@ -27,13 +27,12 @@ #include <linux/i2c.h> #include <linux/slab.h> #include <linux/gpio.h> -#include <linux/regulator/consumer.h> #include <linux/module.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-common.h> -#include "si4713-i2c.h" +#include "si4713.h" /* module parameters */ static int debug; @@ -45,23 +44,18 @@ MODULE_AUTHOR("Eduardo Valentin <eduardo.valentin@nokia.com>"); MODULE_DESCRIPTION("I2C driver for Si4713 FM Radio Transmitter"); MODULE_VERSION("0.0.1"); -static const char *si4713_supply_names[SI4713_NUM_SUPPLIES] = { - "vio", - "vdd", -}; - #define DEFAULT_RDS_PI 0x00 #define DEFAULT_RDS_PTY 0x00 #define DEFAULT_RDS_DEVIATION 0x00C8 #define DEFAULT_RDS_PS_REPEAT_COUNT 0x0003 #define DEFAULT_LIMITER_RTIME 0x1392 #define DEFAULT_LIMITER_DEV 0x102CA -#define DEFAULT_PILOT_FREQUENCY 0x4A38 +#define DEFAULT_PILOT_FREQUENCY 0x4A38 #define DEFAULT_PILOT_DEVIATION 0x1A5E #define DEFAULT_ACOMP_ATIME 0x0000 #define DEFAULT_ACOMP_RTIME 0xF4240L #define DEFAULT_ACOMP_GAIN 0x0F -#define DEFAULT_ACOMP_THRESHOLD (-0x28) +#define DEFAULT_ACOMP_THRESHOLD (-0x28) #define DEFAULT_MUTE 0x01 #define DEFAULT_POWER_LEVEL 88 #define DEFAULT_FREQUENCY 8800 @@ -213,6 +207,7 @@ static int si4713_send_command(struct si4713_device *sdev, const u8 command, u8 response[], const int respn, const int usecs) { struct i2c_client *client = v4l2_get_subdevdata(&sdev->sd); + unsigned long until_jiffies; u8 data1[MAX_ARGS + 1]; int err; @@ -228,30 +223,42 @@ static int si4713_send_command(struct si4713_device *sdev, const u8 command, if (err != argn + 1) { v4l2_err(&sdev->sd, "Error while sending command 0x%02x\n", command); - return (err > 0) ? -EIO : err; + return err < 0 ? err : -EIO; } + until_jiffies = jiffies + usecs_to_jiffies(usecs) + 1; + /* Wait response from interrupt */ - if (!wait_for_completion_timeout(&sdev->work, + if (client->irq) { + if (!wait_for_completion_timeout(&sdev->work, usecs_to_jiffies(usecs) + 1)) - v4l2_warn(&sdev->sd, + v4l2_warn(&sdev->sd, "(%s) Device took too much time to answer.\n", __func__); - - /* Then get the response */ - err = i2c_master_recv(client, response, respn); - if (err != respn) { - v4l2_err(&sdev->sd, - "Error while reading response for command 0x%02x\n", - command); - return (err > 0) ? -EIO : err; } - DBG_BUFFER(&sdev->sd, "Response", response, respn); - if (check_command_failed(response[0])) - return -EBUSY; + do { + err = i2c_master_recv(client, response, respn); + if (err != respn) { + v4l2_err(&sdev->sd, + "Error %d while reading response for command 0x%02x\n", + err, command); + return err < 0 ? err : -EIO; + } - return 0; + DBG_BUFFER(&sdev->sd, "Response", response, respn); + if (!check_command_failed(response[0])) + return 0; + + if (client->irq) + return -EBUSY; + if (usecs <= 1000) + usleep_range(usecs, 1000); + else + usleep_range(1000, 2000); + } while (time_is_after_jiffies(until_jiffies)); + + return -EBUSY; } /* @@ -265,9 +272,9 @@ static int si4713_read_property(struct si4713_device *sdev, u16 prop, u32 *pv) int err; u8 val[SI4713_GET_PROP_NRESP]; /* - * .First byte = 0 - * .Second byte = property's MSB - * .Third byte = property's LSB + * .First byte = 0 + * .Second byte = property's MSB + * .Third byte = property's LSB */ const u8 args[SI4713_GET_PROP_NARGS] = { 0x00, @@ -302,11 +309,11 @@ static int si4713_write_property(struct si4713_device *sdev, u16 prop, u16 val) int rval; u8 resp[SI4713_SET_PROP_NRESP]; /* - * .First byte = 0 - * .Second byte = property's MSB - * .Third byte = property's LSB - * .Fourth byte = value's MSB - * .Fifth byte = value's LSB + * .First byte = 0 + * .Second byte = property's MSB + * .Third byte = property's LSB + * .Fourth byte = value's MSB + * .Fifth byte = value's LSB */ const u8 args[SI4713_SET_PROP_NARGS] = { 0x00, @@ -344,31 +351,36 @@ static int si4713_write_property(struct si4713_device *sdev, u16 prop, u16 val) */ static int si4713_powerup(struct si4713_device *sdev) { + struct i2c_client *client = v4l2_get_subdevdata(&sdev->sd); int err; u8 resp[SI4713_PWUP_NRESP]; /* - * .First byte = Enabled interrupts and boot function - * .Second byte = Input operation mode + * .First byte = Enabled interrupts and boot function + * .Second byte = Input operation mode */ - const u8 args[SI4713_PWUP_NARGS] = { - SI4713_PWUP_CTSIEN | SI4713_PWUP_GPO2OEN | SI4713_PWUP_FUNC_TX, + u8 args[SI4713_PWUP_NARGS] = { + SI4713_PWUP_GPO2OEN | SI4713_PWUP_FUNC_TX, SI4713_PWUP_OPMOD_ANALOG, }; if (sdev->power_state) return 0; - err = regulator_bulk_enable(ARRAY_SIZE(sdev->supplies), - sdev->supplies); - if (err) { - v4l2_err(&sdev->sd, "Failed to enable supplies: %d\n", err); - return err; + if (sdev->supplies) { + err = regulator_bulk_enable(sdev->supplies, sdev->supply_data); + if (err) { + v4l2_err(&sdev->sd, "Failed to enable supplies: %d\n", err); + return err; + } } if (gpio_is_valid(sdev->gpio_reset)) { udelay(50); gpio_set_value(sdev->gpio_reset, 1); } + if (client->irq) + args[0] |= SI4713_PWUP_CTSIEN; + err = si4713_send_command(sdev, SI4713_CMD_POWER_UP, args, ARRAY_SIZE(args), resp, ARRAY_SIZE(resp), @@ -380,13 +392,15 @@ static int si4713_powerup(struct si4713_device *sdev) v4l2_dbg(1, debug, &sdev->sd, "Device in power up mode\n"); sdev->power_state = POWER_ON; - err = si4713_write_property(sdev, SI4713_GPO_IEN, + if (client->irq) + err = si4713_write_property(sdev, SI4713_GPO_IEN, SI4713_STC_INT | SI4713_CTS); - } else { - if (gpio_is_valid(sdev->gpio_reset)) - gpio_set_value(sdev->gpio_reset, 0); - err = regulator_bulk_disable(ARRAY_SIZE(sdev->supplies), - sdev->supplies); + return err; + } + if (gpio_is_valid(sdev->gpio_reset)) + gpio_set_value(sdev->gpio_reset, 0); + if (sdev->supplies) { + err = regulator_bulk_disable(sdev->supplies, sdev->supply_data); if (err) v4l2_err(&sdev->sd, "Failed to disable supplies: %d\n", err); @@ -418,11 +432,13 @@ static int si4713_powerdown(struct si4713_device *sdev) v4l2_dbg(1, debug, &sdev->sd, "Device in reset mode\n"); if (gpio_is_valid(sdev->gpio_reset)) gpio_set_value(sdev->gpio_reset, 0); - err = regulator_bulk_disable(ARRAY_SIZE(sdev->supplies), - sdev->supplies); - if (err) - v4l2_err(&sdev->sd, - "Failed to disable supplies: %d\n", err); + if (sdev->supplies) { + err = regulator_bulk_disable(sdev->supplies, + sdev->supply_data); + if (err) + v4l2_err(&sdev->sd, + "Failed to disable supplies: %d\n", err); + } sdev->power_state = POWER_OFF; } @@ -451,7 +467,7 @@ static int si4713_checkrev(struct si4713_device *sdev) v4l2_info(&sdev->sd, "chip found @ 0x%02x (%s)\n", client->addr << 1, client->adapter->name); } else { - v4l2_err(&sdev->sd, "Invalid product number\n"); + v4l2_err(&sdev->sd, "Invalid product number 0x%X\n", resp[1]); rval = -EINVAL; } return rval; @@ -465,39 +481,45 @@ static int si4713_checkrev(struct si4713_device *sdev) */ static int si4713_wait_stc(struct si4713_device *sdev, const int usecs) { - int err; + struct i2c_client *client = v4l2_get_subdevdata(&sdev->sd); u8 resp[SI4713_GET_STATUS_NRESP]; + unsigned long start_jiffies = jiffies; + int err; - /* Wait response from STC interrupt */ - if (!wait_for_completion_timeout(&sdev->work, - usecs_to_jiffies(usecs) + 1)) + if (client->irq && + !wait_for_completion_timeout(&sdev->work, usecs_to_jiffies(usecs) + 1)) v4l2_warn(&sdev->sd, - "%s: device took too much time to answer (%d usec).\n", - __func__, usecs); - - /* Clear status bits */ - err = si4713_send_command(sdev, SI4713_CMD_GET_INT_STATUS, - NULL, 0, - resp, ARRAY_SIZE(resp), - DEFAULT_TIMEOUT); - - if (err < 0) - goto exit; - - v4l2_dbg(1, debug, &sdev->sd, - "%s: status bits: 0x%02x\n", __func__, resp[0]); - - if (!(resp[0] & SI4713_STC_INT)) - err = -EIO; - -exit: - return err; + "(%s) Device took too much time to answer.\n", __func__); + + for (;;) { + /* Clear status bits */ + err = si4713_send_command(sdev, SI4713_CMD_GET_INT_STATUS, + NULL, 0, + resp, ARRAY_SIZE(resp), + DEFAULT_TIMEOUT); + /* The USB device returns errors when it waits for the + * STC bit to be set. Hence polling */ + if (err >= 0) { + v4l2_dbg(1, debug, &sdev->sd, + "%s: status bits: 0x%02x\n", __func__, resp[0]); + + if (resp[0] & SI4713_STC_INT) + return 0; + } + if (jiffies_to_usecs(jiffies - start_jiffies) > usecs) + return err < 0 ? err : -EIO; + /* We sleep here for 3-4 ms in order to avoid flooding the device + * with USB requests. The si4713 USB driver was developed + * by reverse engineering the Windows USB driver. The windows + * driver also has a ~2.5 ms delay between responses. */ + usleep_range(3000, 4000); + } } /* * si4713_tx_tune_freq - Sets the state of the RF carrier and sets the tuning - * frequency between 76 and 108 MHz in 10 kHz units and - * steps of 50 kHz. + * frequency between 76 and 108 MHz in 10 kHz units and + * steps of 50 kHz. * @sdev: si4713_device structure for the device we are communicating * @frequency: desired frequency (76 - 108 MHz, unit 10 KHz, step 50 kHz) */ @@ -506,9 +528,9 @@ static int si4713_tx_tune_freq(struct si4713_device *sdev, u16 frequency) int err; u8 val[SI4713_TXFREQ_NRESP]; /* - * .First byte = 0 - * .Second byte = frequency's MSB - * .Third byte = frequency's LSB + * .First byte = 0 + * .Second byte = frequency's MSB + * .Third byte = frequency's LSB */ const u8 args[SI4713_TXFREQ_NARGS] = { 0x00, @@ -535,14 +557,14 @@ static int si4713_tx_tune_freq(struct si4713_device *sdev, u16 frequency) } /* - * si4713_tx_tune_power - Sets the RF voltage level between 88 and 115 dBuV in - * 1 dB units. A value of 0x00 indicates off. The command - * also sets the antenna tuning capacitance. A value of 0 - * indicates autotuning, and a value of 1 - 191 indicates - * a manual override, which results in a tuning - * capacitance of 0.25 pF x @antcap. + * si4713_tx_tune_power - Sets the RF voltage level between 88 and 120 dBuV in + * 1 dB units. A value of 0x00 indicates off. The command + * also sets the antenna tuning capacitance. A value of 0 + * indicates autotuning, and a value of 1 - 191 indicates + * a manual override, which results in a tuning + * capacitance of 0.25 pF x @antcap. * @sdev: si4713_device structure for the device we are communicating - * @power: tuning power (88 - 115 dBuV, unit/step 1 dB) + * @power: tuning power (88 - 120 dBuV, unit/step 1 dB) * @antcap: value of antenna tuning capacitor (0 - 191) */ static int si4713_tx_tune_power(struct si4713_device *sdev, u8 power, @@ -551,21 +573,21 @@ static int si4713_tx_tune_power(struct si4713_device *sdev, u8 power, int err; u8 val[SI4713_TXPWR_NRESP]; /* - * .First byte = 0 - * .Second byte = 0 - * .Third byte = power - * .Fourth byte = antcap + * .First byte = 0 + * .Second byte = 0 + * .Third byte = power + * .Fourth byte = antcap */ - const u8 args[SI4713_TXPWR_NARGS] = { + u8 args[SI4713_TXPWR_NARGS] = { 0x00, 0x00, power, antcap, }; - if (((power > 0) && (power < SI4713_MIN_POWER)) || - power > SI4713_MAX_POWER || antcap > SI4713_MAX_ANTCAP) - return -EDOM; + /* Map power values 1-87 to MIN_POWER (88) */ + if (power > 0 && power < SI4713_MIN_POWER) + args[2] = power = SI4713_MIN_POWER; err = si4713_send_command(sdev, SI4713_CMD_TX_TUNE_POWER, args, ARRAY_SIZE(args), val, @@ -583,12 +605,12 @@ static int si4713_tx_tune_power(struct si4713_device *sdev, u8 power, /* * si4713_tx_tune_measure - Enters receive mode and measures the received noise - * level in units of dBuV on the selected frequency. - * The Frequency must be between 76 and 108 MHz in 10 kHz - * units and steps of 50 kHz. The command also sets the - * antenna tuning capacitance. A value of 0 means - * autotuning, and a value of 1 to 191 indicates manual - * override. + * level in units of dBuV on the selected frequency. + * The Frequency must be between 76 and 108 MHz in 10 kHz + * units and steps of 50 kHz. The command also sets the + * antenna tuning capacitance. A value of 0 means + * autotuning, and a value of 1 to 191 indicates manual + * override. * @sdev: si4713_device structure for the device we are communicating * @frequency: desired frequency (76 - 108 MHz, unit 10 KHz, step 50 kHz) * @antcap: value of antenna tuning capacitor (0 - 191) @@ -599,10 +621,10 @@ static int si4713_tx_tune_measure(struct si4713_device *sdev, u16 frequency, int err; u8 val[SI4713_TXMEA_NRESP]; /* - * .First byte = 0 - * .Second byte = frequency's MSB - * .Third byte = frequency's LSB - * .Fourth byte = antcap + * .First byte = 0 + * .Second byte = frequency's MSB + * .Third byte = frequency's LSB + * .Fourth byte = antcap */ const u8 args[SI4713_TXMEA_NARGS] = { 0x00, @@ -632,11 +654,11 @@ static int si4713_tx_tune_measure(struct si4713_device *sdev, u16 frequency, /* * si4713_tx_tune_status- Returns the status of the tx_tune_freq, tx_tune_mea or - * tx_tune_power commands. This command return the current - * frequency, output voltage in dBuV, the antenna tunning - * capacitance value and the received noise level. The - * command also clears the stcint interrupt bit when the - * first bit of its arguments is high. + * tx_tune_power commands. This command return the current + * frequency, output voltage in dBuV, the antenna tunning + * capacitance value and the received noise level. The + * command also clears the stcint interrupt bit when the + * first bit of its arguments is high. * @sdev: si4713_device structure for the device we are communicating * @intack: 0x01 to clear the seek/tune complete interrupt status indicator. * @frequency: returned frequency @@ -651,7 +673,7 @@ static int si4713_tx_tune_status(struct si4713_device *sdev, u8 intack, int err; u8 val[SI4713_TXSTATUS_NRESP]; /* - * .First byte = intack bit + * .First byte = intack bit */ const u8 args[SI4713_TXSTATUS_NARGS] = { intack & SI4713_INTACK_MASK, @@ -812,8 +834,9 @@ static int si4713_set_rds_ps_name(struct si4713_device *sdev, char *ps_name) return rval; } -static int si4713_set_rds_radio_text(struct si4713_device *sdev, char *rt) +static int si4713_set_rds_radio_text(struct si4713_device *sdev, const char *rt) { + static const char cr[RDS_RADIOTEXT_BLK_SIZE] = { RDS_CARRIAGE_RETURN, 0 }; int rval = 0, i; u16 t_index = 0; u8 b_index = 0, cr_inserted = 0; @@ -837,7 +860,7 @@ static int si4713_set_rds_radio_text(struct si4713_device *sdev, char *rt) for (i = 0; i < RDS_RADIOTEXT_BLK_SIZE; i++) { if (!rt[t_index + i] || rt[t_index + i] == RDS_CARRIAGE_RETURN) { - rt[t_index + i] = RDS_CARRIAGE_RETURN; + rt = cr; cr_inserted = 1; break; } @@ -1024,7 +1047,6 @@ static int si4713_initialize(struct si4713_device *sdev) if (rval < 0) return rval; - sdev->frequency = DEFAULT_FREQUENCY; sdev->stereo = 1; sdev->tune_rnl = DEFAULT_TUNE_RNL; @@ -1345,7 +1367,7 @@ static int si4713_probe(struct i2c_client *client, struct v4l2_ctrl_handler *hdl; int rval, i; - sdev = kzalloc(sizeof *sdev, GFP_KERNEL); + sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); if (!sdev) { dev_err(&client->dev, "Failed to alloc video device.\n"); rval = -ENOMEM; @@ -1362,13 +1384,14 @@ static int si4713_probe(struct i2c_client *client, } sdev->gpio_reset = pdata->gpio_reset; gpio_direction_output(sdev->gpio_reset, 0); + sdev->supplies = pdata->supplies; } - for (i = 0; i < ARRAY_SIZE(sdev->supplies); i++) - sdev->supplies[i].supply = si4713_supply_names[i]; + for (i = 0; i < sdev->supplies; i++) + sdev->supply_data[i].supply = pdata->supply_names[i]; - rval = regulator_bulk_get(&client->dev, ARRAY_SIZE(sdev->supplies), - sdev->supplies); + rval = regulator_bulk_get(&client->dev, sdev->supplies, + sdev->supply_data); if (rval) { dev_err(&client->dev, "Cannot get regulators: %d\n", rval); goto free_gpio; @@ -1420,8 +1443,8 @@ static int si4713_probe(struct i2c_client *client, V4L2_CID_AUDIO_COMPRESSION_GAIN, 0, MAX_ACOMP_GAIN, 1, DEFAULT_ACOMP_GAIN); sdev->compression_threshold = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, - V4L2_CID_AUDIO_COMPRESSION_THRESHOLD, MIN_ACOMP_THRESHOLD, - MAX_ACOMP_THRESHOLD, 1, + V4L2_CID_AUDIO_COMPRESSION_THRESHOLD, + MIN_ACOMP_THRESHOLD, MAX_ACOMP_THRESHOLD, 1, DEFAULT_ACOMP_THRESHOLD); sdev->compression_attack_time = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME, 0, @@ -1443,9 +1466,11 @@ static int si4713_probe(struct i2c_client *client, V4L2_CID_TUNE_PREEMPHASIS, V4L2_PREEMPHASIS_75_uS, 0, V4L2_PREEMPHASIS_50_uS); sdev->tune_pwr_level = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, - V4L2_CID_TUNE_POWER_LEVEL, 0, 120, 1, DEFAULT_POWER_LEVEL); + V4L2_CID_TUNE_POWER_LEVEL, 0, SI4713_MAX_POWER, + 1, DEFAULT_POWER_LEVEL); sdev->tune_ant_cap = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, - V4L2_CID_TUNE_ANTENNA_CAPACITOR, 0, 191, 1, 0); + V4L2_CID_TUNE_ANTENNA_CAPACITOR, 0, SI4713_MAX_ANTCAP, + 1, 0); if (hdl->error) { rval = hdl->error; @@ -1481,7 +1506,7 @@ free_irq: free_ctrls: v4l2_ctrl_handler_free(hdl); put_reg: - regulator_bulk_free(ARRAY_SIZE(sdev->supplies), sdev->supplies); + regulator_bulk_free(sdev->supplies, sdev->supply_data); free_gpio: if (gpio_is_valid(sdev->gpio_reset)) gpio_free(sdev->gpio_reset); @@ -1505,7 +1530,7 @@ static int si4713_remove(struct i2c_client *client) v4l2_device_unregister_subdev(sd); v4l2_ctrl_handler_free(sd->ctrl_handler); - regulator_bulk_free(ARRAY_SIZE(sdev->supplies), sdev->supplies); + regulator_bulk_free(sdev->supplies, sdev->supply_data); if (gpio_is_valid(sdev->gpio_reset)) gpio_free(sdev->gpio_reset); kfree(sdev); diff --git a/drivers/media/radio/si4713-i2c.h b/drivers/media/radio/si4713/si4713.h index 25cdea26343b..4837cf6e0e1b 100644 --- a/drivers/media/radio/si4713-i2c.h +++ b/drivers/media/radio/si4713/si4713.h @@ -15,6 +15,7 @@ #ifndef SI4713_I2C_H #define SI4713_I2C_H +#include <linux/regulator/consumer.h> #include <media/v4l2-subdev.h> #include <media/v4l2-ctrls.h> #include <media/si4713.h> @@ -226,7 +227,8 @@ struct si4713_device { struct v4l2_ctrl *tune_ant_cap; }; struct completion work; - struct regulator_bulk_data supplies[SI4713_NUM_SUPPLIES]; + unsigned supplies; + struct regulator_bulk_data supply_data[SI4713_NUM_SUPPLIES]; int gpio_reset; u32 power_state; u32 rds_enabled; diff --git a/drivers/media/radio/tea575x.c b/drivers/media/radio/tea575x.c index cef06981b7c9..7c14060a40b8 100644 --- a/drivers/media/radio/tea575x.c +++ b/drivers/media/radio/tea575x.c @@ -20,12 +20,12 @@ * */ -#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 <asm/io.h> #include <media/v4l2-device.h> #include <media/v4l2-dev.h> #include <media/v4l2-fh.h> diff --git a/drivers/media/rc/imon.c b/drivers/media/rc/imon.c index f329485c6629..822b9f47ca72 100644 --- a/drivers/media/rc/imon.c +++ b/drivers/media/rc/imon.c @@ -1909,10 +1909,8 @@ static struct input_dev *imon_init_idev(struct imon_context *ictx) int ret, i; idev = input_allocate_device(); - if (!idev) { - dev_err(ictx->dev, "input dev allocation failed\n"); + if (!idev) goto out; - } snprintf(ictx->name_idev, sizeof(ictx->name_idev), "iMON Panel, Knob and Mouse(%04x:%04x)", @@ -1960,10 +1958,8 @@ static struct input_dev *imon_init_touch(struct imon_context *ictx) int ret; touch = input_allocate_device(); - if (!touch) { - dev_err(ictx->dev, "touchscreen input dev allocation failed\n"); + if (!touch) goto touch_alloc_failed; - } snprintf(ictx->name_touch, sizeof(ictx->name_touch), "iMON USB Touchscreen (%04x:%04x)", diff --git a/drivers/media/rc/keymaps/Makefile b/drivers/media/rc/keymaps/Makefile index b1cde8c0422b..0b8c54919010 100644 --- a/drivers/media/rc/keymaps/Makefile +++ b/drivers/media/rc/keymaps/Makefile @@ -98,4 +98,5 @@ obj-$(CONFIG_RC_MAP) += rc-adstech-dvb-t-pci.o \ rc-videomate-s350.o \ rc-videomate-tv-pvr.o \ rc-winfast.o \ - rc-winfast-usbii-deluxe.o + rc-winfast-usbii-deluxe.o \ + rc-su3000.o diff --git a/drivers/media/rc/keymaps/rc-su3000.c b/drivers/media/rc/keymaps/rc-su3000.c new file mode 100644 index 000000000000..8dbd3e9bc951 --- /dev/null +++ b/drivers/media/rc/keymaps/rc-su3000.c @@ -0,0 +1,75 @@ +/* rc-su3000.h - Keytable for Geniatech HDStar Remote Controller + * + * Copyright (c) 2013 by Evgeny Plehov <Evgeny Plehov@ukr.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. + */ + +#include <media/rc-map.h> +#include <linux/module.h> + +static struct rc_map_table su3000[] = { + { 0x25, KEY_POWER }, /* right-bottom Red */ + { 0x0a, KEY_MUTE }, /* -/-- */ + { 0x01, KEY_1 }, + { 0x02, KEY_2 }, + { 0x03, KEY_3 }, + { 0x04, KEY_4 }, + { 0x05, KEY_5 }, + { 0x06, KEY_6 }, + { 0x07, KEY_7 }, + { 0x08, KEY_8 }, + { 0x09, KEY_9 }, + { 0x00, KEY_0 }, + { 0x20, KEY_UP }, /* CH+ */ + { 0x21, KEY_DOWN }, /* CH+ */ + { 0x12, KEY_VOLUMEUP }, /* Brightness Up */ + { 0x13, KEY_VOLUMEDOWN },/* Brightness Down */ + { 0x1f, KEY_RECORD }, + { 0x17, KEY_PLAY }, + { 0x16, KEY_PAUSE }, + { 0x0b, KEY_STOP }, + { 0x27, KEY_FASTFORWARD },/* >> */ + { 0x26, KEY_REWIND }, /* << */ + { 0x0d, KEY_OK }, /* Mute */ + { 0x11, KEY_LEFT }, /* VOL- */ + { 0x10, KEY_RIGHT }, /* VOL+ */ + { 0x29, KEY_BACK }, /* button under 9 */ + { 0x2c, KEY_MENU }, /* TTX */ + { 0x2b, KEY_EPG }, /* EPG */ + { 0x1e, KEY_RED }, /* OSD */ + { 0x0e, KEY_GREEN }, /* Window */ + { 0x2d, KEY_YELLOW }, /* button under << */ + { 0x0f, KEY_BLUE }, /* bottom yellow button */ + { 0x14, KEY_AUDIO }, /* Snapshot */ + { 0x38, KEY_TV }, /* TV/Radio */ + { 0x0c, KEY_ESC } /* upper Red button */ +}; + +static struct rc_map_list su3000_map = { + .map = { + .scan = su3000, + .size = ARRAY_SIZE(su3000), + .rc_type = RC_TYPE_RC5, + .name = RC_MAP_SU3000, + } +}; + +static int __init init_rc_map_su3000(void) +{ + return rc_map_register(&su3000_map); +} + +static void __exit exit_rc_map_su3000(void) +{ + rc_map_unregister(&su3000_map); +} + +module_init(init_rc_map_su3000) +module_exit(exit_rc_map_su3000) + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Evgeny Plehov <Evgeny Plehov@ukr.net>"); diff --git a/drivers/media/rc/mceusb.c b/drivers/media/rc/mceusb.c index 3c761014d3ce..a25bb1581e46 100644 --- a/drivers/media/rc/mceusb.c +++ b/drivers/media/rc/mceusb.c @@ -199,6 +199,7 @@ static bool debug; #define VENDOR_TIVO 0x105a #define VENDOR_CONEXANT 0x0572 #define VENDOR_TWISTEDMELON 0x2596 +#define VENDOR_HAUPPAUGE 0x2040 enum mceusb_model_type { MCE_GEN2 = 0, /* Most boards */ @@ -210,6 +211,7 @@ enum mceusb_model_type { MULTIFUNCTION, TIVO_KIT, MCE_GEN2_NO_TX, + HAUPPAUGE_CX_HYBRID_TV, }; struct mceusb_model { @@ -258,6 +260,11 @@ static const struct mceusb_model mceusb_model[] = { .no_tx = 1, /* tx isn't wired up at all */ .name = "Conexant Hybrid TV (cx231xx) MCE IR", }, + [HAUPPAUGE_CX_HYBRID_TV] = { + .rc_map = RC_MAP_HAUPPAUGE, + .no_tx = 1, /* eeprom says it has no tx */ + .name = "Conexant Hybrid TV (cx231xx) MCE IR no TX", + }, [MULTIFUNCTION] = { .mce_gen2 = 1, .ir_intfnum = 2, @@ -399,6 +406,9 @@ static struct usb_device_id mceusb_dev_table[] = { { USB_DEVICE(VENDOR_TWISTEDMELON, 0x8016) }, /* Twisted Melon Inc. - Manta Transceiver */ { USB_DEVICE(VENDOR_TWISTEDMELON, 0x8042) }, + /* Hauppauge WINTV-HVR-HVR 930C-HD - based on cx231xx */ + { USB_DEVICE(VENDOR_HAUPPAUGE, 0xb130), + .driver_info = HAUPPAUGE_CX_HYBRID_TV }, /* Terminating entry */ { } }; diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c index 46da365c9c84..02e2f38c9c85 100644 --- a/drivers/media/rc/rc-main.c +++ b/drivers/media/rc/rc-main.c @@ -22,6 +22,10 @@ #include <linux/module.h> #include "rc-core-priv.h" +/* Bitmap to store allocated device numbers from 0 to IRRCV_NUM_DEVICES - 1 */ +#define IRRCV_NUM_DEVICES 256 +DECLARE_BITMAP(ir_core_dev_number, IRRCV_NUM_DEVICES); + /* Sizes are in bytes, 256 bytes allows for 32 entries on x64 */ #define IR_TAB_MIN_SIZE 256 #define IR_TAB_MAX_SIZE 8192 @@ -1065,10 +1069,9 @@ EXPORT_SYMBOL_GPL(rc_free_device); int rc_register_device(struct rc_dev *dev) { static bool raw_init = false; /* raw decoders loaded? */ - static atomic_t devno = ATOMIC_INIT(0); struct rc_map *rc_map; const char *path; - int rc; + int rc, devno; if (!dev || !dev->map_name) return -EINVAL; @@ -1096,7 +1099,15 @@ int rc_register_device(struct rc_dev *dev) */ mutex_lock(&dev->lock); - dev->devno = (unsigned long)(atomic_inc_return(&devno) - 1); + do { + devno = find_first_zero_bit(ir_core_dev_number, + IRRCV_NUM_DEVICES); + /* No free device slots */ + if (devno >= IRRCV_NUM_DEVICES) + return -ENOMEM; + } while (test_and_set_bit(devno, ir_core_dev_number)); + + dev->devno = devno; dev_set_name(&dev->dev, "rc%ld", dev->devno); dev_set_drvdata(&dev->dev, dev); rc = device_add(&dev->dev); @@ -1186,6 +1197,7 @@ out_dev: device_del(&dev->dev); out_unlock: mutex_unlock(&dev->lock); + clear_bit(dev->devno, ir_core_dev_number); return rc; } EXPORT_SYMBOL_GPL(rc_register_device); @@ -1197,6 +1209,8 @@ void rc_unregister_device(struct rc_dev *dev) del_timer_sync(&dev->timer_keyup); + clear_bit(dev->devno, ir_core_dev_number); + if (dev->driver_type == RC_DRIVER_IR_RAW) ir_raw_event_unregister(dev); diff --git a/drivers/media/rc/st_rc.c b/drivers/media/rc/st_rc.c index 65120c2d47ad..8f0cddb9e8f2 100644 --- a/drivers/media/rc/st_rc.c +++ b/drivers/media/rc/st_rc.c @@ -13,6 +13,7 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> +#include <linux/reset.h> #include <media/rc-core.h> #include <linux/pinctrl/consumer.h> @@ -28,6 +29,7 @@ struct st_rc_device { int sample_mult; int sample_div; bool rxuhfmode; + struct reset_control *rstc; }; /* Registers */ @@ -161,6 +163,10 @@ static void st_rc_hardware_init(struct st_rc_device *dev) unsigned int rx_max_symbol_per = MAX_SYMB_TIME; unsigned int rx_sampling_freq_div; + /* Enable the IP */ + if (dev->rstc) + reset_control_deassert(dev->rstc); + clk_prepare_enable(dev->sys_clock); baseclock = clk_get_rate(dev->sys_clock); @@ -271,6 +277,11 @@ static int st_rc_probe(struct platform_device *pdev) else rc_dev->rx_base = rc_dev->base; + + rc_dev->rstc = reset_control_get(dev, NULL); + if (IS_ERR(rc_dev->rstc)) + rc_dev->rstc = NULL; + rc_dev->dev = dev; platform_set_drvdata(pdev, rc_dev); st_rc_hardware_init(rc_dev); @@ -338,6 +349,8 @@ static int st_rc_suspend(struct device *dev) writel(0x00, rc_dev->rx_base + IRB_RX_EN); writel(0x00, rc_dev->rx_base + IRB_RX_INT_EN); clk_disable_unprepare(rc_dev->sys_clock); + if (rc_dev->rstc) + reset_control_assert(rc_dev->rstc); } return 0; diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig index 15665debc572..ba2e365296cf 100644 --- a/drivers/media/tuners/Kconfig +++ b/drivers/media/tuners/Kconfig @@ -215,6 +215,13 @@ config MEDIA_TUNER_FC2580 help FCI FC2580 silicon tuner driver. +config MEDIA_TUNER_M88TS2022 + tristate "Montage M88TS2022 silicon tuner" + depends on MEDIA_SUPPORT && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Montage M88TS2022 silicon tuner driver. + config MEDIA_TUNER_TUA9001 tristate "Infineon TUA 9001 silicon tuner" depends on MEDIA_SUPPORT && I2C diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile index 308f108eadba..efe82a904b12 100644 --- a/drivers/media/tuners/Makefile +++ b/drivers/media/tuners/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_MEDIA_TUNER_TDA18212) += tda18212.o obj-$(CONFIG_MEDIA_TUNER_E4000) += e4000.o obj-$(CONFIG_MEDIA_TUNER_FC2580) += fc2580.o obj-$(CONFIG_MEDIA_TUNER_TUA9001) += tua9001.o +obj-$(CONFIG_MEDIA_TUNER_M88TS2022) += m88ts2022.o obj-$(CONFIG_MEDIA_TUNER_FC0011) += fc0011.o obj-$(CONFIG_MEDIA_TUNER_FC0012) += fc0012.o obj-$(CONFIG_MEDIA_TUNER_FC0013) += fc0013.o diff --git a/drivers/media/tuners/e4000.c b/drivers/media/tuners/e4000.c index 72971a8d3c37..40c1da707d15 100644 --- a/drivers/media/tuners/e4000.c +++ b/drivers/media/tuners/e4000.c @@ -243,8 +243,10 @@ static int e4000_set_params(struct dvb_frontend *fe) break; } - if (i == ARRAY_SIZE(e4000_pll_lut)) + if (i == ARRAY_SIZE(e4000_pll_lut)) { + ret = -EINVAL; goto err; + } /* * Note: Currently f_vco overflows when c->frequency is 1 073 741 824 Hz @@ -271,8 +273,10 @@ static int e4000_set_params(struct dvb_frontend *fe) break; } - if (i == ARRAY_SIZE(e400_lna_filter_lut)) + if (i == ARRAY_SIZE(e400_lna_filter_lut)) { + ret = -EINVAL; goto err; + } ret = e4000_wr_reg(priv, 0x10, e400_lna_filter_lut[i].val); if (ret < 0) @@ -284,8 +288,10 @@ static int e4000_set_params(struct dvb_frontend *fe) break; } - if (i == ARRAY_SIZE(e4000_if_filter_lut)) + if (i == ARRAY_SIZE(e4000_if_filter_lut)) { + ret = -EINVAL; goto err; + } buf[0] = e4000_if_filter_lut[i].reg11_val; buf[1] = e4000_if_filter_lut[i].reg12_val; @@ -300,8 +306,10 @@ static int e4000_set_params(struct dvb_frontend *fe) break; } - if (i == ARRAY_SIZE(e4000_band_lut)) + if (i == ARRAY_SIZE(e4000_band_lut)) { + ret = -EINVAL; goto err; + } ret = e4000_wr_reg(priv, 0x07, e4000_band_lut[i].reg07_val); if (ret < 0) diff --git a/drivers/media/tuners/m88ts2022.c b/drivers/media/tuners/m88ts2022.c new file mode 100644 index 000000000000..40c42dec721b --- /dev/null +++ b/drivers/media/tuners/m88ts2022.c @@ -0,0 +1,674 @@ +/* + * Montage M88TS2022 silicon tuner 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. + * + * Some calculations are taken from existing TS2020 driver. + */ + +#include "m88ts2022_priv.h" + +/* write multiple registers */ +static int m88ts2022_wr_regs(struct m88ts2022_priv *priv, + u8 reg, const u8 *val, int len) +{ +#define MAX_WR_LEN 3 +#define MAX_WR_XFER_LEN (MAX_WR_LEN + 1) + int ret; + u8 buf[MAX_WR_XFER_LEN]; + struct i2c_msg msg[1] = { + { + .addr = priv->client->addr, + .flags = 0, + .len = 1 + len, + .buf = buf, + } + }; + + if (WARN_ON(len > MAX_WR_LEN)) + return -EINVAL; + + buf[0] = reg; + memcpy(&buf[1], val, len); + + ret = i2c_transfer(priv->client->adapter, msg, 1); + if (ret == 1) { + ret = 0; + } else { + dev_warn(&priv->client->dev, + "%s: i2c wr failed=%d reg=%02x len=%d\n", + KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + + return ret; +} + +/* read multiple registers */ +static int m88ts2022_rd_regs(struct m88ts2022_priv *priv, u8 reg, + u8 *val, int len) +{ +#define MAX_RD_LEN 1 +#define MAX_RD_XFER_LEN (MAX_RD_LEN) + int ret; + u8 buf[MAX_RD_XFER_LEN]; + struct i2c_msg msg[2] = { + { + .addr = priv->client->addr, + .flags = 0, + .len = 1, + .buf = ®, + }, { + .addr = priv->client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = buf, + } + }; + + if (WARN_ON(len > MAX_RD_LEN)) + return -EINVAL; + + ret = i2c_transfer(priv->client->adapter, msg, 2); + if (ret == 2) { + memcpy(val, buf, len); + ret = 0; + } else { + dev_warn(&priv->client->dev, + "%s: i2c rd failed=%d reg=%02x len=%d\n", + KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + + return ret; +} + +/* write single register */ +static int m88ts2022_wr_reg(struct m88ts2022_priv *priv, u8 reg, u8 val) +{ + return m88ts2022_wr_regs(priv, reg, &val, 1); +} + +/* read single register */ +static int m88ts2022_rd_reg(struct m88ts2022_priv *priv, u8 reg, u8 *val) +{ + return m88ts2022_rd_regs(priv, reg, val, 1); +} + +/* write single register with mask */ +static int m88ts2022_wr_reg_mask(struct m88ts2022_priv *priv, + u8 reg, u8 val, u8 mask) +{ + int ret; + u8 u8tmp; + + /* no need for read if whole reg is written */ + if (mask != 0xff) { + ret = m88ts2022_rd_regs(priv, reg, &u8tmp, 1); + if (ret) + return ret; + + val &= mask; + u8tmp &= ~mask; + val |= u8tmp; + } + + return m88ts2022_wr_regs(priv, reg, &val, 1); +} + +static int m88ts2022_cmd(struct dvb_frontend *fe, + int op, int sleep, u8 reg, u8 mask, u8 val, u8 *reg_val) +{ + struct m88ts2022_priv *priv = fe->tuner_priv; + int ret, i; + u8 u8tmp; + struct m88ts2022_reg_val reg_vals[] = { + {0x51, 0x1f - op}, + {0x51, 0x1f}, + {0x50, 0x00 + op}, + {0x50, 0x00}, + }; + + for (i = 0; i < 2; i++) { + dev_dbg(&priv->client->dev, + "%s: i=%d op=%02x reg=%02x mask=%02x val=%02x\n", + __func__, i, op, reg, mask, val); + + for (i = 0; i < ARRAY_SIZE(reg_vals); i++) { + ret = m88ts2022_wr_reg(priv, reg_vals[i].reg, + reg_vals[i].val); + if (ret) + goto err; + } + + usleep_range(sleep * 1000, sleep * 10000); + + ret = m88ts2022_rd_reg(priv, reg, &u8tmp); + if (ret) + goto err; + + if ((u8tmp & mask) != val) + break; + } + + if (reg_val) + *reg_val = u8tmp; +err: + return ret; +} + +static int m88ts2022_set_params(struct dvb_frontend *fe) +{ + struct m88ts2022_priv *priv = fe->tuner_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + unsigned int frequency_khz, frequency_offset_khz, f_3db_hz; + unsigned int f_ref_khz, f_vco_khz, div_ref, div_out, pll_n, gdiv28; + u8 buf[3], u8tmp, cap_code, lpf_gm, lpf_mxdiv, div_max, div_min; + u16 u16tmp; + dev_dbg(&priv->client->dev, + "%s: frequency=%d symbol_rate=%d rolloff=%d\n", + __func__, c->frequency, c->symbol_rate, c->rolloff); + /* + * Integer-N PLL synthesizer + * kHz is used for all calculations to keep calculations within 32-bit + */ + f_ref_khz = DIV_ROUND_CLOSEST(priv->cfg.clock, 1000); + div_ref = DIV_ROUND_CLOSEST(f_ref_khz, 2000); + + if (c->symbol_rate < 5000000) + frequency_offset_khz = 3000; /* 3 MHz */ + else + frequency_offset_khz = 0; + + frequency_khz = c->frequency + frequency_offset_khz; + + if (frequency_khz < 1103000) { + div_out = 4; + u8tmp = 0x1b; + } else { + div_out = 2; + u8tmp = 0x0b; + } + + buf[0] = u8tmp; + buf[1] = 0x40; + ret = m88ts2022_wr_regs(priv, 0x10, buf, 2); + if (ret) + goto err; + + f_vco_khz = frequency_khz * div_out; + pll_n = f_vco_khz * div_ref / f_ref_khz; + pll_n += pll_n % 2; + priv->frequency_khz = pll_n * f_ref_khz / div_ref / div_out; + + if (pll_n < 4095) + u16tmp = pll_n - 1024; + else if (pll_n < 6143) + u16tmp = pll_n + 1024; + else + u16tmp = pll_n + 3072; + + buf[0] = (u16tmp >> 8) & 0x3f; + buf[1] = (u16tmp >> 0) & 0xff; + buf[2] = div_ref - 8; + ret = m88ts2022_wr_regs(priv, 0x01, buf, 3); + if (ret) + goto err; + + dev_dbg(&priv->client->dev, + "%s: frequency=%u offset=%d f_vco_khz=%u pll_n=%u div_ref=%u div_out=%u\n", + __func__, priv->frequency_khz, + priv->frequency_khz - c->frequency, f_vco_khz, pll_n, + div_ref, div_out); + + ret = m88ts2022_cmd(fe, 0x10, 5, 0x15, 0x40, 0x00, NULL); + if (ret) + goto err; + + ret = m88ts2022_rd_reg(priv, 0x14, &u8tmp); + if (ret) + goto err; + + u8tmp &= 0x7f; + if (u8tmp < 64) { + ret = m88ts2022_wr_reg_mask(priv, 0x10, 0x80, 0x80); + if (ret) + goto err; + + ret = m88ts2022_wr_reg(priv, 0x11, 0x6f); + if (ret) + goto err; + + ret = m88ts2022_cmd(fe, 0x10, 5, 0x15, 0x40, 0x00, NULL); + if (ret) + goto err; + } + + ret = m88ts2022_rd_reg(priv, 0x14, &u8tmp); + if (ret) + goto err; + + u8tmp &= 0x1f; + if (u8tmp > 19) { + ret = m88ts2022_wr_reg_mask(priv, 0x10, 0x00, 0x02); + if (ret) + goto err; + } + + ret = m88ts2022_cmd(fe, 0x08, 5, 0x3c, 0xff, 0x00, NULL); + if (ret) + goto err; + + ret = m88ts2022_wr_reg(priv, 0x25, 0x00); + if (ret) + goto err; + + ret = m88ts2022_wr_reg(priv, 0x27, 0x70); + if (ret) + goto err; + + ret = m88ts2022_wr_reg(priv, 0x41, 0x09); + if (ret) + goto err; + + ret = m88ts2022_wr_reg(priv, 0x08, 0x0b); + if (ret) + goto err; + + /* filters */ + gdiv28 = DIV_ROUND_CLOSEST(f_ref_khz * 1694U, 1000000U); + + ret = m88ts2022_wr_reg(priv, 0x04, gdiv28); + if (ret) + goto err; + + ret = m88ts2022_cmd(fe, 0x04, 2, 0x26, 0xff, 0x00, &u8tmp); + if (ret) + goto err; + + cap_code = u8tmp & 0x3f; + + ret = m88ts2022_wr_reg(priv, 0x41, 0x0d); + if (ret) + goto err; + + ret = m88ts2022_cmd(fe, 0x04, 2, 0x26, 0xff, 0x00, &u8tmp); + if (ret) + goto err; + + u8tmp &= 0x3f; + cap_code = (cap_code + u8tmp) / 2; + gdiv28 = gdiv28 * 207 / (cap_code * 2 + 151); + div_max = gdiv28 * 135 / 100; + div_min = gdiv28 * 78 / 100; + div_max = clamp_val(div_max, 0U, 63U); + + f_3db_hz = c->symbol_rate * 135UL / 200UL; + f_3db_hz += 2000000U + (frequency_offset_khz * 1000U); + f_3db_hz = clamp(f_3db_hz, 7000000U, 40000000U); + +#define LPF_COEFF 3200U + lpf_gm = DIV_ROUND_CLOSEST(f_3db_hz * gdiv28, LPF_COEFF * f_ref_khz); + lpf_gm = clamp_val(lpf_gm, 1U, 23U); + + lpf_mxdiv = DIV_ROUND_CLOSEST(lpf_gm * LPF_COEFF * f_ref_khz, f_3db_hz); + if (lpf_mxdiv < div_min) + lpf_mxdiv = DIV_ROUND_CLOSEST(++lpf_gm * LPF_COEFF * f_ref_khz, f_3db_hz); + lpf_mxdiv = clamp_val(lpf_mxdiv, 0U, div_max); + + ret = m88ts2022_wr_reg(priv, 0x04, lpf_mxdiv); + if (ret) + goto err; + + ret = m88ts2022_wr_reg(priv, 0x06, lpf_gm); + if (ret) + goto err; + + ret = m88ts2022_cmd(fe, 0x04, 2, 0x26, 0xff, 0x00, &u8tmp); + if (ret) + goto err; + + cap_code = u8tmp & 0x3f; + + ret = m88ts2022_wr_reg(priv, 0x41, 0x09); + if (ret) + goto err; + + ret = m88ts2022_cmd(fe, 0x04, 2, 0x26, 0xff, 0x00, &u8tmp); + if (ret) + goto err; + + u8tmp &= 0x3f; + cap_code = (cap_code + u8tmp) / 2; + + u8tmp = cap_code | 0x80; + ret = m88ts2022_wr_reg(priv, 0x25, u8tmp); + if (ret) + goto err; + + ret = m88ts2022_wr_reg(priv, 0x27, 0x30); + if (ret) + goto err; + + ret = m88ts2022_wr_reg(priv, 0x08, 0x09); + if (ret) + goto err; + + ret = m88ts2022_cmd(fe, 0x01, 20, 0x21, 0xff, 0x00, NULL); + if (ret) + goto err; +err: + if (ret) + dev_dbg(&priv->client->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +static int m88ts2022_init(struct dvb_frontend *fe) +{ + struct m88ts2022_priv *priv = fe->tuner_priv; + int ret, i; + u8 u8tmp; + static const struct m88ts2022_reg_val reg_vals[] = { + {0x7d, 0x9d}, + {0x7c, 0x9a}, + {0x7a, 0x76}, + {0x3b, 0x01}, + {0x63, 0x88}, + {0x61, 0x85}, + {0x22, 0x30}, + {0x30, 0x40}, + {0x20, 0x23}, + {0x24, 0x02}, + {0x12, 0xa0}, + }; + dev_dbg(&priv->client->dev, "%s:\n", __func__); + + ret = m88ts2022_wr_reg(priv, 0x00, 0x01); + if (ret) + goto err; + + ret = m88ts2022_wr_reg(priv, 0x00, 0x03); + if (ret) + goto err; + + switch (priv->cfg.clock_out) { + case M88TS2022_CLOCK_OUT_DISABLED: + u8tmp = 0x60; + break; + case M88TS2022_CLOCK_OUT_ENABLED: + u8tmp = 0x70; + ret = m88ts2022_wr_reg(priv, 0x05, priv->cfg.clock_out_div); + if (ret) + goto err; + break; + case M88TS2022_CLOCK_OUT_ENABLED_XTALOUT: + u8tmp = 0x6c; + break; + default: + goto err; + } + + ret = m88ts2022_wr_reg(priv, 0x42, u8tmp); + if (ret) + goto err; + + if (priv->cfg.loop_through) + u8tmp = 0xec; + else + u8tmp = 0x6c; + + ret = m88ts2022_wr_reg(priv, 0x62, u8tmp); + if (ret) + goto err; + + for (i = 0; i < ARRAY_SIZE(reg_vals); i++) { + ret = m88ts2022_wr_reg(priv, reg_vals[i].reg, reg_vals[i].val); + if (ret) + goto err; + } +err: + if (ret) + dev_dbg(&priv->client->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ts2022_sleep(struct dvb_frontend *fe) +{ + struct m88ts2022_priv *priv = fe->tuner_priv; + int ret; + dev_dbg(&priv->client->dev, "%s:\n", __func__); + + ret = m88ts2022_wr_reg(priv, 0x00, 0x00); + if (ret) + goto err; +err: + if (ret) + dev_dbg(&priv->client->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int m88ts2022_get_frequency(struct dvb_frontend *fe, u32 *frequency) +{ + struct m88ts2022_priv *priv = fe->tuner_priv; + dev_dbg(&priv->client->dev, "%s:\n", __func__); + + *frequency = priv->frequency_khz; + return 0; +} + +static int m88ts2022_get_if_frequency(struct dvb_frontend *fe, u32 *frequency) +{ + struct m88ts2022_priv *priv = fe->tuner_priv; + dev_dbg(&priv->client->dev, "%s:\n", __func__); + + *frequency = 0; /* Zero-IF */ + return 0; +} + +static int m88ts2022_get_rf_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct m88ts2022_priv *priv = fe->tuner_priv; + int ret; + u8 u8tmp; + u16 gain, u16tmp; + unsigned int gain1, gain2, gain3; + + ret = m88ts2022_rd_reg(priv, 0x3d, &u8tmp); + if (ret) + goto err; + + gain1 = (u8tmp >> 0) & 0x1f; + gain1 = clamp(gain1, 0U, 15U); + + ret = m88ts2022_rd_reg(priv, 0x21, &u8tmp); + if (ret) + goto err; + + gain2 = (u8tmp >> 0) & 0x1f; + gain2 = clamp(gain2, 2U, 16U); + + ret = m88ts2022_rd_reg(priv, 0x66, &u8tmp); + if (ret) + goto err; + + gain3 = (u8tmp >> 3) & 0x07; + gain3 = clamp(gain3, 0U, 6U); + + gain = gain1 * 265 + gain2 * 338 + gain3 * 285; + + /* scale value to 0x0000-0xffff */ + u16tmp = (0xffff - gain); + u16tmp = clamp_val(u16tmp, 59000U, 61500U); + + *strength = (u16tmp - 59000) * 0xffff / (61500 - 59000); +err: + if (ret) + dev_dbg(&priv->client->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static const struct dvb_tuner_ops m88ts2022_tuner_ops = { + .info = { + .name = "Montage M88TS2022", + .frequency_min = 950000, + .frequency_max = 2150000, + }, + + .init = m88ts2022_init, + .sleep = m88ts2022_sleep, + .set_params = m88ts2022_set_params, + + .get_frequency = m88ts2022_get_frequency, + .get_if_frequency = m88ts2022_get_if_frequency, + .get_rf_strength = m88ts2022_get_rf_strength, +}; + +static int m88ts2022_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct m88ts2022_config *cfg = client->dev.platform_data; + struct dvb_frontend *fe = cfg->fe; + struct m88ts2022_priv *priv; + int ret; + u8 chip_id, u8tmp; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + ret = -ENOMEM; + dev_err(&client->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME); + goto err; + } + + memcpy(&priv->cfg, cfg, sizeof(struct m88ts2022_config)); + priv->client = client; + + /* check if the tuner is there */ + ret = m88ts2022_rd_reg(priv, 0x00, &u8tmp); + if (ret) + goto err; + + if ((u8tmp & 0x03) == 0x00) { + ret = m88ts2022_wr_reg(priv, 0x00, 0x01); + if (ret < 0) + goto err; + + usleep_range(2000, 50000); + } + + ret = m88ts2022_wr_reg(priv, 0x00, 0x03); + if (ret) + goto err; + + usleep_range(2000, 50000); + + ret = m88ts2022_rd_reg(priv, 0x00, &chip_id); + if (ret) + goto err; + + dev_dbg(&priv->client->dev, "%s: chip_id=%02x\n", __func__, chip_id); + + switch (chip_id) { + case 0xc3: + case 0x83: + break; + default: + goto err; + } + + switch (priv->cfg.clock_out) { + case M88TS2022_CLOCK_OUT_DISABLED: + u8tmp = 0x60; + break; + case M88TS2022_CLOCK_OUT_ENABLED: + u8tmp = 0x70; + ret = m88ts2022_wr_reg(priv, 0x05, priv->cfg.clock_out_div); + if (ret) + goto err; + break; + case M88TS2022_CLOCK_OUT_ENABLED_XTALOUT: + u8tmp = 0x6c; + break; + default: + goto err; + } + + ret = m88ts2022_wr_reg(priv, 0x42, u8tmp); + if (ret) + goto err; + + if (priv->cfg.loop_through) + u8tmp = 0xec; + else + u8tmp = 0x6c; + + ret = m88ts2022_wr_reg(priv, 0x62, u8tmp); + if (ret) + goto err; + + /* sleep */ + ret = m88ts2022_wr_reg(priv, 0x00, 0x00); + if (ret) + goto err; + + dev_info(&priv->client->dev, + "%s: Montage M88TS2022 successfully identified\n", + KBUILD_MODNAME); + + fe->tuner_priv = priv; + memcpy(&fe->ops.tuner_ops, &m88ts2022_tuner_ops, + sizeof(struct dvb_tuner_ops)); + + i2c_set_clientdata(client, priv); + return 0; +err: + dev_dbg(&client->dev, "%s: failed=%d\n", __func__, ret); + kfree(priv); + return ret; +} + +static int m88ts2022_remove(struct i2c_client *client) +{ + struct m88ts2022_priv *priv = i2c_get_clientdata(client); + struct dvb_frontend *fe = priv->cfg.fe; + dev_dbg(&client->dev, "%s:\n", __func__); + + memset(&fe->ops.tuner_ops, 0, sizeof(struct dvb_tuner_ops)); + fe->tuner_priv = NULL; + kfree(priv); + + return 0; +} + +static const struct i2c_device_id m88ts2022_id[] = { + {"m88ts2022", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, m88ts2022_id); + +static struct i2c_driver m88ts2022_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "m88ts2022", + }, + .probe = m88ts2022_probe, + .remove = m88ts2022_remove, + .id_table = m88ts2022_id, +}; + +module_i2c_driver(m88ts2022_driver); + +MODULE_DESCRIPTION("Montage M88TS2022 silicon tuner driver"); +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/tuners/m88ts2022.h b/drivers/media/tuners/m88ts2022.h new file mode 100644 index 000000000000..659fa1b1633a --- /dev/null +++ b/drivers/media/tuners/m88ts2022.h @@ -0,0 +1,54 @@ +/* + * Montage M88TS2022 silicon tuner 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. + */ + +#ifndef M88TS2022_H +#define M88TS2022_H + +#include "dvb_frontend.h" + +struct m88ts2022_config { + /* + * clock + * 16000000 - 32000000 + */ + u32 clock; + + /* + * RF loop-through + */ + u8 loop_through:1; + + /* + * clock output + */ +#define M88TS2022_CLOCK_OUT_DISABLED 0 +#define M88TS2022_CLOCK_OUT_ENABLED 1 +#define M88TS2022_CLOCK_OUT_ENABLED_XTALOUT 2 + u8 clock_out:2; + + /* + * clock output divider + * 1 - 31 + */ + u8 clock_out_div:5; + + /* + * pointer to DVB frontend + */ + struct dvb_frontend *fe; +}; + +#endif diff --git a/drivers/media/tuners/m88ts2022_priv.h b/drivers/media/tuners/m88ts2022_priv.h new file mode 100644 index 000000000000..0363dd866a2d --- /dev/null +++ b/drivers/media/tuners/m88ts2022_priv.h @@ -0,0 +1,34 @@ +/* + * Montage M88TS2022 silicon tuner 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. + */ + +#ifndef M88TS2022_PRIV_H +#define M88TS2022_PRIV_H + +#include "m88ts2022.h" + +struct m88ts2022_priv { + struct m88ts2022_config cfg; + struct i2c_client *client; + struct dvb_frontend *fe; + u32 frequency_khz; +}; + +struct m88ts2022_reg_val { + u8 reg; + u8 val; +}; + +#endif diff --git a/drivers/media/tuners/tuner-xc2028.c b/drivers/media/tuners/tuner-xc2028.c index 4be5cf808a40..cca508d4aafb 100644 --- a/drivers/media/tuners/tuner-xc2028.c +++ b/drivers/media/tuners/tuner-xc2028.c @@ -134,15 +134,6 @@ struct xc2028_data { _rc; \ }) -#define i2c_rcv(priv, buf, size) ({ \ - int _rc; \ - _rc = tuner_i2c_xfer_recv(&priv->i2c_props, buf, size); \ - if (size != _rc) \ - tuner_err("i2c input error: rc = %d (should be %d)\n", \ - _rc, (int)size); \ - _rc; \ -}) - #define i2c_send_recv(priv, obuf, osize, ibuf, isize) ({ \ int _rc; \ _rc = tuner_i2c_xfer_send_recv(&priv->i2c_props, obuf, osize, \ @@ -276,6 +267,7 @@ static int check_device_status(struct xc2028_data *priv) case XC2028_WAITING_FIRMWARE: return -EAGAIN; case XC2028_ACTIVE: + return 1; case XC2028_SLEEP: return 0; case XC2028_NODEV: @@ -718,6 +710,8 @@ static int load_scode(struct dvb_frontend *fe, unsigned int type, return 0; } +static int xc2028_sleep(struct dvb_frontend *fe); + static int check_firmware(struct dvb_frontend *fe, unsigned int type, v4l2_std_id std, __u16 int_freq) { @@ -890,7 +884,7 @@ read_not_reliable: return 0; fail: - priv->state = XC2028_SLEEP; + priv->state = XC2028_NO_FIRMWARE; memset(&priv->cur_fw, 0, sizeof(priv->cur_fw)); if (retry_count < 8) { @@ -900,6 +894,9 @@ fail: goto retry; } + /* Firmware didn't load. Put the device to sleep */ + xc2028_sleep(fe); + if (rc == -ENOENT) rc = -EINVAL; return rc; @@ -917,6 +914,12 @@ static int xc2028_signal(struct dvb_frontend *fe, u16 *strength) if (rc < 0) return rc; + /* If the device is sleeping, no channel is tuned */ + if (!rc) { + *strength = 0; + return 0; + } + mutex_lock(&priv->lock); /* Sync Lock Indicator */ @@ -964,6 +967,12 @@ static int xc2028_get_afc(struct dvb_frontend *fe, s32 *afc) if (rc < 0) return rc; + /* If the device is sleeping, no channel is tuned */ + if (!rc) { + *afc = 0; + return 0; + } + mutex_lock(&priv->lock); /* Sync Lock Indicator */ @@ -1281,6 +1290,10 @@ static int xc2028_sleep(struct dvb_frontend *fe) if (rc < 0) return rc; + /* Device is already in sleep mode */ + if (!rc) + return 0; + /* Avoid firmware reload on slow devices or if PM disabled */ if (no_poweroff || priv->ctrl.disable_power_mgmt) return 0; @@ -1298,7 +1311,8 @@ static int xc2028_sleep(struct dvb_frontend *fe) else rc = send_seq(priv, {0x80, XREG_POWER_DOWN, 0x00, 0x00}); - priv->state = XC2028_SLEEP; + if (rc >= 0) + priv->state = XC2028_SLEEP; mutex_unlock(&priv->lock); @@ -1366,7 +1380,7 @@ static void load_firmware_cb(const struct firmware *fw, if (rc < 0) return; - priv->state = XC2028_SLEEP; + priv->state = XC2028_ACTIVE; } static int xc2028_set_config(struct dvb_frontend *fe, void *priv_cfg) diff --git a/drivers/media/usb/Kconfig b/drivers/media/usb/Kconfig index cfe8056b91aa..39d824e2bb69 100644 --- a/drivers/media/usb/Kconfig +++ b/drivers/media/usb/Kconfig @@ -17,7 +17,6 @@ source "drivers/media/usb/cpia2/Kconfig" source "drivers/media/usb/zr364xx/Kconfig" source "drivers/media/usb/stkwebcam/Kconfig" source "drivers/media/usb/s2255/Kconfig" -source "drivers/media/usb/sn9c102/Kconfig" source "drivers/media/usb/usbtv/Kconfig" endif diff --git a/drivers/media/usb/Makefile b/drivers/media/usb/Makefile index 0935f47497a6..7ac4b143dce8 100644 --- a/drivers/media/usb/Makefile +++ b/drivers/media/usb/Makefile @@ -10,7 +10,6 @@ obj-$(CONFIG_USB_VIDEO_CLASS) += uvc/ obj-$(CONFIG_USB_GSPCA) += gspca/ obj-$(CONFIG_USB_PWC) += pwc/ obj-$(CONFIG_VIDEO_CPIA2) += cpia2/ -obj-$(CONFIG_USB_SN9C102) += sn9c102/ obj-$(CONFIG_VIDEO_AU0828) += au0828/ obj-$(CONFIG_VIDEO_HDPVR) += hdpvr/ obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2/ diff --git a/drivers/media/usb/au0828/au0828-core.c b/drivers/media/usb/au0828/au0828-core.c index bd9d19a73efd..ab45a6f9dcc9 100644 --- a/drivers/media/usb/au0828/au0828-core.c +++ b/drivers/media/usb/au0828/au0828-core.c @@ -173,9 +173,8 @@ static int au0828_usb_probe(struct usb_interface *interface, const struct usb_device_id *id) { int ifnum; -#ifdef CONFIG_VIDEO_AU0828_V4L2 - int retval; -#endif + int retval = 0; + struct au0828_dev *dev; struct usb_device *usbdev = interface_to_usbdev(interface); @@ -257,7 +256,11 @@ static int au0828_usb_probe(struct usb_interface *interface, #endif /* Digital TV */ - au0828_dvb_register(dev); + retval = au0828_dvb_register(dev); + if (retval) + pr_err("%s() au0282_dev_register failed\n", + __func__); + /* Store the pointer to the au0828_dev so it can be accessed in au0828_usb_disconnect */ @@ -268,7 +271,7 @@ static int au0828_usb_probe(struct usb_interface *interface, mutex_unlock(&dev->lock); - return 0; + return retval; } static struct usb_driver au0828_usb_driver = { diff --git a/drivers/media/usb/au0828/au0828-dvb.c b/drivers/media/usb/au0828/au0828-dvb.c index 9a6f15613a38..4ae8b1074649 100644 --- a/drivers/media/usb/au0828/au0828-dvb.c +++ b/drivers/media/usb/au0828/au0828-dvb.c @@ -33,6 +33,10 @@ #include "mxl5007t.h" #include "tda18271.h" +static int preallocate_big_buffers; +module_param_named(preallocate_big_buffers, preallocate_big_buffers, int, 0644); +MODULE_PARM_DESC(preallocate_big_buffers, "Preallocate the larger transfer buffers at module load time"); + DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); #define _AU0828_BULKPIPE 0x83 @@ -153,9 +157,13 @@ static int stop_urb_transfer(struct au0828_dev *dev) dev->urb_streaming = 0; for (i = 0; i < URB_COUNT; i++) { - usb_kill_urb(dev->urbs[i]); - kfree(dev->urbs[i]->transfer_buffer); - usb_free_urb(dev->urbs[i]); + if (dev->urbs[i]) { + usb_kill_urb(dev->urbs[i]); + if (!preallocate_big_buffers) + kfree(dev->urbs[i]->transfer_buffer); + + usb_free_urb(dev->urbs[i]); + } } return 0; @@ -181,10 +189,18 @@ static int start_urb_transfer(struct au0828_dev *dev) purb = dev->urbs[i]; - purb->transfer_buffer = kzalloc(URB_BUFSIZE, GFP_KERNEL); + if (preallocate_big_buffers) + purb->transfer_buffer = dev->dig_transfer_buffer[i]; + else + purb->transfer_buffer = kzalloc(URB_BUFSIZE, + GFP_KERNEL); + if (!purb->transfer_buffer) { usb_free_urb(purb); dev->urbs[i] = NULL; + printk(KERN_ERR + "%s: failed big buffer allocation, err = %d\n", + __func__, ret); goto err; } @@ -217,6 +233,27 @@ err: return ret; } +static void au0828_start_transport(struct au0828_dev *dev) +{ + au0828_write(dev, 0x608, 0x90); + au0828_write(dev, 0x609, 0x72); + au0828_write(dev, 0x60a, 0x71); + au0828_write(dev, 0x60b, 0x01); + +} + +static void au0828_stop_transport(struct au0828_dev *dev, int full_stop) +{ + if (full_stop) { + au0828_write(dev, 0x608, 0x00); + au0828_write(dev, 0x609, 0x00); + au0828_write(dev, 0x60a, 0x00); + } + au0828_write(dev, 0x60b, 0x00); +} + + + static int au0828_dvb_start_feed(struct dvb_demux_feed *feed) { struct dvb_demux *demux = feed->demux; @@ -231,13 +268,17 @@ static int au0828_dvb_start_feed(struct dvb_demux_feed *feed) if (dvb) { mutex_lock(&dvb->lock); + dvb->start_count++; + dprintk(1, "%s(), start_count: %d, stop_count: %d\n", __func__, + dvb->start_count, dvb->stop_count); if (dvb->feeding++ == 0) { /* Start transport */ - au0828_write(dev, 0x608, 0x90); - au0828_write(dev, 0x609, 0x72); - au0828_write(dev, 0x60a, 0x71); - au0828_write(dev, 0x60b, 0x01); + au0828_start_transport(dev); ret = start_urb_transfer(dev); + if (ret < 0) { + au0828_stop_transport(dev, 0); + dvb->feeding--; /* We ran out of memory... */ + } } mutex_unlock(&dvb->lock); } @@ -256,10 +297,16 @@ static int au0828_dvb_stop_feed(struct dvb_demux_feed *feed) if (dvb) { mutex_lock(&dvb->lock); - if (--dvb->feeding == 0) { - /* Stop transport */ - ret = stop_urb_transfer(dev); - au0828_write(dev, 0x60b, 0x00); + dvb->stop_count++; + dprintk(1, "%s(), start_count: %d, stop_count: %d\n", __func__, + dvb->start_count, dvb->stop_count); + if (dvb->feeding > 0) { + dvb->feeding--; + if (dvb->feeding == 0) { + /* Stop transport */ + ret = stop_urb_transfer(dev); + au0828_stop_transport(dev, 0); + } } mutex_unlock(&dvb->lock); } @@ -282,16 +329,10 @@ static void au0828_restart_dvb_streaming(struct work_struct *work) /* Stop transport */ stop_urb_transfer(dev); - au0828_write(dev, 0x608, 0x00); - au0828_write(dev, 0x609, 0x00); - au0828_write(dev, 0x60a, 0x00); - au0828_write(dev, 0x60b, 0x00); + au0828_stop_transport(dev, 1); /* Start transport */ - au0828_write(dev, 0x608, 0x90); - au0828_write(dev, 0x609, 0x72); - au0828_write(dev, 0x60a, 0x71); - au0828_write(dev, 0x60b, 0x01); + au0828_start_transport(dev); start_urb_transfer(dev); mutex_unlock(&dvb->lock); @@ -304,6 +345,23 @@ static int dvb_register(struct au0828_dev *dev) dprintk(1, "%s()\n", __func__); + if (preallocate_big_buffers) { + int i; + for (i = 0; i < URB_COUNT; i++) { + dev->dig_transfer_buffer[i] = kzalloc(URB_BUFSIZE, + GFP_KERNEL); + + if (!dev->dig_transfer_buffer[i]) { + result = -ENOMEM; + + printk(KERN_ERR + "%s: failed buffer allocation (errno = %d)\n", + DRIVER_NAME, result); + goto fail_adapter; + } + } + } + INIT_WORK(&dev->restart_streaming, au0828_restart_dvb_streaming); /* register adapter */ @@ -375,6 +433,9 @@ static int dvb_register(struct au0828_dev *dev) /* register network adapter */ dvb_net_init(&dvb->adapter, &dvb->net, &dvb->demux.dmx); + + dvb->start_count = 0; + dvb->stop_count = 0; return 0; fail_fe_conn: @@ -391,6 +452,13 @@ fail_frontend: dvb_frontend_detach(dvb->frontend); dvb_unregister_adapter(&dvb->adapter); fail_adapter: + + if (preallocate_big_buffers) { + int i; + for (i = 0; i < URB_COUNT; i++) + kfree(dev->dig_transfer_buffer[i]); + } + return result; } @@ -411,6 +479,14 @@ void au0828_dvb_unregister(struct au0828_dev *dev) dvb_unregister_frontend(dvb->frontend); dvb_frontend_detach(dvb->frontend); dvb_unregister_adapter(&dvb->adapter); + + if (preallocate_big_buffers) { + int i; + for (i = 0; i < URB_COUNT; i++) + kfree(dev->dig_transfer_buffer[i]); + } + + } /* All the DVB attach calls go here, this function get's modified diff --git a/drivers/media/usb/au0828/au0828.h b/drivers/media/usb/au0828/au0828.h index ef1f57f22be7..5439772c1551 100644 --- a/drivers/media/usb/au0828/au0828.h +++ b/drivers/media/usb/au0828/au0828.h @@ -102,6 +102,8 @@ struct au0828_dvb { struct dmx_frontend fe_mem; struct dvb_net net; int feeding; + int start_count; + int stop_count; }; enum au0828_stream_state { @@ -260,6 +262,10 @@ struct au0828_dev { /* USB / URB Related */ int urb_streaming; struct urb *urbs[URB_COUNT]; + + /* Preallocated transfer digital transfer buffers */ + + char *dig_transfer_buffer[URB_COUNT]; }; /* ----------------------------------------------------------- */ diff --git a/drivers/media/usb/cx231xx/Kconfig b/drivers/media/usb/cx231xx/Kconfig index 86feeeaf61c2..f14c5e89a567 100644 --- a/drivers/media/usb/cx231xx/Kconfig +++ b/drivers/media/usb/cx231xx/Kconfig @@ -45,6 +45,8 @@ config VIDEO_CX231XX_DVB select MEDIA_TUNER_XC5000 if MEDIA_SUBDRV_AUTOSELECT select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT select DVB_MB86A20S if MEDIA_SUBDRV_AUTOSELECT + select DVB_LGDT3305 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA18271C2DD if MEDIA_SUBDRV_AUTOSELECT ---help--- This adds support for DVB cards based on the diff --git a/drivers/media/usb/cx231xx/cx231xx-cards.c b/drivers/media/usb/cx231xx/cx231xx-cards.c index 528cce958a82..2ee03e4ddd86 100644 --- a/drivers/media/usb/cx231xx/cx231xx-cards.c +++ b/drivers/media/usb/cx231xx/cx231xx-cards.c @@ -709,6 +709,8 @@ const unsigned int cx231xx_bcount = ARRAY_SIZE(cx231xx_boards); /* table of devices that work with this driver */ struct usb_device_id cx231xx_id_table[] = { + {USB_DEVICE(0x1D19, 0x6109), + .driver_info = CX231XX_BOARD_PV_XCAPTURE_USB}, {USB_DEVICE(0x0572, 0x5A3C), .driver_info = CX231XX_BOARD_UNKNOWN}, {USB_DEVICE(0x0572, 0x58A2), diff --git a/drivers/media/usb/cx231xx/cx231xx-i2c.c b/drivers/media/usb/cx231xx/cx231xx-i2c.c index 96a5a0965399..7c0f797f1057 100644 --- a/drivers/media/usb/cx231xx/cx231xx-i2c.c +++ b/drivers/media/usb/cx231xx/cx231xx-i2c.c @@ -371,9 +371,9 @@ static int cx231xx_i2c_xfer(struct i2c_adapter *i2c_adap, mutex_lock(&dev->i2c_lock); for (i = 0; i < num; i++) { - addr = msgs[i].addr >> 1; + addr = msgs[i].addr; - dprintk2(2, "%s %s addr=%x len=%d:", + dprintk2(2, "%s %s addr=0x%x len=%d:", (msgs[i].flags & I2C_M_RD) ? "read" : "write", i == num - 1 ? "stop" : "nonstop", addr, msgs[i].len); if (!msgs[i].len) { @@ -390,32 +390,41 @@ static int cx231xx_i2c_xfer(struct i2c_adapter *i2c_adap, rc = cx231xx_i2c_recv_bytes(i2c_adap, &msgs[i]); if (i2c_debug >= 2) { for (byte = 0; byte < msgs[i].len; byte++) - printk(" %02x", msgs[i].buf[byte]); + printk(KERN_CONT " %02x", msgs[i].buf[byte]); } } else if (i + 1 < num && (msgs[i + 1].flags & I2C_M_RD) && msgs[i].addr == msgs[i + 1].addr && (msgs[i].len <= 2) && (bus->nr < 3)) { + /* write bytes */ + if (i2c_debug >= 2) { + for (byte = 0; byte < msgs[i].len; byte++) + printk(KERN_CONT " %02x", msgs[i].buf[byte]); + printk(KERN_CONT "\n"); + } /* read bytes */ + dprintk2(2, "plus %s %s addr=0x%x len=%d:", + (msgs[i+1].flags & I2C_M_RD) ? "read" : "write", + i+1 == num - 1 ? "stop" : "nonstop", addr, msgs[i+1].len); rc = cx231xx_i2c_recv_bytes_with_saddr(i2c_adap, &msgs[i], &msgs[i + 1]); if (i2c_debug >= 2) { - for (byte = 0; byte < msgs[i].len; byte++) - printk(" %02x", msgs[i].buf[byte]); + for (byte = 0; byte < msgs[i+1].len; byte++) + printk(KERN_CONT " %02x", msgs[i+1].buf[byte]); } i++; } else { /* write bytes */ if (i2c_debug >= 2) { for (byte = 0; byte < msgs[i].len; byte++) - printk(" %02x", msgs[i].buf[byte]); + printk(KERN_CONT " %02x", msgs[i].buf[byte]); } rc = cx231xx_i2c_send_bytes(i2c_adap, &msgs[i]); } if (rc < 0) goto err; if (i2c_debug >= 2) - printk("\n"); + printk(KERN_CONT "\n"); } mutex_unlock(&dev->i2c_lock); return num; diff --git a/drivers/media/usb/dvb-usb-v2/anysee.c b/drivers/media/usb/dvb-usb-v2/anysee.c index 90cfa35ef6e6..eeab79bdd2aa 100644 --- a/drivers/media/usb/dvb-usb-v2/anysee.c +++ b/drivers/media/usb/dvb-usb-v2/anysee.c @@ -442,6 +442,7 @@ static struct cxd2820r_config anysee_cxd2820r_config = { * IOD[0] ZL10353 1=enabled * IOE[0] tuner 0=enabled * tuner is behind ZL10353 I2C-gate + * tuner is behind TDA10023 I2C-gate * * E7 TC VID=1c73 PID=861f HW=18 FW=0.7 AMTCI=0.5 "anysee-E7TC(LP)" * PCB: 508TC (rev0.6) @@ -956,7 +957,7 @@ static int anysee_tuner_attach(struct dvb_usb_adapter *adap) if (fe && adap->fe[1]) { /* attach tuner for 2nd FE */ - fe = dvb_attach(dvb_pll_attach, adap->fe[0], + fe = dvb_attach(dvb_pll_attach, adap->fe[1], (0xc0 >> 1), &d->i2c_adap, DVB_PLL_SAMSUNG_DTOS403IH102A); } diff --git a/drivers/media/usb/dvb-usb-v2/az6007.c b/drivers/media/usb/dvb-usb-v2/az6007.c index 44c64ef361bf..c1051c347744 100644 --- a/drivers/media/usb/dvb-usb-v2/az6007.c +++ b/drivers/media/usb/dvb-usb-v2/az6007.c @@ -68,6 +68,19 @@ static struct drxk_config terratec_h7_drxk = { .microcode_name = "dvb-usb-terratec-h7-drxk.fw", }; +static struct drxk_config cablestar_hdci_drxk = { + .adr = 0x29, + .parallel_ts = true, + .dynamic_clk = true, + .single_master = true, + .enable_merr_cfg = true, + .no_i2c_bridge = false, + .chunk_size = 64, + .mpeg_out_clk_strength = 0x02, + .qam_demod_parameter_count = 2, + .microcode_name = "dvb-usb-technisat-cablestar-hdci-drxk.fw", +}; + static int drxk_gate_ctrl(struct dvb_frontend *fe, int enable) { struct az6007_device_state *st = fe_to_priv(fe); @@ -630,6 +643,27 @@ static int az6007_frontend_attach(struct dvb_usb_adapter *adap) return 0; } +static int az6007_cablestar_hdci_frontend_attach(struct dvb_usb_adapter *adap) +{ + struct az6007_device_state *st = adap_to_priv(adap); + struct dvb_usb_device *d = adap_to_d(adap); + + pr_debug("attaching demod drxk\n"); + + adap->fe[0] = dvb_attach(drxk_attach, &cablestar_hdci_drxk, + &d->i2c_adap); + if (!adap->fe[0]) + return -EINVAL; + + adap->fe[0]->sec_priv = adap; + st->gate_ctrl = adap->fe[0]->ops.i2c_gate_ctrl; + adap->fe[0]->ops.i2c_gate_ctrl = drxk_gate_ctrl; + + az6007_ci_init(adap); + + return 0; +} + static int az6007_tuner_attach(struct dvb_usb_adapter *adap) { struct dvb_usb_device *d = adap_to_d(adap); @@ -868,6 +902,29 @@ static struct dvb_usb_device_properties az6007_props = { } }; +static struct dvb_usb_device_properties az6007_cablestar_hdci_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .firmware = AZ6007_FIRMWARE, + + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct az6007_device_state), + .i2c_algo = &az6007_i2c_algo, + .tuner_attach = az6007_tuner_attach, + .frontend_attach = az6007_cablestar_hdci_frontend_attach, + .streaming_ctrl = az6007_streaming_ctrl, +/* ditch get_rc_config as it can't work (TS35 remote, I believe it's rc5) */ + .get_rc_config = NULL, + .read_mac_address = az6007_read_mac_addr, + .download_firmware = az6007_download_firmware, + .identify_state = az6007_identify_state, + .power_ctrl = az6007_power_ctrl, + .num_adapters = 1, + .adapter = { + { .stream = DVB_USB_STREAM_BULK(0x02, 10, 4096), } + } +}; + static struct usb_device_id az6007_usb_table[] = { {DVB_USB_DEVICE(USB_VID_AZUREWAVE, USB_PID_AZUREWAVE_6007, &az6007_props, "Azurewave 6007", RC_MAP_EMPTY)}, @@ -875,6 +932,8 @@ static struct usb_device_id az6007_usb_table[] = { &az6007_props, "Terratec H7", RC_MAP_NEC_TERRATEC_CINERGY_XS)}, {DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_H7_2, &az6007_props, "Terratec H7", RC_MAP_NEC_TERRATEC_CINERGY_XS)}, + {DVB_USB_DEVICE(USB_VID_TECHNISAT, USB_PID_TECHNISAT_USB2_CABLESTAR_HDCI, + &az6007_cablestar_hdci_props, "Technisat CableStar Combo HD CI", RC_MAP_EMPTY)}, {0}, }; diff --git a/drivers/media/usb/dvb-usb-v2/ec168.c b/drivers/media/usb/dvb-usb-v2/ec168.c index 5c68f3918bc8..0c2b377704ff 100644 --- a/drivers/media/usb/dvb-usb-v2/ec168.c +++ b/drivers/media/usb/dvb-usb-v2/ec168.c @@ -170,7 +170,7 @@ static int ec168_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], error: mutex_unlock(&d->i2c_mutex); - return i; + return ret; } static u32 ec168_i2c_func(struct i2c_adapter *adapter) diff --git a/drivers/media/usb/dvb-usb-v2/it913x.c b/drivers/media/usb/dvb-usb-v2/it913x.c index 1cb6899cf797..fe95a586dd5d 100644 --- a/drivers/media/usb/dvb-usb-v2/it913x.c +++ b/drivers/media/usb/dvb-usb-v2/it913x.c @@ -799,6 +799,9 @@ static const struct usb_device_id it913x_id_table[] = { { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_CTVDIGDUAL_V2, &it913x_properties, "Digital Dual TV Receiver CTVDIGDUAL_V2", RC_MAP_IT913X_V1) }, + { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_H335, + &it913x_properties, "Avermedia H335", + RC_MAP_IT913X_V2) }, {} /* Terminating entry */ }; diff --git a/drivers/media/usb/dvb-usb-v2/rtl28xxu.c b/drivers/media/usb/dvb-usb-v2/rtl28xxu.c index ecca03667f98..fda5c64ba0e8 100644 --- a/drivers/media/usb/dvb-usb-v2/rtl28xxu.c +++ b/drivers/media/usb/dvb-usb-v2/rtl28xxu.c @@ -1407,6 +1407,8 @@ static const struct usb_device_id rtl28xxu_id_table[] = { &rtl2832u_props, "Dexatek DK DVB-T Dongle", NULL) }, { DVB_USB_DEVICE(USB_VID_LEADTEK, 0x6680, &rtl2832u_props, "DigitalNow Quad DVB-T Receiver", NULL) }, + { DVB_USB_DEVICE(USB_VID_LEADTEK, USB_PID_WINFAST_DTV_DONGLE_MINID, + &rtl2832u_props, "Leadtek Winfast DTV Dongle Mini D", NULL) }, { DVB_USB_DEVICE(USB_VID_TERRATEC, 0x00d3, &rtl2832u_props, "TerraTec Cinergy T Stick RC (Rev. 3)", NULL) }, { DVB_USB_DEVICE(USB_VID_DEXATEK, 0x1102, diff --git a/drivers/media/usb/dvb-usb/cxusb.c b/drivers/media/usb/dvb-usb/cxusb.c index 20e345d9fe8f..a1c641e18362 100644 --- a/drivers/media/usb/dvb-usb/cxusb.c +++ b/drivers/media/usb/dvb-usb/cxusb.c @@ -149,6 +149,7 @@ static int cxusb_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], int num) { struct dvb_usb_device *d = i2c_get_adapdata(adap); + int ret; int i; if (mutex_lock_interruptible(&d->i2c_mutex) < 0) @@ -173,7 +174,8 @@ static int cxusb_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], if (1 + msg[i].len > sizeof(ibuf)) { warn("i2c rd: len=%d is too big!\n", msg[i].len); - return -EOPNOTSUPP; + ret = -EOPNOTSUPP; + goto unlock; } obuf[0] = 0; obuf[1] = msg[i].len; @@ -193,12 +195,14 @@ static int cxusb_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], if (3 + msg[i].len > sizeof(obuf)) { warn("i2c wr: len=%d is too big!\n", msg[i].len); - return -EOPNOTSUPP; + ret = -EOPNOTSUPP; + goto unlock; } if (1 + msg[i + 1].len > sizeof(ibuf)) { warn("i2c rd: len=%d is too big!\n", msg[i + 1].len); - return -EOPNOTSUPP; + ret = -EOPNOTSUPP; + goto unlock; } obuf[0] = msg[i].len; obuf[1] = msg[i+1].len; @@ -223,7 +227,8 @@ static int cxusb_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], if (2 + msg[i].len > sizeof(obuf)) { warn("i2c wr: len=%d is too big!\n", msg[i].len); - return -EOPNOTSUPP; + ret = -EOPNOTSUPP; + goto unlock; } obuf[0] = msg[i].addr; obuf[1] = msg[i].len; @@ -237,8 +242,14 @@ static int cxusb_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], } } + if (i == num) + ret = num; + else + ret = -EREMOTEIO; + +unlock: mutex_unlock(&d->i2c_mutex); - return i == num ? num : -EREMOTEIO; + return ret; } static u32 cxusb_i2c_func(struct i2c_adapter *adapter) diff --git a/drivers/media/usb/dvb-usb/dw2102.c b/drivers/media/usb/dvb-usb/dw2102.c index c1a63b2a6baa..ae0f56a32e4d 100644 --- a/drivers/media/usb/dvb-usb/dw2102.c +++ b/drivers/media/usb/dvb-usb/dw2102.c @@ -2,7 +2,7 @@ * DVBWorld DVB-S 2101, 2102, DVB-S2 2104, DVB-C 3101, * TeVii S600, S630, S650, S660, S480, S421, S632 * Prof 1100, 7500, - * Geniatech SU3000 Cards + * Geniatech SU3000, T220 Cards * Copyright (C) 2008-2012 Igor M. Liplianin (liplianin@me.by) * * This program is free software; you can redistribute it and/or modify it @@ -29,6 +29,8 @@ #include "stb6100.h" #include "stb6100_proc.h" #include "m88rs2000.h" +#include "tda18271.h" +#include "cxd2820r.h" /* Max transfer size done by I2C transfer functions */ #define MAX_XFER_SIZE 64 @@ -110,11 +112,6 @@ "Please see linux/Documentation/dvb/ for more details " \ "on firmware-problems." -struct rc_map_dvb_usb_table_table { - struct rc_map_table *rc_keys; - int rc_keys_size; -}; - struct su3000_state { u8 initialized; }; @@ -129,12 +126,6 @@ module_param_named(debug, dvb_usb_dw2102_debug, int, 0644); MODULE_PARM_DESC(debug, "set debugging level (1=info 2=xfer 4=rc(or-able))." DVB_USB_DEBUG_STATUS); -/* keymaps */ -static int ir_keymap; -module_param_named(keymap, ir_keymap, int, 0644); -MODULE_PARM_DESC(keymap, "set keymap 0=default 1=dvbworld 2=tevii 3=tbs ..." - " 256=none"); - /* demod probe */ static int demod_probe = 1; module_param_named(demod, demod_probe, int, 0644); @@ -301,6 +292,7 @@ static int dw2102_serit_i2c_transfer(struct i2c_adapter *adap, static int dw2102_earda_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], int num) { struct dvb_usb_device *d = i2c_get_adapdata(adap); + int ret; if (!d) return -ENODEV; @@ -316,7 +308,8 @@ static int dw2102_earda_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg ms if (2 + msg[1].len > sizeof(ibuf)) { warn("i2c rd: len=%d is too big!\n", msg[1].len); - return -EOPNOTSUPP; + ret = -EOPNOTSUPP; + goto unlock; } obuf[0] = msg[0].addr << 1; @@ -340,7 +333,8 @@ static int dw2102_earda_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg ms if (2 + msg[0].len > sizeof(obuf)) { warn("i2c wr: len=%d is too big!\n", msg[1].len); - return -EOPNOTSUPP; + ret = -EOPNOTSUPP; + goto unlock; } obuf[0] = msg[0].addr << 1; @@ -357,7 +351,8 @@ static int dw2102_earda_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg ms if (2 + msg[0].len > sizeof(obuf)) { warn("i2c wr: len=%d is too big!\n", msg[1].len); - return -EOPNOTSUPP; + ret = -EOPNOTSUPP; + goto unlock; } obuf[0] = msg[0].addr << 1; @@ -386,15 +381,17 @@ static int dw2102_earda_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg ms break; } + ret = num; +unlock: mutex_unlock(&d->i2c_mutex); - return num; + return ret; } static int dw2104_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], int num) { struct dvb_usb_device *d = i2c_get_adapdata(adap); - int len, i, j; + int len, i, j, ret; if (!d) return -ENODEV; @@ -430,7 +427,8 @@ static int dw2104_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], i if (2 + msg[j].len > sizeof(ibuf)) { warn("i2c rd: len=%d is too big!\n", msg[j].len); - return -EOPNOTSUPP; + ret = -EOPNOTSUPP; + goto unlock; } dw210x_op_rw(d->udev, 0xc3, @@ -466,7 +464,8 @@ static int dw2104_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], i if (2 + msg[j].len > sizeof(obuf)) { warn("i2c wr: len=%d is too big!\n", msg[j].len); - return -EOPNOTSUPP; + ret = -EOPNOTSUPP; + goto unlock; } obuf[0] = msg[j].addr << 1; @@ -481,15 +480,18 @@ static int dw2104_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], i } } + ret = num; +unlock: mutex_unlock(&d->i2c_mutex); - return num; + return ret; } static int dw3101_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], int num) { struct dvb_usb_device *d = i2c_get_adapdata(adap); + int ret; int i; if (!d) @@ -506,7 +508,8 @@ static int dw3101_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], if (2 + msg[1].len > sizeof(ibuf)) { warn("i2c rd: len=%d is too big!\n", msg[1].len); - return -EOPNOTSUPP; + ret = -EOPNOTSUPP; + goto unlock; } obuf[0] = msg[0].addr << 1; obuf[1] = msg[0].len; @@ -530,7 +533,8 @@ static int dw3101_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], if (2 + msg[0].len > sizeof(obuf)) { warn("i2c wr: len=%d is too big!\n", msg[0].len); - return -EOPNOTSUPP; + ret = -EOPNOTSUPP; + goto unlock; } obuf[0] = msg[0].addr << 1; obuf[1] = msg[0].len; @@ -556,9 +560,11 @@ static int dw3101_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], msg[i].flags == 0 ? ">>>" : "<<<"); debug_dump(msg[i].buf, msg[i].len, deb_xfer); } + ret = num; +unlock: mutex_unlock(&d->i2c_mutex); - return num; + return ret; } static int s6x0_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], @@ -566,7 +572,7 @@ static int s6x0_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], { struct dvb_usb_device *d = i2c_get_adapdata(adap); struct usb_device *udev; - int len, i, j; + int len, i, j, ret; if (!d) return -ENODEV; @@ -618,7 +624,8 @@ static int s6x0_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], if (msg[j].len > sizeof(ibuf)) { warn("i2c rd: len=%d is too big!\n", msg[j].len); - return -EOPNOTSUPP; + ret = -EOPNOTSUPP; + goto unlock; } dw210x_op_rw(d->udev, 0x91, 0, 0, @@ -652,7 +659,8 @@ static int s6x0_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], if (2 + msg[j].len > sizeof(obuf)) { warn("i2c wr: len=%d is too big!\n", msg[j].len); - return -EOPNOTSUPP; + ret = -EOPNOTSUPP; + goto unlock; } obuf[0] = msg[j + 1].len; @@ -671,7 +679,8 @@ static int s6x0_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], if (2 + msg[j].len > sizeof(obuf)) { warn("i2c wr: len=%d is too big!\n", msg[j].len); - return -EOPNOTSUPP; + ret = -EOPNOTSUPP; + goto unlock; } obuf[0] = msg[j].len + 1; obuf[1] = (msg[j].addr << 1); @@ -685,9 +694,11 @@ static int s6x0_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], } } } + ret = num; +unlock: mutex_unlock(&d->i2c_mutex); - return num; + return ret; } static int su3000_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], @@ -1095,6 +1106,16 @@ static struct ds3000_config su3000_ds3000_config = { .set_lock_led = dw210x_led_ctrl, }; +static struct cxd2820r_config cxd2820r_config = { + .i2c_address = 0x6c, /* (0xd8 >> 1) */ + .ts_mode = 0x38, +}; + +static struct tda18271_config tda18271_config = { + .output_opt = TDA18271_OUTPUT_LT_OFF, + .gate = TDA18271_GATE_DIGITAL, +}; + static u8 m88rs2000_inittab[] = { DEMOD_WRITE, 0x9a, 0x30, DEMOD_WRITE, 0x00, 0x01, @@ -1364,6 +1385,49 @@ static int su3000_frontend_attach(struct dvb_usb_adapter *d) return -EIO; } +static int t220_frontend_attach(struct dvb_usb_adapter *d) +{ + u8 obuf[3] = { 0xe, 0x80, 0 }; + u8 ibuf[] = { 0 }; + + if (dvb_usb_generic_rw(d->dev, obuf, 3, ibuf, 1, 0) < 0) + err("command 0x0e transfer failed."); + + obuf[0] = 0xe; + obuf[1] = 0x83; + obuf[2] = 0; + + if (dvb_usb_generic_rw(d->dev, obuf, 3, ibuf, 1, 0) < 0) + err("command 0x0e transfer failed."); + + msleep(100); + + obuf[0] = 0xe; + obuf[1] = 0x80; + obuf[2] = 1; + + if (dvb_usb_generic_rw(d->dev, obuf, 3, ibuf, 1, 0) < 0) + err("command 0x0e transfer failed."); + + obuf[0] = 0x51; + + if (dvb_usb_generic_rw(d->dev, obuf, 1, ibuf, 1, 0) < 0) + err("command 0x51 transfer failed."); + + d->fe_adap[0].fe = dvb_attach(cxd2820r_attach, &cxd2820r_config, + &d->dev->i2c_adap, NULL); + if (d->fe_adap[0].fe != NULL) { + if (dvb_attach(tda18271_attach, d->fe_adap[0].fe, 0x60, + &d->dev->i2c_adap, &tda18271_config)) { + info("Attached TDA18271HD/CXD2820R!\n"); + return 0; + } + } + + info("Failed to attach TDA18271HD/CXD2820R!\n"); + return -EIO; +} + static int m88rs2000_frontend_attach(struct dvb_usb_adapter *d) { u8 obuf[] = { 0x51 }; @@ -1404,174 +1468,29 @@ static int dw3101_tuner_attach(struct dvb_usb_adapter *adap) return 0; } -static struct rc_map_table rc_map_dw210x_table[] = { - { 0xf80a, KEY_POWER2 }, /*power*/ - { 0xf80c, KEY_MUTE }, /*mute*/ - { 0xf811, KEY_1 }, - { 0xf812, KEY_2 }, - { 0xf813, KEY_3 }, - { 0xf814, KEY_4 }, - { 0xf815, KEY_5 }, - { 0xf816, KEY_6 }, - { 0xf817, KEY_7 }, - { 0xf818, KEY_8 }, - { 0xf819, KEY_9 }, - { 0xf810, KEY_0 }, - { 0xf81c, KEY_CHANNELUP }, /*ch+*/ - { 0xf80f, KEY_CHANNELDOWN }, /*ch-*/ - { 0xf81a, KEY_VOLUMEUP }, /*vol+*/ - { 0xf80e, KEY_VOLUMEDOWN }, /*vol-*/ - { 0xf804, KEY_RECORD }, /*rec*/ - { 0xf809, KEY_FAVORITES }, /*fav*/ - { 0xf808, KEY_REWIND }, /*rewind*/ - { 0xf807, KEY_FASTFORWARD }, /*fast*/ - { 0xf80b, KEY_PAUSE }, /*pause*/ - { 0xf802, KEY_ESC }, /*cancel*/ - { 0xf803, KEY_TAB }, /*tab*/ - { 0xf800, KEY_UP }, /*up*/ - { 0xf81f, KEY_OK }, /*ok*/ - { 0xf801, KEY_DOWN }, /*down*/ - { 0xf805, KEY_CAMERA }, /*cap*/ - { 0xf806, KEY_STOP }, /*stop*/ - { 0xf840, KEY_ZOOM }, /*full*/ - { 0xf81e, KEY_TV }, /*tvmode*/ - { 0xf81b, KEY_LAST }, /*recall*/ -}; - -static struct rc_map_table rc_map_tevii_table[] = { - { 0xf80a, KEY_POWER }, - { 0xf80c, KEY_MUTE }, - { 0xf811, KEY_1 }, - { 0xf812, KEY_2 }, - { 0xf813, KEY_3 }, - { 0xf814, KEY_4 }, - { 0xf815, KEY_5 }, - { 0xf816, KEY_6 }, - { 0xf817, KEY_7 }, - { 0xf818, KEY_8 }, - { 0xf819, KEY_9 }, - { 0xf810, KEY_0 }, - { 0xf81c, KEY_MENU }, - { 0xf80f, KEY_VOLUMEDOWN }, - { 0xf81a, KEY_LAST }, - { 0xf80e, KEY_OPEN }, - { 0xf804, KEY_RECORD }, - { 0xf809, KEY_VOLUMEUP }, - { 0xf808, KEY_CHANNELUP }, - { 0xf807, KEY_PVR }, - { 0xf80b, KEY_TIME }, - { 0xf802, KEY_RIGHT }, - { 0xf803, KEY_LEFT }, - { 0xf800, KEY_UP }, - { 0xf81f, KEY_OK }, - { 0xf801, KEY_DOWN }, - { 0xf805, KEY_TUNER }, - { 0xf806, KEY_CHANNELDOWN }, - { 0xf840, KEY_PLAYPAUSE }, - { 0xf81e, KEY_REWIND }, - { 0xf81b, KEY_FAVORITES }, - { 0xf81d, KEY_BACK }, - { 0xf84d, KEY_FASTFORWARD }, - { 0xf844, KEY_EPG }, - { 0xf84c, KEY_INFO }, - { 0xf841, KEY_AB }, - { 0xf843, KEY_AUDIO }, - { 0xf845, KEY_SUBTITLE }, - { 0xf84a, KEY_LIST }, - { 0xf846, KEY_F1 }, - { 0xf847, KEY_F2 }, - { 0xf85e, KEY_F3 }, - { 0xf85c, KEY_F4 }, - { 0xf852, KEY_F5 }, - { 0xf85a, KEY_F6 }, - { 0xf856, KEY_MODE }, - { 0xf858, KEY_SWITCHVIDEOMODE }, -}; - -static struct rc_map_table rc_map_tbs_table[] = { - { 0xf884, KEY_POWER }, - { 0xf894, KEY_MUTE }, - { 0xf887, KEY_1 }, - { 0xf886, KEY_2 }, - { 0xf885, KEY_3 }, - { 0xf88b, KEY_4 }, - { 0xf88a, KEY_5 }, - { 0xf889, KEY_6 }, - { 0xf88f, KEY_7 }, - { 0xf88e, KEY_8 }, - { 0xf88d, KEY_9 }, - { 0xf892, KEY_0 }, - { 0xf896, KEY_CHANNELUP }, - { 0xf891, KEY_CHANNELDOWN }, - { 0xf893, KEY_VOLUMEUP }, - { 0xf88c, KEY_VOLUMEDOWN }, - { 0xf883, KEY_RECORD }, - { 0xf898, KEY_PAUSE }, - { 0xf899, KEY_OK }, - { 0xf89a, KEY_SHUFFLE }, - { 0xf881, KEY_UP }, - { 0xf890, KEY_LEFT }, - { 0xf882, KEY_RIGHT }, - { 0xf888, KEY_DOWN }, - { 0xf895, KEY_FAVORITES }, - { 0xf897, KEY_SUBTITLE }, - { 0xf89d, KEY_ZOOM }, - { 0xf89f, KEY_EXIT }, - { 0xf89e, KEY_MENU }, - { 0xf89c, KEY_EPG }, - { 0xf880, KEY_PREVIOUS }, - { 0xf89b, KEY_MODE } -}; +static int dw2102_rc_query(struct dvb_usb_device *d) +{ + u8 key[2]; + struct i2c_msg msg = { + .addr = DW2102_RC_QUERY, + .flags = I2C_M_RD, + .buf = key, + .len = 2 + }; -static struct rc_map_table rc_map_su3000_table[] = { - { 0x25, KEY_POWER }, /* right-bottom Red */ - { 0x0a, KEY_MUTE }, /* -/-- */ - { 0x01, KEY_1 }, - { 0x02, KEY_2 }, - { 0x03, KEY_3 }, - { 0x04, KEY_4 }, - { 0x05, KEY_5 }, - { 0x06, KEY_6 }, - { 0x07, KEY_7 }, - { 0x08, KEY_8 }, - { 0x09, KEY_9 }, - { 0x00, KEY_0 }, - { 0x20, KEY_UP }, /* CH+ */ - { 0x21, KEY_DOWN }, /* CH+ */ - { 0x12, KEY_VOLUMEUP }, /* Brightness Up */ - { 0x13, KEY_VOLUMEDOWN },/* Brightness Down */ - { 0x1f, KEY_RECORD }, - { 0x17, KEY_PLAY }, - { 0x16, KEY_PAUSE }, - { 0x0b, KEY_STOP }, - { 0x27, KEY_FASTFORWARD },/* >> */ - { 0x26, KEY_REWIND }, /* << */ - { 0x0d, KEY_OK }, /* Mute */ - { 0x11, KEY_LEFT }, /* VOL- */ - { 0x10, KEY_RIGHT }, /* VOL+ */ - { 0x29, KEY_BACK }, /* button under 9 */ - { 0x2c, KEY_MENU }, /* TTX */ - { 0x2b, KEY_EPG }, /* EPG */ - { 0x1e, KEY_RED }, /* OSD */ - { 0x0e, KEY_GREEN }, /* Window */ - { 0x2d, KEY_YELLOW }, /* button under << */ - { 0x0f, KEY_BLUE }, /* bottom yellow button */ - { 0x14, KEY_AUDIO }, /* Snapshot */ - { 0x38, KEY_TV }, /* TV/Radio */ - { 0x0c, KEY_ESC } /* upper Red button */ -}; + if (d->props.i2c_algo->master_xfer(&d->i2c_adap, &msg, 1) == 1) { + if (msg.buf[0] != 0xff) { + deb_rc("%s: rc code: %x, %x\n", + __func__, key[0], key[1]); + rc_keydown(d->rc_dev, key[0], 1); + } + } -static struct rc_map_dvb_usb_table_table keys_tables[] = { - { rc_map_dw210x_table, ARRAY_SIZE(rc_map_dw210x_table) }, - { rc_map_tevii_table, ARRAY_SIZE(rc_map_tevii_table) }, - { rc_map_tbs_table, ARRAY_SIZE(rc_map_tbs_table) }, - { rc_map_su3000_table, ARRAY_SIZE(rc_map_su3000_table) }, -}; + return 0; +} -static int dw2102_rc_query(struct dvb_usb_device *d, u32 *event, int *state) +static int prof_rc_query(struct dvb_usb_device *d) { - struct rc_map_table *keymap = d->props.rc.legacy.rc_map_table; - int keymap_size = d->props.rc.legacy.rc_map_size; u8 key[2]; struct i2c_msg msg = { .addr = DW2102_RC_QUERY, @@ -1579,32 +1498,34 @@ static int dw2102_rc_query(struct dvb_usb_device *d, u32 *event, int *state) .buf = key, .len = 2 }; - int i; - /* override keymap */ - if ((ir_keymap > 0) && (ir_keymap <= ARRAY_SIZE(keys_tables))) { - keymap = keys_tables[ir_keymap - 1].rc_keys ; - keymap_size = keys_tables[ir_keymap - 1].rc_keys_size; - } else if (ir_keymap > ARRAY_SIZE(keys_tables)) - return 0; /* none */ - - *state = REMOTE_NO_KEY_PRESSED; - if (d->props.i2c_algo->master_xfer(&d->i2c_adap, &msg, 1) == 1) { - for (i = 0; i < keymap_size ; i++) { - if (rc5_data(&keymap[i]) == msg.buf[0]) { - *state = REMOTE_KEY_PRESSED; - *event = keymap[i].keycode; - break; - } + if (d->props.i2c_algo->master_xfer(&d->i2c_adap, &msg, 1) == 1) { + if (msg.buf[0] != 0xff) { + deb_rc("%s: rc code: %x, %x\n", + __func__, key[0], key[1]); + rc_keydown(d->rc_dev, key[0]^0xff, 1); } + } - if ((*state) == REMOTE_KEY_PRESSED) - deb_rc("%s: found rc key: %x, %x, event: %x\n", - __func__, key[0], key[1], (*event)); - else if (key[0] != 0xff) - deb_rc("%s: unknown rc key: %x, %x\n", - __func__, key[0], key[1]); + return 0; +} +static int su3000_rc_query(struct dvb_usb_device *d) +{ + u8 key[2]; + struct i2c_msg msg = { + .addr = DW2102_RC_QUERY, + .flags = I2C_M_RD, + .buf = key, + .len = 2 + }; + + if (d->props.i2c_algo->master_xfer(&d->i2c_adap, &msg, 1) == 1) { + if (msg.buf[0] != 0xff) { + deb_rc("%s: rc code: %x, %x\n", + __func__, key[0], key[1]); + rc_keydown(d->rc_dev, key[1] << 8 | key[0], 1); + } } return 0; @@ -1630,6 +1551,7 @@ enum dw2102_table_entry { TEVII_S632, TERRATEC_CINERGY_S2_R2, GOTVIEW_SAT_HD, + GENIATECH_T220, }; static struct usb_device_id dw2102_table[] = { @@ -1652,6 +1574,7 @@ static struct usb_device_id dw2102_table[] = { [TEVII_S632] = {USB_DEVICE(0x9022, USB_PID_TEVII_S632)}, [TERRATEC_CINERGY_S2_R2] = {USB_DEVICE(USB_VID_TERRATEC, 0x00b0)}, [GOTVIEW_SAT_HD] = {USB_DEVICE(0x1FE1, USB_PID_GOTVIEW_SAT_HD)}, + [GENIATECH_T220] = {USB_DEVICE(0x1f4d, 0xD220)}, { } }; @@ -1711,9 +1634,7 @@ static int dw2102_load_firmware(struct usb_device *dev, /* init registers */ switch (dev->descriptor.idProduct) { case USB_PID_TEVII_S650: - dw2104_properties.rc.legacy.rc_map_table = rc_map_tevii_table; - dw2104_properties.rc.legacy.rc_map_size = - ARRAY_SIZE(rc_map_tevii_table); + dw2104_properties.rc.core.rc_codes = RC_MAP_TEVII_NEC; case USB_PID_DW2104: reset = 1; dw210x_op_rw(dev, 0xc4, 0x0000, 0, &reset, 1, @@ -1777,10 +1698,11 @@ static struct dvb_usb_device_properties dw2102_properties = { .i2c_algo = &dw2102_serit_i2c_algo, - .rc.legacy = { - .rc_map_table = rc_map_dw210x_table, - .rc_map_size = ARRAY_SIZE(rc_map_dw210x_table), + .rc.core = { .rc_interval = 150, + .rc_codes = RC_MAP_DM1105_NEC, + .module_name = "dw2102", + .allowed_protos = RC_BIT_NEC, .rc_query = dw2102_rc_query, }, @@ -1831,10 +1753,11 @@ static struct dvb_usb_device_properties dw2104_properties = { .no_reconnect = 1, .i2c_algo = &dw2104_i2c_algo, - .rc.legacy = { - .rc_map_table = rc_map_dw210x_table, - .rc_map_size = ARRAY_SIZE(rc_map_dw210x_table), + .rc.core = { .rc_interval = 150, + .rc_codes = RC_MAP_DM1105_NEC, + .module_name = "dw2102", + .allowed_protos = RC_BIT_NEC, .rc_query = dw2102_rc_query, }, @@ -1881,10 +1804,11 @@ static struct dvb_usb_device_properties dw3101_properties = { .no_reconnect = 1, .i2c_algo = &dw3101_i2c_algo, - .rc.legacy = { - .rc_map_table = rc_map_dw210x_table, - .rc_map_size = ARRAY_SIZE(rc_map_dw210x_table), + .rc.core = { .rc_interval = 150, + .rc_codes = RC_MAP_DM1105_NEC, + .module_name = "dw2102", + .allowed_protos = RC_BIT_NEC, .rc_query = dw2102_rc_query, }, @@ -1929,10 +1853,11 @@ static struct dvb_usb_device_properties s6x0_properties = { .no_reconnect = 1, .i2c_algo = &s6x0_i2c_algo, - .rc.legacy = { - .rc_map_table = rc_map_tevii_table, - .rc_map_size = ARRAY_SIZE(rc_map_tevii_table), + .rc.core = { .rc_interval = 150, + .rc_codes = RC_MAP_TEVII_NEC, + .module_name = "dw2102", + .allowed_protos = RC_BIT_NEC, .rc_query = dw2102_rc_query, }, @@ -2022,11 +1947,12 @@ static struct dvb_usb_device_properties su3000_properties = { .identify_state = su3000_identify_state, .i2c_algo = &su3000_i2c_algo, - .rc.legacy = { - .rc_map_table = rc_map_su3000_table, - .rc_map_size = ARRAY_SIZE(rc_map_su3000_table), + .rc.core = { .rc_interval = 150, - .rc_query = dw2102_rc_query, + .rc_codes = RC_MAP_SU3000, + .module_name = "dw2102", + .allowed_protos = RC_BIT_RC5, + .rc_query = su3000_rc_query, }, .read_mac_address = su3000_read_mac_address, @@ -2077,6 +2003,55 @@ static struct dvb_usb_device_properties su3000_properties = { } }; +static struct dvb_usb_device_properties t220_properties = { + .caps = DVB_USB_IS_AN_I2C_ADAPTER, + .usb_ctrl = DEVICE_SPECIFIC, + .size_of_priv = sizeof(struct su3000_state), + .power_ctrl = su3000_power_ctrl, + .num_adapters = 1, + .identify_state = su3000_identify_state, + .i2c_algo = &su3000_i2c_algo, + + .rc.core = { + .rc_interval = 150, + .rc_codes = RC_MAP_SU3000, + .module_name = "dw2102", + .allowed_protos = RC_BIT_RC5, + .rc_query = su3000_rc_query, + }, + + .read_mac_address = su3000_read_mac_address, + + .generic_bulk_ctrl_endpoint = 0x01, + + .adapter = { + { + .num_frontends = 1, + .fe = { { + .streaming_ctrl = su3000_streaming_ctrl, + .frontend_attach = t220_frontend_attach, + .stream = { + .type = USB_BULK, + .count = 8, + .endpoint = 0x82, + .u = { + .bulk = { + .buffersize = 4096, + } + } + } + } }, + } + }, + .num_device_descs = 1, + .devices = { + { "Geniatech T220 DVB-T/T2 USB2.0", + { &dw2102_table[GENIATECH_T220], NULL }, + { NULL }, + }, + } +}; + static int dw2102_probe(struct usb_interface *intf, const struct usb_device_id *id) { @@ -2088,8 +2063,8 @@ static int dw2102_probe(struct usb_interface *intf, /* fill only different fields */ p1100->firmware = P1100_FIRMWARE; p1100->devices[0] = d1100; - p1100->rc.legacy.rc_map_table = rc_map_tbs_table; - p1100->rc.legacy.rc_map_size = ARRAY_SIZE(rc_map_tbs_table); + p1100->rc.core.rc_query = prof_rc_query; + p1100->rc.core.rc_codes = RC_MAP_TBS_NEC; p1100->adapter->fe[0].frontend_attach = stv0288_frontend_attach; s660 = kmemdup(&s6x0_properties, @@ -2114,8 +2089,8 @@ static int dw2102_probe(struct usb_interface *intf, } p7500->firmware = P7500_FIRMWARE; p7500->devices[0] = d7500; - p7500->rc.legacy.rc_map_table = rc_map_tbs_table; - p7500->rc.legacy.rc_map_size = ARRAY_SIZE(rc_map_tbs_table); + p7500->rc.core.rc_query = prof_rc_query; + p7500->rc.core.rc_codes = RC_MAP_TBS_NEC; p7500->adapter->fe[0].frontend_attach = prof_7500_frontend_attach; @@ -2149,7 +2124,9 @@ static int dw2102_probe(struct usb_interface *intf, 0 == dvb_usb_device_init(intf, s421, THIS_MODULE, NULL, adapter_nr) || 0 == dvb_usb_device_init(intf, &su3000_properties, - THIS_MODULE, NULL, adapter_nr)) + THIS_MODULE, NULL, adapter_nr) || + 0 == dvb_usb_device_init(intf, &t220_properties, + THIS_MODULE, NULL, adapter_nr)) return 0; return -ENODEV; @@ -2169,7 +2146,7 @@ MODULE_DESCRIPTION("Driver for DVBWorld DVB-S 2101, 2102, DVB-S2 2104," " DVB-C 3101 USB2.0," " TeVii S600, S630, S650, S660, S480, S421, S632" " Prof 1100, 7500 USB2.0," - " Geniatech SU3000 devices"); + " Geniatech SU3000, T220 devices"); MODULE_VERSION("0.1"); MODULE_LICENSE("GPL"); MODULE_FIRMWARE(DW2101_FIRMWARE); diff --git a/drivers/media/usb/em28xx/Kconfig b/drivers/media/usb/em28xx/Kconfig index ca5ee6aceb62..a1fccf3096de 100644 --- a/drivers/media/usb/em28xx/Kconfig +++ b/drivers/media/usb/em28xx/Kconfig @@ -1,8 +1,12 @@ config VIDEO_EM28XX - tristate "Empia EM28xx USB video capture support" + tristate "Empia EM28xx USB devices support" depends on VIDEO_DEV && I2C select VIDEO_TUNER select VIDEO_TVEEPROM + +config VIDEO_EM28XX_V4L2 + tristate "Empia EM28xx analog TV, video capture and/or webcam support" + depends on VIDEO_EM28XX select VIDEOBUF2_VMALLOC select VIDEO_SAA711X if MEDIA_SUBDRV_AUTOSELECT select VIDEO_TVP5150 if MEDIA_SUBDRV_AUTOSELECT @@ -49,6 +53,8 @@ config VIDEO_EM28XX_DVB select DVB_MB86A20S if MEDIA_SUBDRV_AUTOSELECT select MEDIA_TUNER_QT1010 if MEDIA_SUBDRV_AUTOSELECT select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT + select DVB_M88DS3103 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_M88TS2022 if MEDIA_SUBDRV_AUTOSELECT ---help--- This adds support for DVB cards based on the Empiatech em28xx chips. diff --git a/drivers/media/usb/em28xx/Makefile b/drivers/media/usb/em28xx/Makefile index ad6d48557940..3f850d5063d0 100644 --- a/drivers/media/usb/em28xx/Makefile +++ b/drivers/media/usb/em28xx/Makefile @@ -1,10 +1,11 @@ -em28xx-y += em28xx-video.o em28xx-i2c.o em28xx-cards.o -em28xx-y += em28xx-core.o em28xx-vbi.o em28xx-camera.o +em28xx-y += em28xx-core.o em28xx-i2c.o em28xx-cards.o em28xx-camera.o +em28xx-v4l-objs := em28xx-video.o em28xx-vbi.o em28xx-alsa-objs := em28xx-audio.o em28xx-rc-objs := em28xx-input.o obj-$(CONFIG_VIDEO_EM28XX) += em28xx.o +obj-$(CONFIG_VIDEO_EM28XX_V4L2) += em28xx-v4l.o obj-$(CONFIG_VIDEO_EM28XX_ALSA) += em28xx-alsa.o obj-$(CONFIG_VIDEO_EM28XX_DVB) += em28xx-dvb.o obj-$(CONFIG_VIDEO_EM28XX_RC) += em28xx-rc.o diff --git a/drivers/media/usb/em28xx/em28xx-audio.c b/drivers/media/usb/em28xx/em28xx-audio.c index 2fdb66ee44ab..05e9bd11a3ff 100644 --- a/drivers/media/usb/em28xx/em28xx-audio.c +++ b/drivers/media/usb/em28xx/em28xx-audio.c @@ -3,7 +3,7 @@ * * Copyright (C) 2006 Markus Rechberger <mrechberger@gmail.com> * - * Copyright (C) 2007-2011 Mauro Carvalho Chehab <mchehab@redhat.com> + * Copyright (C) 2007-2014 Mauro Carvalho Chehab * - Port to work with the in-kernel driver * - Cleanups, fixes, alsa-controls, etc. * @@ -50,6 +50,9 @@ static int debug; module_param(debug, int, 0644); MODULE_PARM_DESC(debug, "activates debug info"); +#define EM28XX_MAX_AUDIO_BUFS 5 +#define EM28XX_MIN_AUDIO_PACKETS 64 + #define dprintk(fmt, arg...) do { \ if (debug) \ printk(KERN_INFO "em28xx-audio %s: " fmt, \ @@ -63,17 +66,13 @@ static int em28xx_deinit_isoc_audio(struct em28xx *dev) int i; dprintk("Stopping isoc\n"); - for (i = 0; i < EM28XX_AUDIO_BUFS; i++) { + for (i = 0; i < dev->adev.num_urb; i++) { + struct urb *urb = dev->adev.urb[i]; + if (!irqs_disabled()) - usb_kill_urb(dev->adev.urb[i]); + usb_kill_urb(urb); else - usb_unlink_urb(dev->adev.urb[i]); - - usb_free_urb(dev->adev.urb[i]); - dev->adev.urb[i] = NULL; - - kfree(dev->adev.transfer_buffer[i]); - dev->adev.transfer_buffer[i] = NULL; + usb_unlink_urb(urb); } return 0; @@ -91,6 +90,12 @@ static void em28xx_audio_isocirq(struct urb *urb) struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; + if (dev->disconnected) { + dprintk("device disconnected while streaming. URB status=%d.\n", urb->status); + atomic_set(&dev->stream_started, 0); + return; + } + switch (urb->status) { case 0: /* success */ case -ETIMEDOUT: /* NAK */ @@ -158,63 +163,27 @@ static void em28xx_audio_isocirq(struct urb *urb) urb->status = 0; status = usb_submit_urb(urb, GFP_ATOMIC); - if (status < 0) { + if (status < 0) em28xx_errdev("resubmit of audio urb failed (error=%i)\n", status); - } return; } static int em28xx_init_audio_isoc(struct em28xx *dev) { int i, errCode; - const int sb_size = EM28XX_NUM_AUDIO_PACKETS * - EM28XX_AUDIO_MAX_PACKET_SIZE; dprintk("Starting isoc transfers\n"); - for (i = 0; i < EM28XX_AUDIO_BUFS; i++) { - struct urb *urb; - int j, k; - - dev->adev.transfer_buffer[i] = kmalloc(sb_size, GFP_ATOMIC); - if (!dev->adev.transfer_buffer[i]) - return -ENOMEM; - - memset(dev->adev.transfer_buffer[i], 0x80, sb_size); - urb = usb_alloc_urb(EM28XX_NUM_AUDIO_PACKETS, GFP_ATOMIC); - if (!urb) { - em28xx_errdev("usb_alloc_urb failed!\n"); - for (j = 0; j < i; j++) { - usb_free_urb(dev->adev.urb[j]); - kfree(dev->adev.transfer_buffer[j]); - } - return -ENOMEM; - } - - urb->dev = dev->udev; - urb->context = dev; - urb->pipe = usb_rcvisocpipe(dev->udev, EM28XX_EP_AUDIO); - urb->transfer_flags = URB_ISO_ASAP; - urb->transfer_buffer = dev->adev.transfer_buffer[i]; - urb->interval = 1; - urb->complete = em28xx_audio_isocirq; - urb->number_of_packets = EM28XX_NUM_AUDIO_PACKETS; - urb->transfer_buffer_length = sb_size; - - for (j = k = 0; j < EM28XX_NUM_AUDIO_PACKETS; - j++, k += EM28XX_AUDIO_MAX_PACKET_SIZE) { - urb->iso_frame_desc[j].offset = k; - urb->iso_frame_desc[j].length = - EM28XX_AUDIO_MAX_PACKET_SIZE; - } - dev->adev.urb[i] = urb; - } + /* Start streaming */ + for (i = 0; i < dev->adev.num_urb; i++) { + memset(dev->adev.transfer_buffer[i], 0x80, + dev->adev.urb[i]->transfer_buffer_length); - for (i = 0; i < EM28XX_AUDIO_BUFS; i++) { errCode = usb_submit_urb(dev->adev.urb[i], GFP_ATOMIC); if (errCode) { - em28xx_errdev("submit of audio urb failed\n"); + em28xx_errdev("submit of audio urb failed (error=%i)\n", + errCode); em28xx_deinit_isoc_audio(dev); atomic_set(&dev->stream_started, 0); return errCode; @@ -255,15 +224,26 @@ static struct snd_pcm_hardware snd_em28xx_hw_capture = { .formats = SNDRV_PCM_FMTBIT_S16_LE, - .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT, + .rates = SNDRV_PCM_RATE_48000, .rate_min = 48000, .rate_max = 48000, .channels_min = 2, .channels_max = 2, .buffer_bytes_max = 62720 * 8, /* just about the value in usbaudio.c */ - .period_bytes_min = 64, /* 12544/2, */ - .period_bytes_max = 12544, + + + /* + * The period is 12.288 bytes. Allow a 10% of variation along its + * value, in order to avoid overruns/underruns due to some clock + * drift. + * + * FIXME: This period assumes 64 packets, and a 48000 PCM rate. + * Calculate it dynamically. + */ + .period_bytes_min = 11059, + .period_bytes_max = 13516, + .periods_min = 2, .periods_max = 98, /* 12544, */ }; @@ -274,28 +254,48 @@ static int snd_em28xx_capture_open(struct snd_pcm_substream *substream) struct snd_pcm_runtime *runtime = substream->runtime; int ret = 0; - dprintk("opening device and trying to acquire exclusive lock\n"); - if (!dev) { em28xx_err("BUG: em28xx can't find device struct." " Can't proceed with open\n"); return -ENODEV; } + if (dev->disconnected) + return -ENODEV; + + dprintk("opening device and trying to acquire exclusive lock\n"); + runtime->hw = snd_em28xx_hw_capture; - if ((dev->alt == 0 || dev->audio_ifnum) && dev->adev.users == 0) { - if (dev->audio_ifnum) + if ((dev->alt == 0 || dev->is_audio_only) && dev->adev.users == 0) { + int nonblock = !!(substream->f_flags & O_NONBLOCK); + + if (nonblock) { + if (!mutex_trylock(&dev->lock)) + return -EAGAIN; + } else + mutex_lock(&dev->lock); + if (dev->is_audio_only) + /* vendor audio is on a separate interface */ dev->alt = 1; else + /* vendor audio is on the same interface as video */ dev->alt = 7; + /* + * FIXME: The intention seems to be to select the alt + * setting with the largest wMaxPacketSize for the video + * endpoint. + * At least dev->alt should be used instead, but we + * should probably not touch it at all if it is + * already >0, because wMaxPacketSize of the audio + * endpoints seems to be the same for all. + */ dprintk("changing alternate number on interface %d to %d\n", - dev->audio_ifnum, dev->alt); - usb_set_interface(dev->udev, dev->audio_ifnum, dev->alt); + dev->ifnum, dev->alt); + usb_set_interface(dev->udev, dev->ifnum, dev->alt); /* Sets volume, mute, etc */ dev->mute = 0; - mutex_lock(&dev->lock); ret = em28xx_audio_analog_set(dev); if (ret < 0) goto err; @@ -304,7 +304,12 @@ static int snd_em28xx_capture_open(struct snd_pcm_substream *substream) mutex_unlock(&dev->lock); } + /* Dynamically adjust the period size */ snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + dev->adev.period * 95 / 100, + dev->adev.period * 105 / 100); + dev->adev.capture_pcm_substream = substream; return 0; @@ -344,6 +349,10 @@ static int snd_em28xx_hw_capture_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { int ret; + struct em28xx *dev = snd_pcm_substream_chip(substream); + + if (dev->disconnected) + return -ENODEV; dprintk("Setting capture parameters\n"); @@ -383,6 +392,9 @@ static int snd_em28xx_prepare(struct snd_pcm_substream *substream) { struct em28xx *dev = snd_pcm_substream_chip(substream); + if (dev->disconnected) + return -ENODEV; + dev->adev.hwptr_done_capture = 0; dev->adev.capture_transfer_done = 0; @@ -408,6 +420,9 @@ static int snd_em28xx_capture_trigger(struct snd_pcm_substream *substream, struct em28xx *dev = snd_pcm_substream_chip(substream); int retval = 0; + if (dev->disconnected) + return -ENODEV; + switch (cmd) { case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* fall through */ case SNDRV_PCM_TRIGGER_RESUME: /* fall through */ @@ -434,6 +449,9 @@ static snd_pcm_uframes_t snd_em28xx_capture_pointer(struct snd_pcm_substream snd_pcm_uframes_t hwptr_done; dev = snd_pcm_substream_chip(substream); + if (dev->disconnected) + return SNDRV_PCM_POS_XRUN; + spin_lock_irqsave(&dev->adev.slock, flags); hwptr_done = dev->adev.hwptr_done_capture; spin_unlock_irqrestore(&dev->adev.slock, flags); @@ -455,6 +473,11 @@ static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs, static int em28xx_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *info) { + struct em28xx *dev = snd_kcontrol_chip(kcontrol); + + if (dev->disconnected) + return -ENODEV; + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; info->count = 2; info->value.integer.min = 0; @@ -467,11 +490,22 @@ static int em28xx_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *value) { struct em28xx *dev = snd_kcontrol_chip(kcontrol); + struct snd_pcm_substream *substream = dev->adev.capture_pcm_substream; u16 val = (0x1f - (value->value.integer.value[0] & 0x1f)) | (0x1f - (value->value.integer.value[1] & 0x1f)) << 8; + int nonblock = 0; int rc; - mutex_lock(&dev->lock); + if (dev->disconnected) + return -ENODEV; + + if (substream) + nonblock = !!(substream->f_flags & O_NONBLOCK); + if (nonblock) { + if (!mutex_trylock(&dev->lock)) + return -EAGAIN; + } else + mutex_lock(&dev->lock); rc = em28xx_read_ac97(dev, kcontrol->private_value); if (rc < 0) goto err; @@ -496,9 +530,20 @@ static int em28xx_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *value) { struct em28xx *dev = snd_kcontrol_chip(kcontrol); + struct snd_pcm_substream *substream = dev->adev.capture_pcm_substream; + int nonblock = 0; int val; - mutex_lock(&dev->lock); + if (dev->disconnected) + return -ENODEV; + + if (substream) + nonblock = !!(substream->f_flags & O_NONBLOCK); + if (nonblock) { + if (!mutex_trylock(&dev->lock)) + return -EAGAIN; + } else + mutex_lock(&dev->lock); val = em28xx_read_ac97(dev, kcontrol->private_value); mutex_unlock(&dev->lock); if (val < 0) @@ -520,9 +565,20 @@ static int em28xx_vol_put_mute(struct snd_kcontrol *kcontrol, { struct em28xx *dev = snd_kcontrol_chip(kcontrol); u16 val = value->value.integer.value[0]; + struct snd_pcm_substream *substream = dev->adev.capture_pcm_substream; + int nonblock = 0; int rc; - mutex_lock(&dev->lock); + if (dev->disconnected) + return -ENODEV; + + if (substream) + nonblock = !!(substream->f_flags & O_NONBLOCK); + if (nonblock) { + if (!mutex_trylock(&dev->lock)) + return -EAGAIN; + } else + mutex_lock(&dev->lock); rc = em28xx_read_ac97(dev, kcontrol->private_value); if (rc < 0) goto err; @@ -550,9 +606,20 @@ static int em28xx_vol_get_mute(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *value) { struct em28xx *dev = snd_kcontrol_chip(kcontrol); + struct snd_pcm_substream *substream = dev->adev.capture_pcm_substream; + int nonblock = 0; int val; - mutex_lock(&dev->lock); + if (dev->disconnected) + return -ENODEV; + + if (substream) + nonblock = !!(substream->f_flags & O_NONBLOCK); + if (nonblock) { + if (!mutex_trylock(&dev->lock)) + return -EAGAIN; + } else + mutex_lock(&dev->lock); val = em28xx_read_ac97(dev, kcontrol->private_value); mutex_unlock(&dev->lock); if (val < 0) @@ -634,25 +701,204 @@ static struct snd_pcm_ops snd_em28xx_pcm_capture = { .page = snd_pcm_get_vmalloc_page, }; +static void em28xx_audio_free_urb(struct em28xx *dev) +{ + int i; + + for (i = 0; i < dev->adev.num_urb; i++) { + struct urb *urb = dev->adev.urb[i]; + + if (!urb) + continue; + + usb_free_coherent(dev->udev, urb->transfer_buffer_length, + dev->adev.transfer_buffer[i], + urb->transfer_dma); + + usb_free_urb(urb); + } + kfree(dev->adev.urb); + kfree(dev->adev.transfer_buffer); + dev->adev.num_urb = 0; +} + +/* high bandwidth multiplier, as encoded in highspeed endpoint descriptors */ +static int em28xx_audio_ep_packet_size(struct usb_device *udev, + struct usb_endpoint_descriptor *e) +{ + int size = le16_to_cpu(e->wMaxPacketSize); + + if (udev->speed == USB_SPEED_HIGH) + return (size & 0x7ff) * (1 + (((size) >> 11) & 0x03)); + + return size & 0x7ff; +} + +static int em28xx_audio_urb_init(struct em28xx *dev) +{ + struct usb_interface *intf; + struct usb_endpoint_descriptor *e, *ep = NULL; + int i, ep_size, interval, num_urb, npackets; + int urb_size, bytes_per_transfer; + u8 alt; + + if (dev->ifnum) + alt = 1; + else + alt = 7; + + intf = usb_ifnum_to_if(dev->udev, dev->ifnum); + + if (intf->num_altsetting <= alt) { + em28xx_errdev("alt %d doesn't exist on interface %d\n", + dev->ifnum, alt); + return -ENODEV; + } + + for (i = 0; i < intf->altsetting[alt].desc.bNumEndpoints; i++) { + e = &intf->altsetting[alt].endpoint[i].desc; + if (!usb_endpoint_dir_in(e)) + continue; + if (e->bEndpointAddress == EM28XX_EP_AUDIO) { + ep = e; + break; + } + } + + if (!ep) { + em28xx_errdev("Couldn't find an audio endpoint"); + return -ENODEV; + } + + ep_size = em28xx_audio_ep_packet_size(dev->udev, ep); + interval = 1 << (ep->bInterval - 1); + + em28xx_info("Endpoint 0x%02x %s on intf %d alt %d interval = %d, size %d\n", + EM28XX_EP_AUDIO, usb_speed_string(dev->udev->speed), + dev->ifnum, alt, + interval, + ep_size); + + /* Calculate the number and size of URBs to better fit the audio samples */ + + /* + * Estimate the number of bytes per DMA transfer. + * + * This is given by the bit rate (for now, only 48000 Hz) multiplied + * by 2 channels and 2 bytes/sample divided by the number of microframe + * intervals and by the microframe rate (125 us) + */ + bytes_per_transfer = DIV_ROUND_UP(48000 * 2 * 2, 125 * interval); + + /* + * Estimate the number of transfer URBs. Don't let it go past the + * maximum number of URBs that is known to be supported by the device. + */ + num_urb = DIV_ROUND_UP(bytes_per_transfer, ep_size); + if (num_urb > EM28XX_MAX_AUDIO_BUFS) + num_urb = EM28XX_MAX_AUDIO_BUFS; + + /* + * Now that we know the number of bytes per transfer and the number of + * URBs, estimate the typical size of an URB, in order to adjust the + * minimal number of packets. + */ + urb_size = bytes_per_transfer / num_urb; + + /* + * Now, calculate the amount of audio packets to be filled on each + * URB. In order to preserve the old behaviour, use a minimal + * threshold for this value. + */ + npackets = EM28XX_MIN_AUDIO_PACKETS; + if (urb_size > ep_size * npackets) + npackets = DIV_ROUND_UP(urb_size, ep_size); + + em28xx_info("Number of URBs: %d, with %d packets and %d size", + num_urb, npackets, urb_size); + + /* Estimate the bytes per period */ + dev->adev.period = urb_size * npackets; + + /* Allocate space to store the number of URBs to be used */ + + dev->adev.transfer_buffer = kcalloc(num_urb, + sizeof(*dev->adev.transfer_buffer), + GFP_ATOMIC); + if (!dev->adev.transfer_buffer) { + return -ENOMEM; + } + + dev->adev.urb = kcalloc(num_urb, sizeof(*dev->adev.urb), GFP_ATOMIC); + if (!dev->adev.urb) { + kfree(dev->adev.transfer_buffer); + return -ENOMEM; + } + + /* Alloc memory for each URB and for each transfer buffer */ + dev->adev.num_urb = num_urb; + for (i = 0; i < num_urb; i++) { + struct urb *urb; + int j, k; + void *buf; + + urb = usb_alloc_urb(npackets, GFP_ATOMIC); + if (!urb) { + em28xx_errdev("usb_alloc_urb failed!\n"); + em28xx_audio_free_urb(dev); + return -ENOMEM; + } + dev->adev.urb[i] = urb; + + buf = usb_alloc_coherent(dev->udev, npackets * ep_size, GFP_ATOMIC, + &urb->transfer_dma); + if (!buf) { + em28xx_errdev("usb_alloc_coherent failed!\n"); + em28xx_audio_free_urb(dev); + return -ENOMEM; + } + dev->adev.transfer_buffer[i] = buf; + + urb->dev = dev->udev; + urb->context = dev; + urb->pipe = usb_rcvisocpipe(dev->udev, EM28XX_EP_AUDIO); + urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + urb->transfer_buffer = buf; + urb->interval = interval; + urb->complete = em28xx_audio_isocirq; + urb->number_of_packets = npackets; + urb->transfer_buffer_length = ep_size * npackets; + + for (j = k = 0; j < npackets; j++, k += ep_size) { + urb->iso_frame_desc[j].offset = k; + urb->iso_frame_desc[j].length = ep_size; + } + } + + return 0; +} + static int em28xx_audio_init(struct em28xx *dev) { struct em28xx_audio *adev = &dev->adev; struct snd_pcm *pcm; struct snd_card *card; static int devnr; - int err; + int err; - if (!dev->has_alsa_audio || dev->audio_ifnum < 0) { + if (!dev->has_alsa_audio) { /* This device does not support the extension (in this case the device is expecting the snd-usb-audio module or doesn't have analog audio support at all) */ return 0; } - printk(KERN_INFO "em28xx-audio.c: probing for em28xx Audio Vendor Class\n"); + em28xx_info("Binding audio extension\n"); + printk(KERN_INFO "em28xx-audio.c: Copyright (C) 2006 Markus " "Rechberger\n"); - printk(KERN_INFO "em28xx-audio.c: Copyright (C) 2007-2011 Mauro Carvalho Chehab\n"); + printk(KERN_INFO + "em28xx-audio.c: Copyright (C) 2007-2014 Mauro Carvalho Chehab\n"); err = snd_card_create(index[devnr], "Em28xx Audio", THIS_MODULE, 0, &card); @@ -660,11 +906,12 @@ static int em28xx_audio_init(struct em28xx *dev) return err; spin_lock_init(&adev->slock); + adev->sndcard = card; + adev->udev = dev->udev; + err = snd_pcm_new(card, "Em28xx Audio", 0, 0, 1, &pcm); - if (err < 0) { - snd_card_free(card); - return err; - } + if (err < 0) + goto card_free; snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_em28xx_pcm_capture); pcm->info_flags = 0; @@ -694,15 +941,25 @@ static int em28xx_audio_init(struct em28xx *dev) em28xx_cvol_new(card, dev, "Surround", AC97_SURROUND_MASTER); } + err = em28xx_audio_urb_init(dev); + if (err) + goto card_free; + err = snd_card_register(card); - if (err < 0) { - snd_card_free(card); - return err; - } - adev->sndcard = card; - adev->udev = dev->udev; + if (err < 0) + goto urb_free; + em28xx_info("Audio extension successfully initialized\n"); return 0; + +urb_free: + em28xx_audio_free_urb(dev); + +card_free: + snd_card_free(card); + adev->sndcard = NULL; + + return err; } static int em28xx_audio_fini(struct em28xx *dev) @@ -717,7 +974,14 @@ static int em28xx_audio_fini(struct em28xx *dev) return 0; } + em28xx_info("Closing audio extension"); + if (dev->adev.sndcard) { + snd_card_disconnect(dev->adev.sndcard); + flush_work(&dev->wq_trigger); + + em28xx_audio_free_urb(dev); + snd_card_free(dev->adev.sndcard); dev->adev.sndcard = NULL; } @@ -745,7 +1009,8 @@ static void __exit em28xx_alsa_unregister(void) MODULE_LICENSE("GPL"); MODULE_AUTHOR("Markus Rechberger <mrechberger@gmail.com>"); MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@redhat.com>"); -MODULE_DESCRIPTION("Em28xx Audio driver"); +MODULE_DESCRIPTION(DRIVER_DESC " - audio interface"); +MODULE_VERSION(EM28XX_VERSION); module_init(em28xx_alsa_register); module_exit(em28xx_alsa_unregister); diff --git a/drivers/media/usb/em28xx/em28xx-camera.c b/drivers/media/usb/em28xx/em28xx-camera.c index d666741797d4..c29f5c4e7b40 100644 --- a/drivers/media/usb/em28xx/em28xx-camera.c +++ b/drivers/media/usb/em28xx/em28xx-camera.c @@ -454,3 +454,4 @@ int em28xx_init_camera(struct em28xx *dev) return ret; } +EXPORT_SYMBOL_GPL(em28xx_init_camera); diff --git a/drivers/media/usb/em28xx/em28xx-cards.c b/drivers/media/usb/em28xx/em28xx-cards.c index a5196697627f..4d97a76cc3b0 100644 --- a/drivers/media/usb/em28xx/em28xx-cards.c +++ b/drivers/media/usb/em28xx/em28xx-cards.c @@ -36,7 +36,6 @@ #include <media/tvaudio.h> #include <media/i2c-addr.h> #include <media/tveeprom.h> -#include <media/v4l2-clk.h> #include <media/v4l2-common.h> #include "em28xx.h" @@ -67,7 +66,7 @@ MODULE_PARM_DESC(usb_xfer_mode, /* Bitmask marking allocated devices from 0 to EM28XX_MAXBOARDS - 1 */ -static unsigned long em28xx_devused; +DECLARE_BITMAP(em28xx_devused, EM28XX_MAXBOARDS); struct em28xx_hash_table { unsigned long hash; @@ -356,6 +355,28 @@ static struct em28xx_reg_seq c3tech_digital_duo_digital[] = { { -1, -1, -1, -1}, }; +/* + * 2013:0258 PCTV DVB-S2 Stick (461e) + * GPIO 0 = POWER_ON + * GPIO 1 = BOOST + * GPIO 2 = VUV_LNB (red LED) + * GPIO 3 = #EXT_12V + * GPIO 4 = INT_DEM + * GPIO 5 = INT_LNB + * GPIO 6 = #RESET_DEM + * GPIO 7 = P07_LED (green LED) + */ +static struct em28xx_reg_seq pctv_461e[] = { + {EM2874_R80_GPIO_P0_CTRL, 0x7f, 0xff, 0}, + {0x0d, 0xff, 0xff, 0}, + {EM2874_R80_GPIO_P0_CTRL, 0x3f, 0xff, 100}, /* reset demod */ + {EM2874_R80_GPIO_P0_CTRL, 0x7f, 0xff, 200}, /* reset demod */ + {0x0d, 0x42, 0xff, 0}, + {EM2874_R80_GPIO_P0_CTRL, 0xeb, 0xff, 0}, + {EM2874_R5F_TS_ENABLE, 0x84, 0x84, 0}, /* parallel? | null discard */ + { -1, -1, -1, -1}, +}; + #if 0 static struct em28xx_reg_seq hauppauge_930c_gpio[] = { {EM2874_R80_GPIO_P0_CTRL, 0x6f, 0xff, 10}, @@ -412,6 +433,70 @@ static struct em28xx_reg_seq pctv_520e[] = { { -1, -1, -1, -1}, }; +/* 1ae7:9003/9004 SpeedLink Vicious And Devine Laplace webcam + * reg 0x80/0x84: + * GPIO_0: capturing LED, 0=on, 1=off + * GPIO_2: AV mute button, 0=pressed, 1=unpressed + * GPIO 3: illumination button, 0=pressed, 1=unpressed + * GPIO_6: illumination/flash LED, 0=on, 1=off + * reg 0x81/0x85: + * GPIO_7: snapshot button, 0=pressed, 1=unpressed + */ +static struct em28xx_reg_seq speedlink_vad_laplace_reg_seq[] = { + {EM2820_R08_GPIO_CTRL, 0xf7, 0xff, 10}, + {EM2874_R80_GPIO_P0_CTRL, 0xff, 0xb2, 10}, + { -1, -1, -1, -1}, +}; + +/* + * Button definitions + */ +static struct em28xx_button std_snapshot_button[] = { + { + .role = EM28XX_BUTTON_SNAPSHOT, + .reg_r = EM28XX_R0C_USBSUSP, + .reg_clearing = EM28XX_R0C_USBSUSP, + .mask = EM28XX_R0C_USBSUSP_SNAPSHOT, + .inverted = 0, + }, + {-1, 0, 0, 0, 0}, +}; + +static struct em28xx_button speedlink_vad_laplace_buttons[] = { + { + .role = EM28XX_BUTTON_SNAPSHOT, + .reg_r = EM2874_R85_GPIO_P1_STATE, + .mask = 0x80, + .inverted = 1, + }, + { + .role = EM28XX_BUTTON_ILLUMINATION, + .reg_r = EM2874_R84_GPIO_P0_STATE, + .mask = 0x08, + .inverted = 1, + }, + {-1, 0, 0, 0, 0}, +}; + +/* + * LED definitions + */ +static struct em28xx_led speedlink_vad_laplace_leds[] = { + { + .role = EM28XX_LED_ANALOG_CAPTURING, + .gpio_reg = EM2874_R80_GPIO_P0_CTRL, + .gpio_mask = 0x01, + .inverted = 1, + }, + { + .role = EM28XX_LED_ILLUMINATION, + .gpio_reg = EM2874_R80_GPIO_P0_CTRL, + .gpio_mask = 0x40, + .inverted = 1, + }, + {-1, 0, 0, 0}, +}; + /* * Board definitions */ @@ -1391,7 +1476,7 @@ struct em28xx_board em28xx_boards[] = { }, [EM2820_BOARD_PROLINK_PLAYTV_USB2] = { .name = "SIIG AVTuner-PVR / Pixelview Prolink PlayTV USB 2.0", - .has_snapshot_button = 1, + .buttons = std_snapshot_button, .tda9887_conf = TDA9887_PRESENT, .tuner_type = TUNER_YMEC_TVF_5533MF, .decoder = EM28XX_SAA711X, @@ -1413,7 +1498,7 @@ struct em28xx_board em28xx_boards[] = { }, [EM2860_BOARD_SAA711X_REFERENCE_DESIGN] = { .name = "EM2860/SAA711X Reference Design", - .has_snapshot_button = 1, + .buttons = std_snapshot_button, .tuner_type = TUNER_ABSENT, .decoder = EM28XX_SAA711X, .input = { { @@ -2020,7 +2105,7 @@ struct em28xx_board em28xx_boards[] = { }, /* 1b80:e1cc Delock 61959 * Empia EM2874B + Micronas DRX 3913KA2 + NXP TDA18271HDC2 - * mostly the same as MaxMedia UB-425-TC but different remote */ + * mostly the same as MaxMedia UB-425-TC but different remote */ [EM2874_BOARD_DELOCK_61959] = { .name = "Delock 61959", .tuner_type = TUNER_ABSENT, @@ -2043,7 +2128,38 @@ struct em28xx_board em28xx_boards[] = { .tuner_gpio = default_tuner_gpio, .def_i2c_bus = 1, }, + /* 1ae7:9003/9004 SpeedLink Vicious And Devine Laplace webcam + * Empia EM2765 + OmniVision OV2640 */ + [EM2765_BOARD_SPEEDLINK_VAD_LAPLACE] = { + .name = "SpeedLink Vicious And Devine Laplace webcam", + .xclk = EM28XX_XCLK_FREQUENCY_24MHZ, + .i2c_speed = EM28XX_I2C_CLK_WAIT_ENABLE | + EM28XX_I2C_FREQ_100_KHZ, + .def_i2c_bus = 1, + .tuner_type = TUNER_ABSENT, + .is_webcam = 1, + .input = { { + .type = EM28XX_VMUX_COMPOSITE1, + .amux = EM28XX_AMUX_VIDEO, + .gpio = speedlink_vad_laplace_reg_seq, + } }, + .buttons = speedlink_vad_laplace_buttons, + .leds = speedlink_vad_laplace_leds, + }, + /* 2013:0258 PCTV DVB-S2 Stick (461e) + * Empia EM28178, Montage M88DS3103, Montage M88TS2022, Allegro A8293 */ + [EM28178_BOARD_PCTV_461E] = { + .def_i2c_bus = 1, + .i2c_speed = EM28XX_I2C_CLK_WAIT_ENABLE | EM28XX_I2C_FREQ_400_KHZ, + .name = "PCTV DVB-S2 Stick (461e)", + .tuner_type = TUNER_ABSENT, + .tuner_gpio = pctv_461e, + .has_dvb = 1, + .ir_codes = RC_MAP_PINNACLE_PCTV_HD, + }, }; +EXPORT_SYMBOL_GPL(em28xx_boards); + const unsigned int em28xx_bcount = ARRAY_SIZE(em28xx_boards); /* table of devices that work with this driver */ @@ -2208,6 +2324,12 @@ struct usb_device_id em28xx_id_table[] = { .driver_info = EM2884_BOARD_PCTV_520E }, { USB_DEVICE(0x1b80, 0xe1cc), .driver_info = EM2874_BOARD_DELOCK_61959 }, + { USB_DEVICE(0x1ae7, 0x9003), + .driver_info = EM2765_BOARD_SPEEDLINK_VAD_LAPLACE }, + { USB_DEVICE(0x1ae7, 0x9004), + .driver_info = EM2765_BOARD_SPEEDLINK_VAD_LAPLACE }, + { USB_DEVICE(0x2013, 0x0258), + .driver_info = EM28178_BOARD_PCTV_461E }, { }, }; MODULE_DEVICE_TABLE(usb, em28xx_id_table); @@ -2239,24 +2361,6 @@ static struct em28xx_hash_table em28xx_i2c_hash[] = { }; /* NOTE: introduce a separate hash table for devices with 16 bit eeproms */ -/* I2C possible address to saa7115, tvp5150, msp3400, tvaudio */ -static unsigned short saa711x_addrs[] = { - 0x4a >> 1, 0x48 >> 1, /* SAA7111, SAA7111A and SAA7113 */ - 0x42 >> 1, 0x40 >> 1, /* SAA7114, SAA7115 and SAA7118 */ - I2C_CLIENT_END }; - -static unsigned short tvp5150_addrs[] = { - 0xb8 >> 1, - 0xba >> 1, - I2C_CLIENT_END -}; - -static unsigned short msp3400_addrs[] = { - 0x80 >> 1, - 0x88 >> 1, - I2C_CLIENT_END -}; - int em28xx_tuner_callback(void *ptr, int component, int command, int arg) { struct em28xx_i2c_bus *i2c_bus = ptr; @@ -2408,113 +2512,6 @@ static void em28xx_pre_card_setup(struct em28xx *dev) em28xx_set_mode(dev, EM28XX_SUSPEND); } -static void em28xx_setup_xc3028(struct em28xx *dev, struct xc2028_ctrl *ctl) -{ - memset(ctl, 0, sizeof(*ctl)); - - ctl->fname = XC2028_DEFAULT_FIRMWARE; - ctl->max_len = 64; - ctl->mts = em28xx_boards[dev->model].mts_firmware; - - switch (dev->model) { - case EM2880_BOARD_EMPIRE_DUAL_TV: - case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900: - case EM2882_BOARD_TERRATEC_HYBRID_XS: - ctl->demod = XC3028_FE_ZARLINK456; - break; - case EM2880_BOARD_TERRATEC_HYBRID_XS: - case EM2880_BOARD_TERRATEC_HYBRID_XS_FR: - case EM2881_BOARD_PINNACLE_HYBRID_PRO: - ctl->demod = XC3028_FE_ZARLINK456; - break; - case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2: - case EM2882_BOARD_PINNACLE_HYBRID_PRO_330E: - ctl->demod = XC3028_FE_DEFAULT; - break; - case EM2880_BOARD_AMD_ATI_TV_WONDER_HD_600: - ctl->demod = XC3028_FE_DEFAULT; - ctl->fname = XC3028L_DEFAULT_FIRMWARE; - break; - case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_850: - case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950: - case EM2880_BOARD_PINNACLE_PCTV_HD_PRO: - /* FIXME: Better to specify the needed IF */ - ctl->demod = XC3028_FE_DEFAULT; - break; - case EM2883_BOARD_KWORLD_HYBRID_330U: - case EM2882_BOARD_DIKOM_DK300: - case EM2882_BOARD_KWORLD_VS_DVBT: - ctl->demod = XC3028_FE_CHINA; - ctl->fname = XC2028_DEFAULT_FIRMWARE; - break; - case EM2882_BOARD_EVGA_INDTUBE: - ctl->demod = XC3028_FE_CHINA; - ctl->fname = XC3028L_DEFAULT_FIRMWARE; - break; - default: - ctl->demod = XC3028_FE_OREN538; - } -} - -static void em28xx_tuner_setup(struct em28xx *dev) -{ - struct tuner_setup tun_setup; - struct v4l2_frequency f; - - if (dev->tuner_type == TUNER_ABSENT) - return; - - memset(&tun_setup, 0, sizeof(tun_setup)); - - tun_setup.mode_mask = T_ANALOG_TV | T_RADIO; - tun_setup.tuner_callback = em28xx_tuner_callback; - - if (dev->board.radio.type) { - tun_setup.type = dev->board.radio.type; - tun_setup.addr = dev->board.radio_addr; - - v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_type_addr, &tun_setup); - } - - if ((dev->tuner_type != TUNER_ABSENT) && (dev->tuner_type)) { - tun_setup.type = dev->tuner_type; - tun_setup.addr = dev->tuner_addr; - - v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_type_addr, &tun_setup); - } - - if (dev->tda9887_conf) { - struct v4l2_priv_tun_config tda9887_cfg; - - tda9887_cfg.tuner = TUNER_TDA9887; - tda9887_cfg.priv = &dev->tda9887_conf; - - v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_config, &tda9887_cfg); - } - - if (dev->tuner_type == TUNER_XC2028) { - struct v4l2_priv_tun_config xc2028_cfg; - struct xc2028_ctrl ctl; - - memset(&xc2028_cfg, 0, sizeof(xc2028_cfg)); - memset(&ctl, 0, sizeof(ctl)); - - em28xx_setup_xc3028(dev, &ctl); - - xc2028_cfg.tuner = TUNER_XC2028; - xc2028_cfg.priv = &ctl; - - v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_config, &xc2028_cfg); - } - - /* configure tuner */ - f.tuner = 0; - f.type = V4L2_TUNER_ANALOG_TV; - f.frequency = 9076; /* just a magic number */ - dev->ctl_freq = f.frequency; - v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, &f); -} - static int em28xx_hint_board(struct em28xx *dev) { int i; @@ -2768,57 +2765,56 @@ static void em28xx_card_setup(struct em28xx *dev) /* Allow override tuner type by a module parameter */ if (tuner >= 0) dev->tuner_type = tuner; +} - /* request some modules */ - if (dev->board.has_msp34xx) - v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap[dev->def_i2c_bus], - "msp3400", 0, msp3400_addrs); - - if (dev->board.decoder == EM28XX_SAA711X) - v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap[dev->def_i2c_bus], - "saa7115_auto", 0, saa711x_addrs); - - if (dev->board.decoder == EM28XX_TVP5150) - v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap[dev->def_i2c_bus], - "tvp5150", 0, tvp5150_addrs); - - if (dev->board.adecoder == EM28XX_TVAUDIO) - v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap[dev->def_i2c_bus], - "tvaudio", dev->board.tvaudio_addr, NULL); - - if (dev->board.tuner_type != TUNER_ABSENT) { - int has_demod = (dev->tda9887_conf & TDA9887_PRESENT); - - if (dev->board.radio.type) - v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap[dev->def_i2c_bus], - "tuner", dev->board.radio_addr, NULL); - - if (has_demod) - v4l2_i2c_new_subdev(&dev->v4l2_dev, - &dev->i2c_adap[dev->def_i2c_bus], "tuner", - 0, v4l2_i2c_tuner_addrs(ADDRS_DEMOD)); - if (dev->tuner_addr == 0) { - enum v4l2_i2c_tuner_type type = - has_demod ? ADDRS_TV_WITH_DEMOD : ADDRS_TV; - struct v4l2_subdev *sd; - - sd = v4l2_i2c_new_subdev(&dev->v4l2_dev, - &dev->i2c_adap[dev->def_i2c_bus], "tuner", - 0, v4l2_i2c_tuner_addrs(type)); - - if (sd) - dev->tuner_addr = v4l2_i2c_subdev_addr(sd); - } else { - v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap[dev->def_i2c_bus], - "tuner", dev->tuner_addr, NULL); - } - } +void em28xx_setup_xc3028(struct em28xx *dev, struct xc2028_ctrl *ctl) +{ + memset(ctl, 0, sizeof(*ctl)); - em28xx_tuner_setup(dev); + ctl->fname = XC2028_DEFAULT_FIRMWARE; + ctl->max_len = 64; + ctl->mts = em28xx_boards[dev->model].mts_firmware; - em28xx_init_camera(dev); + switch (dev->model) { + case EM2880_BOARD_EMPIRE_DUAL_TV: + case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900: + case EM2882_BOARD_TERRATEC_HYBRID_XS: + ctl->demod = XC3028_FE_ZARLINK456; + break; + case EM2880_BOARD_TERRATEC_HYBRID_XS: + case EM2880_BOARD_TERRATEC_HYBRID_XS_FR: + case EM2881_BOARD_PINNACLE_HYBRID_PRO: + ctl->demod = XC3028_FE_ZARLINK456; + break; + case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2: + case EM2882_BOARD_PINNACLE_HYBRID_PRO_330E: + ctl->demod = XC3028_FE_DEFAULT; + break; + case EM2880_BOARD_AMD_ATI_TV_WONDER_HD_600: + ctl->demod = XC3028_FE_DEFAULT; + ctl->fname = XC3028L_DEFAULT_FIRMWARE; + break; + case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_850: + case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950: + case EM2880_BOARD_PINNACLE_PCTV_HD_PRO: + /* FIXME: Better to specify the needed IF */ + ctl->demod = XC3028_FE_DEFAULT; + break; + case EM2883_BOARD_KWORLD_HYBRID_330U: + case EM2882_BOARD_DIKOM_DK300: + case EM2882_BOARD_KWORLD_VS_DVBT: + ctl->demod = XC3028_FE_CHINA; + ctl->fname = XC2028_DEFAULT_FIRMWARE; + break; + case EM2882_BOARD_EVGA_INDTUBE: + ctl->demod = XC3028_FE_CHINA; + ctl->fname = XC3028L_DEFAULT_FIRMWARE; + break; + default: + ctl->demod = XC3028_FE_OREN538; + } } - +EXPORT_SYMBOL_GPL(em28xx_setup_xc3028); static void request_module_async(struct work_struct *work) { @@ -2831,17 +2827,30 @@ static void request_module_async(struct work_struct *work) * can be initialised right now. Otherwise, the module init * code will do it. */ + + /* + * Devicdes with an audio-only interface also have a V4L/DVB/RC + * interface. Don't register extensions twice on those devices. + */ + if (dev->is_audio_only) { +#if defined(CONFIG_MODULES) && defined(MODULE) + request_module("em28xx-alsa"); +#endif + return; + } + em28xx_init_extension(dev); #if defined(CONFIG_MODULES) && defined(MODULE) + if (dev->has_video) + request_module("em28xx-v4l"); if (dev->has_audio_class) request_module("snd-usb-audio"); else if (dev->has_alsa_audio) request_module("em28xx-alsa"); - if (dev->board.has_dvb) request_module("em28xx-dvb"); - if (dev->board.has_snapshot_button || + if (dev->board.buttons || ((dev->board.ir_codes || dev->board.has_ir_i2c) && !disable_ir)) request_module("em28xx-rc"); #endif /* CONFIG_MODULES */ @@ -2867,23 +2876,20 @@ void em28xx_release_resources(struct em28xx *dev) { /*FIXME: I2C IR should be disconnected */ - em28xx_release_analog_resources(dev); + mutex_lock(&dev->lock); if (dev->def_i2c_bus) em28xx_i2c_unregister(dev, 1); em28xx_i2c_unregister(dev, 0); - if (dev->clk) - v4l2_clk_unregister_fixed(dev->clk); - - v4l2_ctrl_handler_free(&dev->ctrl_handler); - - v4l2_device_unregister(&dev->v4l2_dev); usb_put_dev(dev->udev); /* Mark device as unused */ - clear_bit(dev->devno, &em28xx_devused); + clear_bit(dev->devno, em28xx_devused); + + mutex_unlock(&dev->lock); }; +EXPORT_SYMBOL_GPL(em28xx_release_resources); /* * em28xx_init_dev() @@ -2893,7 +2899,6 @@ static int em28xx_init_dev(struct em28xx *dev, struct usb_device *udev, struct usb_interface *interface, int minor) { - struct v4l2_ctrl_handler *hdl = &dev->ctrl_handler; int retval; static const char *default_chip_name = "em28xx"; const char *chip_name = default_chip_name; @@ -2968,6 +2973,11 @@ static int em28xx_init_dev(struct em28xx *dev, struct usb_device *udev, dev->wait_after_write = 0; dev->eeprom_addrwidth_16bit = 1; break; + case CHIP_ID_EM28178: + chip_name = "em28178"; + dev->wait_after_write = 0; + dev->eeprom_addrwidth_16bit = 1; + break; case CHIP_ID_EM2883: chip_name = "em2882/3"; dev->wait_after_write = 0; @@ -2983,6 +2993,16 @@ static int em28xx_init_dev(struct em28xx *dev, struct usb_device *udev, } } + if (dev->chip_id == CHIP_ID_EM2870 || + dev->chip_id == CHIP_ID_EM2874 || + dev->chip_id == CHIP_ID_EM28174 || + dev->chip_id == CHIP_ID_EM28178) { + /* Digital only device - don't load any alsa module */ + dev->audio_mode.has_audio = false; + dev->has_audio_class = false; + dev->has_alsa_audio = false; + } + if (chip_name != default_chip_name) printk(KERN_INFO DRIVER_NAME ": chip ID is %s\n", chip_name); @@ -3015,15 +3035,6 @@ static int em28xx_init_dev(struct em28xx *dev, struct usb_device *udev, } } - retval = v4l2_device_register(&interface->dev, &dev->v4l2_dev); - if (retval < 0) { - em28xx_errdev("Call to v4l2_device_register() failed!\n"); - return retval; - } - - v4l2_ctrl_handler_init(hdl, 8); - dev->v4l2_dev.ctrl_handler = hdl; - rt_mutex_init(&dev->i2c_bus_lock); /* register i2c bus 0 */ @@ -3034,7 +3045,7 @@ static int em28xx_init_dev(struct em28xx *dev, struct usb_device *udev, if (retval < 0) { em28xx_errdev("%s: em28xx_i2c_register bus 0 - error [%d]!\n", __func__, retval); - goto unregister_dev; + return retval; } /* register i2c bus 1 */ @@ -3048,88 +3059,17 @@ static int em28xx_init_dev(struct em28xx *dev, struct usb_device *udev, if (retval < 0) { em28xx_errdev("%s: em28xx_i2c_register bus 1 - error [%d]!\n", __func__, retval); - goto unregister_dev; - } - } - - /* - * Default format, used for tvp5150 or saa711x output formats - */ - dev->vinmode = 0x10; - dev->vinctl = EM28XX_VINCTRL_INTERLACED | - EM28XX_VINCTRL_CCIR656_ENABLE; - /* Do board specific init and eeprom reading */ - em28xx_card_setup(dev); + em28xx_i2c_unregister(dev, 0); - /* Configure audio */ - retval = em28xx_audio_setup(dev); - if (retval < 0) { - em28xx_errdev("%s: Error while setting audio - error [%d]!\n", - __func__, retval); - goto fail; - } - if (dev->audio_mode.ac97 != EM28XX_NO_AC97) { - v4l2_ctrl_new_std(hdl, &em28xx_ctrl_ops, - V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); - v4l2_ctrl_new_std(hdl, &em28xx_ctrl_ops, - V4L2_CID_AUDIO_VOLUME, 0, 0x1f, 1, 0x1f); - } else { - /* install the em28xx notify callback */ - v4l2_ctrl_notify(v4l2_ctrl_find(hdl, V4L2_CID_AUDIO_MUTE), - em28xx_ctrl_notify, dev); - v4l2_ctrl_notify(v4l2_ctrl_find(hdl, V4L2_CID_AUDIO_VOLUME), - em28xx_ctrl_notify, dev); - } - - /* wake i2c devices */ - em28xx_wake_i2c(dev); - - /* init video dma queues */ - INIT_LIST_HEAD(&dev->vidq.active); - INIT_LIST_HEAD(&dev->vbiq.active); - - if (dev->board.has_msp34xx) { - /* Send a reset to other chips via gpio */ - retval = em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xf7); - if (retval < 0) { - em28xx_errdev("%s: em28xx_write_reg - " - "msp34xx(1) failed! error [%d]\n", - __func__, retval); - goto fail; - } - msleep(3); - - retval = em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xff); - if (retval < 0) { - em28xx_errdev("%s: em28xx_write_reg - " - "msp34xx(2) failed! error [%d]\n", - __func__, retval); - goto fail; + return retval; } - msleep(3); - } - - retval = em28xx_register_analog_devices(dev); - if (retval < 0) { - goto fail; } - /* Save some power by putting tuner to sleep */ - v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_power, 0); + /* Do board specific init and eeprom reading */ + em28xx_card_setup(dev); return 0; - -fail: - if (dev->def_i2c_bus) - em28xx_i2c_unregister(dev, 1); - em28xx_i2c_unregister(dev, 0); - v4l2_ctrl_handler_free(&dev->ctrl_handler); - -unregister_dev: - v4l2_device_unregister(&dev->v4l2_dev); - - return retval; } /* high bandwidth multiplier, as encoded in highspeed endpoint descriptors */ @@ -3154,7 +3094,7 @@ static int em28xx_usb_probe(struct usb_interface *interface, /* Check to see next free device and mark as used */ do { - nr = find_first_zero_bit(&em28xx_devused, EM28XX_MAXBOARDS); + nr = find_first_zero_bit(em28xx_devused, EM28XX_MAXBOARDS); if (nr >= EM28XX_MAXBOARDS) { /* No free device slots */ printk(DRIVER_NAME ": Supports only %i em28xx boards.\n", @@ -3162,7 +3102,7 @@ static int em28xx_usb_probe(struct usb_interface *interface, retval = -ENOMEM; goto err_no_slot; } - } while (test_and_set_bit(nr, &em28xx_devused)); + } while (test_and_set_bit(nr, em28xx_devused)); /* Don't register audio interfaces */ if (interface->altsetting[0].desc.bInterfaceClass == USB_CLASS_AUDIO) { @@ -3332,7 +3272,9 @@ static int em28xx_usb_probe(struct usb_interface *interface, dev->alt = -1; dev->is_audio_only = has_audio && !(has_video || has_dvb); dev->has_alsa_audio = has_audio; - dev->audio_ifnum = ifnum; + dev->audio_mode.has_audio = has_audio; + dev->has_video = has_video; + dev->ifnum = ifnum; /* Checks if audio is provided by some interface */ for (i = 0; i < udev->config->desc.bNumInterfaces; i++) { @@ -3369,15 +3311,11 @@ static int em28xx_usb_probe(struct usb_interface *interface, /* save our data pointer in this interface device */ usb_set_intfdata(interface, dev); - /* initialize videobuf2 stuff */ - em28xx_vb2_setup(dev); - /* allocate device struct */ mutex_init(&dev->lock); - mutex_lock(&dev->lock); retval = em28xx_init_dev(dev, udev, interface, nr); if (retval) { - goto unlock_and_free; + goto err_free; } if (usb_xfer_mode < 0) { @@ -3402,26 +3340,6 @@ static int em28xx_usb_probe(struct usb_interface *interface, em28xx_info("dvb set to %s mode.\n", dev->dvb_xfer_bulk ? "bulk" : "isoc"); - - /* pre-allocate DVB usb transfer buffers */ - if (dev->dvb_xfer_bulk) { - retval = em28xx_alloc_urbs(dev, EM28XX_DIGITAL_MODE, - dev->dvb_xfer_bulk, - EM28XX_DVB_NUM_BUFS, - 512, - EM28XX_DVB_BULK_PACKET_MULTIPLIER); - } else { - retval = em28xx_alloc_urbs(dev, EM28XX_DIGITAL_MODE, - dev->dvb_xfer_bulk, - EM28XX_DVB_NUM_BUFS, - dev->dvb_max_pkt_size_isoc, - EM28XX_DVB_NUM_ISOC_PACKETS); - } - if (retval) { - printk(DRIVER_NAME - ": Failed to pre-allocate USB transfer buffers for DVB.\n"); - goto unlock_and_free; - } } request_modules(dev); @@ -3429,19 +3347,15 @@ static int em28xx_usb_probe(struct usb_interface *interface, /* Should be the last thing to do, to avoid newer udev's to open the device before fully initializing it */ - mutex_unlock(&dev->lock); return 0; -unlock_and_free: - mutex_unlock(&dev->lock); - err_free: kfree(dev->alt_max_pkt_size_isoc); kfree(dev); err: - clear_bit(nr, &em28xx_devused); + clear_bit(nr, em28xx_devused); err_no_slot: usb_put_dev(udev); @@ -3465,36 +3379,13 @@ static void em28xx_usb_disconnect(struct usb_interface *interface) dev->disconnected = 1; - if (dev->is_audio_only) { - mutex_lock(&dev->lock); - em28xx_close_extension(dev); - mutex_unlock(&dev->lock); - return; - } - - em28xx_info("disconnecting %s\n", dev->vdev->name); + em28xx_info("Disconnecting %s\n", dev->name); flush_request_modules(dev); - mutex_lock(&dev->lock); - - v4l2_device_disconnect(&dev->v4l2_dev); - - if (dev->users) { - em28xx_warn("device %s is open! Deregistration and memory deallocation are deferred on close.\n", - video_device_node_name(dev->vdev)); - - em28xx_uninit_usb_xfer(dev, EM28XX_ANALOG_MODE); - em28xx_uninit_usb_xfer(dev, EM28XX_DIGITAL_MODE); - } - em28xx_close_extension(dev); - /* NOTE: must be called BEFORE the resources are released */ - - if (!dev->users) - em28xx_release_resources(dev); - mutex_unlock(&dev->lock); + em28xx_release_resources(dev); if (!dev->users) { kfree(dev->alt_max_pkt_size_isoc); diff --git a/drivers/media/usb/em28xx/em28xx-core.c b/drivers/media/usb/em28xx/em28xx-core.c index fc157af5234a..898fb9bd88a2 100644 --- a/drivers/media/usb/em28xx/em28xx-core.c +++ b/drivers/media/usb/em28xx/em28xx-core.c @@ -23,6 +23,7 @@ */ #include <linux/init.h> +#include <linux/jiffies.h> #include <linux/list.h> #include <linux/module.h> #include <linux/slab.h> @@ -33,6 +34,16 @@ #include "em28xx.h" +#define DRIVER_AUTHOR "Ludovico Cavedon <cavedon@sssup.it>, " \ + "Markus Rechberger <mrechberger@gmail.com>, " \ + "Mauro Carvalho Chehab <mchehab@infradead.org>, " \ + "Sascha Sommer <saschasommer@freenet.de>" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_VERSION(EM28XX_VERSION); + /* #define ENABLE_DEBUG_ISOC_FRAMES */ static unsigned int core_debug; @@ -53,14 +64,6 @@ MODULE_PARM_DESC(reg_debug, "enable debug messages [URB reg]"); printk(KERN_INFO "%s %s :"fmt, \ dev->name, __func__ , ##arg); } while (0) -static int alt; -module_param(alt, int, 0644); -MODULE_PARM_DESC(alt, "alternate setting to use for video endpoint"); - -static unsigned int disable_vbi; -module_param(disable_vbi, int, 0644); -MODULE_PARM_DESC(disable_vbi, "disable vbi support"); - /* FIXME */ #define em28xx_isocdbg(fmt, arg...) do {\ if (core_debug) \ @@ -226,21 +229,42 @@ int em28xx_write_reg_bits(struct em28xx *dev, u16 reg, u8 val, EXPORT_SYMBOL_GPL(em28xx_write_reg_bits); /* + * em28xx_toggle_reg_bits() + * toggles/inverts the bits (specified by bitmask) of a register + */ +int em28xx_toggle_reg_bits(struct em28xx *dev, u16 reg, u8 bitmask) +{ + int oldval; + u8 newval; + + oldval = em28xx_read_reg(dev, reg); + if (oldval < 0) + return oldval; + + newval = (~oldval & bitmask) | (oldval & ~bitmask); + + return em28xx_write_reg(dev, reg, newval); +} +EXPORT_SYMBOL_GPL(em28xx_toggle_reg_bits); + +/* * em28xx_is_ac97_ready() * Checks if ac97 is ready */ static int em28xx_is_ac97_ready(struct em28xx *dev) { - int ret, i; + unsigned long timeout = jiffies + msecs_to_jiffies(EM28XX_AC97_XFER_TIMEOUT); + int ret; /* Wait up to 50 ms for AC97 command to complete */ - for (i = 0; i < 10; i++, msleep(5)) { + while (time_is_after_jiffies(timeout)) { ret = em28xx_read_reg(dev, EM28XX_R43_AC97BUSY); if (ret < 0) return ret; if (!(ret & 0x01)) return 0; + msleep(5); } em28xx_warn("AC97 command still being executed: not handled properly!\n"); @@ -482,16 +506,8 @@ int em28xx_audio_setup(struct em28xx *dev) int vid1, vid2, feat, cfg; u32 vid; - if (dev->chip_id == CHIP_ID_EM2870 || dev->chip_id == CHIP_ID_EM2874 - || dev->chip_id == CHIP_ID_EM28174) { - /* Digital only device - don't load any alsa module */ - dev->audio_mode.has_audio = false; - dev->has_audio_class = false; - dev->has_alsa_audio = false; + if (!dev->audio_mode.has_audio) return 0; - } - - dev->audio_mode.has_audio = true; /* See how this device is configured */ cfg = em28xx_read_reg(dev, EM28XX_R00_CHIPCFG); @@ -504,17 +520,19 @@ int em28xx_audio_setup(struct em28xx *dev) dev->has_alsa_audio = false; dev->audio_mode.has_audio = false; return 0; - } else if ((cfg & EM28XX_CHIPCFG_AUDIOMASK) == - EM28XX_CHIPCFG_I2S_3_SAMPRATES) { - em28xx_info("I2S Audio (3 sample rates)\n"); - dev->audio_mode.i2s_3rates = 1; - } else if ((cfg & EM28XX_CHIPCFG_AUDIOMASK) == - EM28XX_CHIPCFG_I2S_5_SAMPRATES) { - em28xx_info("I2S Audio (5 sample rates)\n"); - dev->audio_mode.i2s_5rates = 1; - } - - if ((cfg & EM28XX_CHIPCFG_AUDIOMASK) != EM28XX_CHIPCFG_AC97) { + } else if ((cfg & EM28XX_CHIPCFG_AUDIOMASK) != EM28XX_CHIPCFG_AC97) { + if (dev->chip_id < CHIP_ID_EM2860 && + (cfg & EM28XX_CHIPCFG_AUDIOMASK) == + EM2820_CHIPCFG_I2S_1_SAMPRATE) + dev->audio_mode.i2s_samplerates = 1; + else if (dev->chip_id >= CHIP_ID_EM2860 && + (cfg & EM28XX_CHIPCFG_AUDIOMASK) == + EM2860_CHIPCFG_I2S_5_SAMPRATES) + dev->audio_mode.i2s_samplerates = 5; + else + dev->audio_mode.i2s_samplerates = 3; + em28xx_info("I2S Audio (%d sample rate(s))\n", + dev->audio_mode.i2s_samplerates); /* Skip the code that does AC97 vendor detection */ dev->audio_mode.ac97 = EM28XX_NO_AC97; goto init_audio; @@ -582,23 +600,21 @@ init_audio: } EXPORT_SYMBOL_GPL(em28xx_audio_setup); -int em28xx_colorlevels_set_default(struct em28xx *dev) +const struct em28xx_led *em28xx_find_led(struct em28xx *dev, + enum em28xx_led_role role) { - em28xx_write_reg(dev, EM28XX_R20_YGAIN, CONTRAST_DEFAULT); - em28xx_write_reg(dev, EM28XX_R21_YOFFSET, BRIGHTNESS_DEFAULT); - em28xx_write_reg(dev, EM28XX_R22_UVGAIN, SATURATION_DEFAULT); - em28xx_write_reg(dev, EM28XX_R23_UOFFSET, BLUE_BALANCE_DEFAULT); - em28xx_write_reg(dev, EM28XX_R24_VOFFSET, RED_BALANCE_DEFAULT); - em28xx_write_reg(dev, EM28XX_R25_SHARPNESS, SHARPNESS_DEFAULT); - - em28xx_write_reg(dev, EM28XX_R14_GAMMA, 0x20); - em28xx_write_reg(dev, EM28XX_R15_RGAIN, 0x20); - em28xx_write_reg(dev, EM28XX_R16_GGAIN, 0x20); - em28xx_write_reg(dev, EM28XX_R17_BGAIN, 0x20); - em28xx_write_reg(dev, EM28XX_R18_ROFFSET, 0x00); - em28xx_write_reg(dev, EM28XX_R19_GOFFSET, 0x00); - return em28xx_write_reg(dev, EM28XX_R1A_BOFFSET, 0x00); + if (dev->board.leds) { + u8 k = 0; + while (dev->board.leds[k].role >= 0 && + dev->board.leds[k].role < EM28XX_NUM_LED_ROLES) { + if (dev->board.leds[k].role == role) + return &dev->board.leds[k]; + k++; + } + } + return NULL; } +EXPORT_SYMBOL_GPL(em28xx_find_led); int em28xx_capture_start(struct em28xx *dev, int start) { @@ -606,271 +622,57 @@ int em28xx_capture_start(struct em28xx *dev, int start) if (dev->chip_id == CHIP_ID_EM2874 || dev->chip_id == CHIP_ID_EM2884 || - dev->chip_id == CHIP_ID_EM28174) { + dev->chip_id == CHIP_ID_EM28174 || + dev->chip_id == CHIP_ID_EM28178) { /* The Transport Stream Enable Register moved in em2874 */ - if (!start) { - rc = em28xx_write_reg_bits(dev, EM2874_R5F_TS_ENABLE, - 0x00, - EM2874_TS1_CAPTURE_ENABLE); - return rc; - } - - /* Enable Transport Stream */ rc = em28xx_write_reg_bits(dev, EM2874_R5F_TS_ENABLE, - EM2874_TS1_CAPTURE_ENABLE, + start ? + EM2874_TS1_CAPTURE_ENABLE : 0x00, EM2874_TS1_CAPTURE_ENABLE); - return rc; - } - - - /* FIXME: which is the best order? */ - /* video registers are sampled by VREF */ - rc = em28xx_write_reg_bits(dev, EM28XX_R0C_USBSUSP, - start ? 0x10 : 0x00, 0x10); - if (rc < 0) - return rc; - - if (!start) { - /* disable video capture */ - rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x27); - return rc; - } - - if (dev->board.is_webcam) - rc = em28xx_write_reg(dev, 0x13, 0x0c); - - /* enable video capture */ - rc = em28xx_write_reg(dev, 0x48, 0x00); - - if (dev->mode == EM28XX_ANALOG_MODE) - rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x67); - else - rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x37); - - msleep(6); - - return rc; -} - -int em28xx_vbi_supported(struct em28xx *dev) -{ - /* Modprobe option to manually disable */ - if (disable_vbi == 1) - return 0; - - if (dev->board.is_webcam) - return 0; - - /* FIXME: check subdevices for VBI support */ - - if (dev->chip_id == CHIP_ID_EM2860 || - dev->chip_id == CHIP_ID_EM2883) - return 1; - - /* Version of em28xx that does not support VBI */ - return 0; -} - -int em28xx_set_outfmt(struct em28xx *dev) -{ - int ret; - u8 fmt, vinctrl; - - fmt = dev->format->reg; - if (!dev->is_em25xx) - fmt |= 0x20; - /* - * NOTE: it's not clear if this is really needed ! - * The datasheets say bit 5 is a reserved bit and devices seem to work - * fine without it. But the Windows driver sets it for em2710/50+em28xx - * devices and we've always been setting it, too. - * - * em2765 (em25xx, em276x/7x/8x) devices do NOT work with this bit set, - * it's likely used for an additional (compressed ?) format there. - */ - ret = em28xx_write_reg(dev, EM28XX_R27_OUTFMT, fmt); - if (ret < 0) - return ret; - - ret = em28xx_write_reg(dev, EM28XX_R10_VINMODE, dev->vinmode); - if (ret < 0) - return ret; - - vinctrl = dev->vinctl; - if (em28xx_vbi_supported(dev) == 1) { - vinctrl |= EM28XX_VINCTRL_VBI_RAW; - em28xx_write_reg(dev, EM28XX_R34_VBI_START_H, 0x00); - em28xx_write_reg(dev, EM28XX_R36_VBI_WIDTH, dev->vbi_width/4); - em28xx_write_reg(dev, EM28XX_R37_VBI_HEIGHT, dev->vbi_height); - if (dev->norm & V4L2_STD_525_60) { - /* NTSC */ - em28xx_write_reg(dev, EM28XX_R35_VBI_START_V, 0x09); - } else if (dev->norm & V4L2_STD_625_50) { - /* PAL */ - em28xx_write_reg(dev, EM28XX_R35_VBI_START_V, 0x07); - } - } - - return em28xx_write_reg(dev, EM28XX_R11_VINCTRL, vinctrl); -} - -static int em28xx_accumulator_set(struct em28xx *dev, u8 xmin, u8 xmax, - u8 ymin, u8 ymax) -{ - em28xx_coredbg("em28xx Scale: (%d,%d)-(%d,%d)\n", - xmin, ymin, xmax, ymax); - - em28xx_write_regs(dev, EM28XX_R28_XMIN, &xmin, 1); - em28xx_write_regs(dev, EM28XX_R29_XMAX, &xmax, 1); - em28xx_write_regs(dev, EM28XX_R2A_YMIN, &ymin, 1); - return em28xx_write_regs(dev, EM28XX_R2B_YMAX, &ymax, 1); -} - -static void em28xx_capture_area_set(struct em28xx *dev, u8 hstart, u8 vstart, - u16 width, u16 height) -{ - u8 cwidth = width >> 2; - u8 cheight = height >> 2; - u8 overflow = (height >> 9 & 0x02) | (width >> 10 & 0x01); - /* NOTE: size limit: 2047x1023 = 2MPix */ - - em28xx_coredbg("capture area set to (%d,%d): %dx%d\n", - hstart, vstart, - ((overflow & 2) << 9 | cwidth << 2), - ((overflow & 1) << 10 | cheight << 2)); - - em28xx_write_regs(dev, EM28XX_R1C_HSTART, &hstart, 1); - em28xx_write_regs(dev, EM28XX_R1D_VSTART, &vstart, 1); - em28xx_write_regs(dev, EM28XX_R1E_CWIDTH, &cwidth, 1); - em28xx_write_regs(dev, EM28XX_R1F_CHEIGHT, &cheight, 1); - em28xx_write_regs(dev, EM28XX_R1B_OFLOW, &overflow, 1); - - /* FIXME: function/meaning of these registers ? */ - /* FIXME: align width+height to multiples of 4 ?! */ - if (dev->is_em25xx) { - em28xx_write_reg(dev, 0x34, width >> 4); - em28xx_write_reg(dev, 0x35, height >> 4); - } -} - -static int em28xx_scaler_set(struct em28xx *dev, u16 h, u16 v) -{ - u8 mode; - /* the em2800 scaler only supports scaling down to 50% */ - - if (dev->board.is_em2800) { - mode = (v ? 0x20 : 0x00) | (h ? 0x10 : 0x00); } else { - u8 buf[2]; - - buf[0] = h; - buf[1] = h >> 8; - em28xx_write_regs(dev, EM28XX_R30_HSCALELOW, (char *)buf, 2); - - buf[0] = v; - buf[1] = v >> 8; - em28xx_write_regs(dev, EM28XX_R32_VSCALELOW, (char *)buf, 2); - /* it seems that both H and V scalers must be active - to work correctly */ - mode = (h || v) ? 0x30 : 0x00; - } - return em28xx_write_reg_bits(dev, EM28XX_R26_COMPR, mode, 0x30); -} - -/* FIXME: this only function read values from dev */ -int em28xx_resolution_set(struct em28xx *dev) -{ - int width, height; - width = norm_maxw(dev); - height = norm_maxh(dev); - - /* Properly setup VBI */ - dev->vbi_width = 720; - if (dev->norm & V4L2_STD_525_60) - dev->vbi_height = 12; - else - dev->vbi_height = 18; - - em28xx_set_outfmt(dev); + /* FIXME: which is the best order? */ + /* video registers are sampled by VREF */ + rc = em28xx_write_reg_bits(dev, EM28XX_R0C_USBSUSP, + start ? 0x10 : 0x00, 0x10); + if (rc < 0) + return rc; - em28xx_accumulator_set(dev, 1, (width - 4) >> 2, 1, (height - 4) >> 2); + if (start) { + if (dev->board.is_webcam) + rc = em28xx_write_reg(dev, 0x13, 0x0c); - /* If we don't set the start position to 2 in VBI mode, we end up - with line 20/21 being YUYV encoded instead of being in 8-bit - greyscale. The core of the issue is that line 21 (and line 23 for - PAL WSS) are inside of active video region, and as a result they - get the pixelformatting associated with that area. So by cropping - it out, we end up with the same format as the rest of the VBI - region */ - if (em28xx_vbi_supported(dev) == 1) - em28xx_capture_area_set(dev, 0, 2, width, height); - else - em28xx_capture_area_set(dev, 0, 0, width, height); + /* Enable video capture */ + rc = em28xx_write_reg(dev, 0x48, 0x00); - return em28xx_scaler_set(dev, dev->hscale, dev->vscale); -} + if (dev->mode == EM28XX_ANALOG_MODE) + rc = em28xx_write_reg(dev, + EM28XX_R12_VINENABLE, 0x67); + else + rc = em28xx_write_reg(dev, + EM28XX_R12_VINENABLE, 0x37); -/* Set USB alternate setting for analog video */ -int em28xx_set_alternate(struct em28xx *dev) -{ - int errCode; - int i; - unsigned int min_pkt_size = dev->width * 2 + 4; - - /* NOTE: for isoc transfers, only alt settings > 0 are allowed - bulk transfers seem to work only with alt=0 ! */ - dev->alt = 0; - if ((alt > 0) && (alt < dev->num_alt)) { - em28xx_coredbg("alternate forced to %d\n", dev->alt); - dev->alt = alt; - goto set_alt; + msleep(6); + } else { + /* disable video capture */ + rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x27); + } } - if (dev->analog_xfer_bulk) - goto set_alt; - /* When image size is bigger than a certain value, - the frame size should be increased, otherwise, only - green screen will be received. - */ - if (dev->width * 2 * dev->height > 720 * 240 * 2) - min_pkt_size *= 2; + if (rc < 0) + return rc; - for (i = 0; i < dev->num_alt; i++) { - /* stop when the selected alt setting offers enough bandwidth */ - if (dev->alt_max_pkt_size_isoc[i] >= min_pkt_size) { - dev->alt = i; - break; - /* otherwise make sure that we end up with the maximum bandwidth - because the min_pkt_size equation might be wrong... - */ - } else if (dev->alt_max_pkt_size_isoc[i] > - dev->alt_max_pkt_size_isoc[dev->alt]) - dev->alt = i; + /* Switch (explicitly controlled) analog capturing LED on/off */ + if (dev->mode == EM28XX_ANALOG_MODE) { + const struct em28xx_led *led; + led = em28xx_find_led(dev, EM28XX_LED_ANALOG_CAPTURING); + if (led) + em28xx_write_reg_bits(dev, led->gpio_reg, + (!start ^ led->inverted) ? + ~led->gpio_mask : led->gpio_mask, + led->gpio_mask); } -set_alt: - /* NOTE: for bulk transfers, we need to call usb_set_interface() - * even if the previous settings were the same. Otherwise streaming - * fails with all urbs having status = -EOVERFLOW ! */ - if (dev->analog_xfer_bulk) { - dev->max_pkt_size = 512; /* USB 2.0 spec */ - dev->packet_multiplier = EM28XX_BULK_PACKET_MULTIPLIER; - } else { /* isoc */ - em28xx_coredbg("minimum isoc packet size: %u (alt=%d)\n", - min_pkt_size, dev->alt); - dev->max_pkt_size = - dev->alt_max_pkt_size_isoc[dev->alt]; - dev->packet_multiplier = EM28XX_NUM_ISOC_PACKETS; - } - em28xx_coredbg("setting alternate %d with wMaxPacketSize=%u\n", - dev->alt, dev->max_pkt_size); - errCode = usb_set_interface(dev->udev, 0, dev->alt); - if (errCode < 0) { - em28xx_errdev("cannot change alternate number to %d (error=%i)\n", - dev->alt, errCode); - return errCode; - } - return 0; + return rc; } int em28xx_gpio_set(struct em28xx *dev, struct em28xx_reg_seq *gpio) @@ -1238,18 +1040,6 @@ int em28xx_init_usb_xfer(struct em28xx *dev, enum em28xx_mode mode, EXPORT_SYMBOL_GPL(em28xx_init_usb_xfer); /* - * em28xx_wake_i2c() - * configure i2c attached devices - */ -void em28xx_wake_i2c(struct em28xx *dev) -{ - v4l2_device_call_all(&dev->v4l2_dev, 0, core, reset, 0); - v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_routing, - INPUT(dev->ctl_input)->vmux, 0, 0); - v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_stream, 0); -} - -/* * Device control list */ @@ -1272,7 +1062,7 @@ int em28xx_register_extension(struct em28xx_ops *ops) ops->init(dev); } mutex_unlock(&em28xx_devlist_mutex); - printk(KERN_INFO "Em28xx: Initialized (%s) extension\n", ops->name); + printk(KERN_INFO "em28xx: Registered (%s) extension\n", ops->name); return 0; } EXPORT_SYMBOL(em28xx_register_extension); diff --git a/drivers/media/usb/em28xx/em28xx-dvb.c b/drivers/media/usb/em28xx/em28xx-dvb.c index 344042bb845c..a0a669e81362 100644 --- a/drivers/media/usb/em28xx/em28xx-dvb.c +++ b/drivers/media/usb/em28xx/em28xx-dvb.c @@ -51,10 +51,14 @@ #include "a8293.h" #include "qt1010.h" #include "mb86a20s.h" +#include "m88ds3103.h" +#include "m88ts2022.h" -MODULE_DESCRIPTION("driver for em28xx based DVB cards"); MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@infradead.org>"); MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION(DRIVER_DESC " - digital TV interface"); +MODULE_VERSION(EM28XX_VERSION); + static unsigned int debug; module_param(debug, int, 0644); @@ -87,6 +91,7 @@ struct em28xx_dvb { struct semaphore pll_mutex; bool dont_attach_fe1; int lna_gpio; + struct i2c_client *i2c_client_tuner; }; @@ -198,7 +203,7 @@ static int em28xx_start_streaming(struct em28xx_dvb *dvb) dvb_alt = dev->dvb_alt_isoc; } - usb_set_interface(dev->udev, 0, dvb_alt); + usb_set_interface(dev->udev, dev->ifnum, dvb_alt); rc = em28xx_set_mode(dev, EM28XX_DIGITAL_MODE); if (rc < 0) return rc; @@ -271,7 +276,7 @@ static int em28xx_stop_feed(struct dvb_demux_feed *feed) static int em28xx_dvb_bus_ctrl(struct dvb_frontend *fe, int acquire) { struct em28xx_i2c_bus *i2c_bus = fe->dvb->priv; - struct em28xx *dev = i2c_bus->dev; + struct em28xx *dev = i2c_bus->dev; if (acquire) return em28xx_set_mode(dev, EM28XX_DIGITAL_MODE); @@ -370,7 +375,6 @@ static struct drxk_config terratec_h5_drxk = { .no_i2c_bridge = 1, .microcode_name = "dvb-usb-terratec-h5-drxk.fw", .qam_demod_parameter_count = 2, - .load_firmware_sync = true, }; static struct drxk_config hauppauge_930c_drxk = { @@ -380,7 +384,6 @@ static struct drxk_config hauppauge_930c_drxk = { .microcode_name = "dvb-usb-hauppauge-hvr930c-drxk.fw", .chunk_size = 56, .qam_demod_parameter_count = 2, - .load_firmware_sync = true, }; static struct drxk_config terratec_htc_stick_drxk = { @@ -394,7 +397,6 @@ static struct drxk_config terratec_htc_stick_drxk = { .antenna_dvbt = true, /* The windows driver uses the same. This will disable LNA. */ .antenna_gpio = 0x6, - .load_firmware_sync = true, }; static struct drxk_config maxmedia_ub425_tc_drxk = { @@ -403,7 +405,6 @@ static struct drxk_config maxmedia_ub425_tc_drxk = { .no_i2c_bridge = 1, .microcode_name = "dvb-demod-drxk-01.fw", .chunk_size = 62, - .load_firmware_sync = true, .qam_demod_parameter_count = 2, }; @@ -415,7 +416,6 @@ static struct drxk_config pctv_520e_drxk = { .chunk_size = 58, .antenna_dvbt = true, /* disable LNA */ .antenna_gpio = (1 << 2), /* disable LNA */ - .load_firmware_sync = true, }; static int drxk_gate_ctrl(struct dvb_frontend *fe, int enable) @@ -808,6 +808,14 @@ static struct tda18271_config c3tech_duo_tda18271_config = { .small_i2c = TDA18271_03_BYTE_CHUNK_INIT, }; +static const struct m88ds3103_config pctv_461e_m88ds3103_config = { + .i2c_addr = 0x68, + .clock = 27000000, + .i2c_wr_max = 33, + .clock_out = 0, + .ts_mode = M88DS3103_TS_PARALLEL_16, + .agc = 0x99, +}; /* ------------------------------------------------------------------ */ @@ -815,11 +823,16 @@ static int em28xx_attach_xc3028(u8 addr, struct em28xx *dev) { struct dvb_frontend *fe; struct xc2028_config cfg; + struct xc2028_ctrl ctl; memset(&cfg, 0, sizeof(cfg)); cfg.i2c_adap = &dev->i2c_adap[dev->def_i2c_bus]; cfg.i2c_addr = addr; + memset(&ctl, 0, sizeof(ctl)); + em28xx_setup_xc3028(dev, &ctl); + cfg.ctrl = &ctl; + if (!dev->dvb->fe[0]) { em28xx_errdev("/2: dvb frontend not attached. " "Can't attach xc3028\n"); @@ -979,12 +992,18 @@ static int em28xx_dvb_init(struct em28xx *dev) int result = 0, mfe_shared = 0; struct em28xx_dvb *dvb; + if (dev->is_audio_only) { + /* Shouldn't initialize IR for this interface */ + return 0; + } + if (!dev->board.has_dvb) { /* This device does not support the extension */ - printk(KERN_INFO "em28xx_dvb: This device does not support the extension\n"); return 0; } + em28xx_info("Binding DVB extension\n"); + dvb = kzalloc(sizeof(struct em28xx_dvb), GFP_KERNEL); if (dvb == NULL) { @@ -994,6 +1013,27 @@ static int em28xx_dvb_init(struct em28xx *dev) dev->dvb = dvb; dvb->fe[0] = dvb->fe[1] = NULL; + /* pre-allocate DVB usb transfer buffers */ + if (dev->dvb_xfer_bulk) { + result = em28xx_alloc_urbs(dev, EM28XX_DIGITAL_MODE, + dev->dvb_xfer_bulk, + EM28XX_DVB_NUM_BUFS, + 512, + EM28XX_DVB_BULK_PACKET_MULTIPLIER); + } else { + result = em28xx_alloc_urbs(dev, EM28XX_DIGITAL_MODE, + dev->dvb_xfer_bulk, + EM28XX_DVB_NUM_BUFS, + dev->dvb_max_pkt_size_isoc, + EM28XX_DVB_NUM_ISOC_PACKETS); + } + if (result) { + em28xx_errdev("em28xx_dvb: failed to pre-allocate USB transfer buffers for DVB.\n"); + kfree(dvb); + dev->dvb = NULL; + return result; + } + mutex_lock(&dev->lock); em28xx_set_mode(dev, EM28XX_DIGITAL_MODE); /* init frontend */ @@ -1330,6 +1370,48 @@ static int em28xx_dvb_init(struct em28xx *dev) goto out_free; } break; + case EM28178_BOARD_PCTV_461E: + { + /* demod I2C adapter */ + struct i2c_adapter *i2c_adapter; + struct i2c_board_info info; + struct m88ts2022_config m88ts2022_config = { + .clock = 27000000, + }; + memset(&info, 0, sizeof(struct i2c_board_info)); + + /* attach demod */ + dvb->fe[0] = dvb_attach(m88ds3103_attach, + &pctv_461e_m88ds3103_config, + &dev->i2c_adap[dev->def_i2c_bus], + &i2c_adapter); + if (dvb->fe[0] == NULL) { + result = -ENODEV; + goto out_free; + } + + /* attach tuner */ + m88ts2022_config.fe = dvb->fe[0]; + strlcpy(info.type, "m88ts2022", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &m88ts2022_config; + request_module("m88ts2022"); + dvb->i2c_client_tuner = i2c_new_device(i2c_adapter, &info); + + /* delegate signal strength measurement to tuner */ + dvb->fe[0]->ops.read_signal_strength = + dvb->fe[0]->ops.tuner_ops.get_rf_strength; + + /* attach SEC */ + if (!dvb_attach(a8293_attach, dvb->fe[0], + &dev->i2c_adap[dev->def_i2c_bus], + &em28xx_a8293_config)) { + dvb_frontend_detach(dvb->fe[0]); + result = -ENODEV; + goto out_free; + } + } + break; default: em28xx_errdev("/2: The frontend of your DVB/ATSC card" " isn't supported yet\n"); @@ -1354,7 +1436,7 @@ static int em28xx_dvb_init(struct em28xx *dev) /* MFE lock */ dvb->adapter.mfe_shared = mfe_shared; - em28xx_info("Successfully loaded em28xx-dvb\n"); + em28xx_info("DVB extension successfully initialized\n"); ret: em28xx_set_mode(dev, EM28XX_SUSPEND); mutex_unlock(&dev->lock); @@ -1375,14 +1457,23 @@ static inline void prevent_sleep(struct dvb_frontend_ops *ops) static int em28xx_dvb_fini(struct em28xx *dev) { + if (dev->is_audio_only) { + /* Shouldn't initialize IR for this interface */ + return 0; + } + if (!dev->board.has_dvb) { /* This device does not support the extension */ return 0; } + em28xx_info("Closing DVB extension"); + if (dev->dvb) { struct em28xx_dvb *dvb = dev->dvb; + em28xx_uninit_usb_xfer(dev, EM28XX_DIGITAL_MODE); + if (dev->disconnected) { /* We cannot tell the device to sleep * once it has been unplugged. */ @@ -1392,6 +1483,7 @@ static int em28xx_dvb_fini(struct em28xx *dev) prevent_sleep(&dvb->fe[1]->ops); } + i2c_release_client(dvb->i2c_client_tuner); em28xx_unregister_dvb(dvb); kfree(dvb); dev->dvb = NULL; diff --git a/drivers/media/usb/em28xx/em28xx-i2c.c b/drivers/media/usb/em28xx/em28xx-i2c.c index c4ff9739a7ae..7e1724076ac4 100644 --- a/drivers/media/usb/em28xx/em28xx-i2c.c +++ b/drivers/media/usb/em28xx/em28xx-i2c.c @@ -26,6 +26,7 @@ #include <linux/kernel.h> #include <linux/usb.h> #include <linux/i2c.h> +#include <linux/jiffies.h> #include "em28xx.h" #include "tuner-xc2028.h" @@ -40,7 +41,7 @@ MODULE_PARM_DESC(i2c_scan, "scan i2c bus at insmod time"); static unsigned int i2c_debug; module_param(i2c_debug, int, 0644); -MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]"); +MODULE_PARM_DESC(i2c_debug, "i2c debug message level (1: normal debug, 2: show I2C transfers)"); /* * em2800_i2c_send_bytes() @@ -48,8 +49,8 @@ MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]"); */ static int em2800_i2c_send_bytes(struct em28xx *dev, u8 addr, u8 *buf, u16 len) { + unsigned long timeout = jiffies + msecs_to_jiffies(EM28XX_I2C_XFER_TIMEOUT); int ret; - int write_timeout; u8 b2[6]; if (len < 1 || len > 4) @@ -74,22 +75,26 @@ static int em2800_i2c_send_bytes(struct em28xx *dev, u8 addr, u8 *buf, u16 len) return (ret < 0) ? ret : -EIO; } /* wait for completion */ - for (write_timeout = EM2800_I2C_XFER_TIMEOUT; write_timeout > 0; - write_timeout -= 5) { + while (time_is_after_jiffies(timeout)) { ret = dev->em28xx_read_reg(dev, 0x05); - if (ret == 0x80 + len - 1) { + if (ret == 0x80 + len - 1) return len; - } else if (ret == 0x94 + len - 1) { - return -ENODEV; - } else if (ret < 0) { + if (ret == 0x94 + len - 1) { + if (i2c_debug == 1) + em28xx_warn("R05 returned 0x%02x: I2C timeout", + ret); + return -ENXIO; + } + if (ret < 0) { em28xx_warn("failed to get i2c transfer status from bridge register (error=%i)\n", ret); return ret; } msleep(5); } - em28xx_warn("write to i2c device at 0x%x timed out\n", addr); - return -EIO; + if (i2c_debug) + em28xx_warn("write to i2c device at 0x%x timed out\n", addr); + return -ETIMEDOUT; } /* @@ -98,9 +103,9 @@ static int em2800_i2c_send_bytes(struct em28xx *dev, u8 addr, u8 *buf, u16 len) */ static int em2800_i2c_recv_bytes(struct em28xx *dev, u8 addr, u8 *buf, u16 len) { + unsigned long timeout = jiffies + msecs_to_jiffies(EM28XX_I2C_XFER_TIMEOUT); u8 buf2[4]; int ret; - int read_timeout; int i; if (len < 1 || len > 4) @@ -117,22 +122,28 @@ static int em2800_i2c_recv_bytes(struct em28xx *dev, u8 addr, u8 *buf, u16 len) } /* wait for completion */ - for (read_timeout = EM2800_I2C_XFER_TIMEOUT; read_timeout > 0; - read_timeout -= 5) { + while (time_is_after_jiffies(timeout)) { ret = dev->em28xx_read_reg(dev, 0x05); - if (ret == 0x84 + len - 1) { + if (ret == 0x84 + len - 1) break; - } else if (ret == 0x94 + len - 1) { - return -ENODEV; - } else if (ret < 0) { + if (ret == 0x94 + len - 1) { + if (i2c_debug == 1) + em28xx_warn("R05 returned 0x%02x: I2C timeout", + ret); + return -ENXIO; + } + if (ret < 0) { em28xx_warn("failed to get i2c transfer status from bridge register (error=%i)\n", ret); return ret; } msleep(5); } - if (ret != 0x84 + len - 1) - em28xx_warn("read from i2c device at 0x%x timed out\n", addr); + if (ret != 0x84 + len - 1) { + if (i2c_debug) + em28xx_warn("read from i2c device at 0x%x timed out\n", + addr); + } /* get the received message */ ret = dev->em28xx_read_reg_req_len(dev, 0x00, 4-len, buf2, len); @@ -168,7 +179,8 @@ static int em2800_i2c_check_for_device(struct em28xx *dev, u8 addr) static int em28xx_i2c_send_bytes(struct em28xx *dev, u16 addr, u8 *buf, u16 len, int stop) { - int write_timeout, ret; + unsigned long timeout = jiffies + msecs_to_jiffies(EM28XX_I2C_XFER_TIMEOUT); + int ret; if (len < 1 || len > 64) return -EOPNOTSUPP; @@ -191,16 +203,19 @@ static int em28xx_i2c_send_bytes(struct em28xx *dev, u16 addr, u8 *buf, } } - /* Check success of the i2c operation */ - for (write_timeout = EM2800_I2C_XFER_TIMEOUT; write_timeout > 0; - write_timeout -= 5) { + /* wait for completion */ + while (time_is_after_jiffies(timeout)) { ret = dev->em28xx_read_reg(dev, 0x05); - if (ret == 0) { /* success */ + if (ret == 0) /* success */ return len; - } else if (ret == 0x10) { - return -ENODEV; - } else if (ret < 0) { - em28xx_warn("failed to read i2c transfer status from bridge (error=%i)\n", + if (ret == 0x10) { + if (i2c_debug == 1) + em28xx_warn("I2C transfer timeout on writing to addr 0x%02x", + addr); + return -ENXIO; + } + if (ret < 0) { + em28xx_warn("failed to get i2c transfer status from bridge register (error=%i)\n", ret); return ret; } @@ -211,8 +226,10 @@ static int em28xx_i2c_send_bytes(struct em28xx *dev, u16 addr, u8 *buf, * (even with high payload) ... */ } - em28xx_warn("write to i2c device at 0x%x timed out\n", addr); - return -EIO; + if (i2c_debug) + em28xx_warn("write to i2c device at 0x%x timed out (status=%i)\n", + addr, ret); + return -ETIMEDOUT; } /* @@ -242,26 +259,28 @@ static int em28xx_i2c_recv_bytes(struct em28xx *dev, u16 addr, u8 *buf, u16 len) * bytes if we are on bus B AND there was no write attempt to the * specified slave address before AND no device is present at the * requested slave address. - * Anyway, the next check will fail with -ENODEV in this case, so avoid + * Anyway, the next check will fail with -ENXIO in this case, so avoid * spamming the system log on device probing and do nothing here. */ /* Check success of the i2c operation */ ret = dev->em28xx_read_reg(dev, 0x05); + if (ret == 0) /* success */ + return len; if (ret < 0) { - em28xx_warn("failed to read i2c transfer status from bridge (error=%i)\n", + em28xx_warn("failed to get i2c transfer status from bridge register (error=%i)\n", ret); return ret; } - if (ret > 0) { - if (ret == 0x10) { - return -ENODEV; - } else { - em28xx_warn("unknown i2c error (status=%i)\n", ret); - return -EIO; - } + if (ret == 0x10) { + if (i2c_debug == 1) + em28xx_warn("I2C transfer timeout on writing to addr 0x%02x", + addr); + return -ENXIO; } - return len; + + em28xx_warn("unknown i2c error (status=%i)\n", ret); + return -ETIMEDOUT; } /* @@ -316,8 +335,12 @@ static int em25xx_bus_B_send_bytes(struct em28xx *dev, u16 addr, u8 *buf, */ if (!ret) return len; - else if (ret > 0) - return -ENODEV; + else if (ret > 0) { + if (i2c_debug == 1) + em28xx_warn("Bus B R08 returned 0x%02x: I2C timeout", + ret); + return -ENXIO; + } return ret; /* @@ -355,7 +378,7 @@ static int em25xx_bus_B_recv_bytes(struct em28xx *dev, u16 addr, u8 *buf, * bytes if we are on bus B AND there was no write attempt to the * specified slave address before AND no device is present at the * requested slave address. - * Anyway, the next check will fail with -ENODEV in this case, so avoid + * Anyway, the next check will fail with -ENXIO in this case, so avoid * spamming the system log on device probing and do nothing here. */ @@ -367,8 +390,12 @@ static int em25xx_bus_B_recv_bytes(struct em28xx *dev, u16 addr, u8 *buf, */ if (!ret) return len; - else if (ret > 0) - return -ENODEV; + else if (ret > 0) { + if (i2c_debug == 1) + em28xx_warn("Bus B R08 returned 0x%02x: I2C timeout", + ret); + return -ENXIO; + } return ret; /* @@ -409,10 +436,6 @@ static inline int i2c_check_for_device(struct em28xx_i2c_bus *i2c_bus, u16 addr) rc = em2800_i2c_check_for_device(dev, addr); else if (i2c_bus->algo_type == EM28XX_I2C_ALGO_EM25XX_BUS_B) rc = em25xx_bus_B_check_for_device(dev, addr); - if (rc == -ENODEV) { - if (i2c_debug) - printk(" no device\n"); - } return rc; } @@ -421,7 +444,7 @@ static inline int i2c_recv_bytes(struct em28xx_i2c_bus *i2c_bus, { struct em28xx *dev = i2c_bus->dev; u16 addr = msg.addr << 1; - int byte, rc = -EOPNOTSUPP; + int rc = -EOPNOTSUPP; if (i2c_bus->algo_type == EM28XX_I2C_ALGO_EM28XX) rc = em28xx_i2c_recv_bytes(dev, addr, msg.buf, msg.len); @@ -429,10 +452,6 @@ static inline int i2c_recv_bytes(struct em28xx_i2c_bus *i2c_bus, rc = em2800_i2c_recv_bytes(dev, addr, msg.buf, msg.len); else if (i2c_bus->algo_type == EM28XX_I2C_ALGO_EM25XX_BUS_B) rc = em25xx_bus_B_recv_bytes(dev, addr, msg.buf, msg.len); - if (i2c_debug) { - for (byte = 0; byte < msg.len; byte++) - printk(" %02x", msg.buf[byte]); - } return rc; } @@ -441,12 +460,8 @@ static inline int i2c_send_bytes(struct em28xx_i2c_bus *i2c_bus, { struct em28xx *dev = i2c_bus->dev; u16 addr = msg.addr << 1; - int byte, rc = -EOPNOTSUPP; + int rc = -EOPNOTSUPP; - if (i2c_debug) { - for (byte = 0; byte < msg.len; byte++) - printk(" %02x", msg.buf[byte]); - } if (i2c_bus->algo_type == EM28XX_I2C_ALGO_EM28XX) rc = em28xx_i2c_send_bytes(dev, addr, msg.buf, msg.len, stop); else if (i2c_bus->algo_type == EM28XX_I2C_ALGO_EM2800) @@ -491,33 +506,53 @@ static int em28xx_i2c_xfer(struct i2c_adapter *i2c_adap, } for (i = 0; i < num; i++) { addr = msgs[i].addr << 1; - if (i2c_debug) + if (i2c_debug > 1) printk(KERN_DEBUG "%s at %s: %s %s addr=%02x len=%d:", dev->name, __func__ , (msgs[i].flags & I2C_M_RD) ? "read" : "write", i == num - 1 ? "stop" : "nonstop", addr, msgs[i].len); - if (!msgs[i].len) { /* no len: check only for device presence */ + if (!msgs[i].len) { + /* + * no len: check only for device presence + * This code is only called during device probe. + */ rc = i2c_check_for_device(i2c_bus, addr); - if (rc == -ENODEV) { + if (rc < 0) { + if (rc == -ENXIO) { + if (i2c_debug > 1) + printk(KERN_CONT " no device\n"); + rc = -ENODEV; + } else { + if (i2c_debug > 1) + printk(KERN_CONT " ERROR: %i\n", rc); + } rt_mutex_unlock(&dev->i2c_bus_lock); return rc; } } else if (msgs[i].flags & I2C_M_RD) { /* read bytes */ rc = i2c_recv_bytes(i2c_bus, msgs[i]); + + if (i2c_debug > 1 && rc >= 0) + printk(KERN_CONT " %*ph", + msgs[i].len, msgs[i].buf); } else { + if (i2c_debug > 1) + printk(KERN_CONT " %*ph", + msgs[i].len, msgs[i].buf); + /* write bytes */ rc = i2c_send_bytes(i2c_bus, msgs[i], i == num - 1); } if (rc < 0) { - if (i2c_debug) - printk(" ERROR: %i\n", rc); + if (i2c_debug > 1) + printk(KERN_CONT " ERROR: %i\n", rc); rt_mutex_unlock(&dev->i2c_bus_lock); return rc; } - if (i2c_debug) - printk("\n"); + if (i2c_debug > 1) + printk(KERN_CONT "\n"); } rt_mutex_unlock(&dev->i2c_bus_lock); @@ -600,7 +635,7 @@ static int em28xx_i2c_eeprom(struct em28xx *dev, unsigned bus, * calculation and returned device dataset. Simplifies the code a lot, * but we might have to deal with multiple sizes in the future ! */ - int i, err; + int err; struct em28xx_eeprom *dev_config; u8 buf, *data; @@ -631,20 +666,14 @@ static int em28xx_i2c_eeprom(struct em28xx *dev, unsigned bus, goto error; } - /* Display eeprom content */ - for (i = 0; i < len; i++) { - if (0 == (i % 16)) { - if (dev->eeprom_addrwidth_16bit) - em28xx_info("i2c eeprom %04x:", i); - else - em28xx_info("i2c eeprom %02x:", i); - } - printk(" %02x", data[i]); - if (15 == (i % 16)) - printk("\n"); + if (i2c_debug) { + /* Display eeprom content */ + print_hex_dump(KERN_INFO, "eeprom ", DUMP_PREFIX_OFFSET, + 16, 1, data, len, true); + + if (dev->eeprom_addrwidth_16bit) + em28xx_info("eeprom %06x: ... (skipped)\n", 256); } - if (dev->eeprom_addrwidth_16bit) - em28xx_info("i2c eeprom %04x: ... (skipped)\n", i); if (dev->eeprom_addrwidth_16bit && data[0] == 0x26 && data[3] == 0x00) { @@ -736,10 +765,16 @@ static int em28xx_i2c_eeprom(struct em28xx *dev, unsigned bus, em28xx_info("\tAC97 audio (5 sample rates)\n"); break; case 2: - em28xx_info("\tI2S audio, sample rate=32k\n"); + if (dev->chip_id < CHIP_ID_EM2860) + em28xx_info("\tI2S audio, sample rate=32k\n"); + else + em28xx_info("\tI2S audio, 3 sample rates\n"); break; case 3: - em28xx_info("\tI2S audio, 3 sample rates\n"); + if (dev->chip_id < CHIP_ID_EM2860) + em28xx_info("\tI2S audio, 3 sample rates\n"); + else + em28xx_info("\tI2S audio, 5 sample rates\n"); break; } diff --git a/drivers/media/usb/em28xx/em28xx-input.c b/drivers/media/usb/em28xx/em28xx-input.c index ea181e4b68c5..18f65d89d4bc 100644 --- a/drivers/media/usb/em28xx/em28xx-input.c +++ b/drivers/media/usb/em28xx/em28xx-input.c @@ -30,8 +30,9 @@ #include "em28xx.h" -#define EM28XX_SNAPSHOT_KEY KEY_CAMERA -#define EM28XX_SBUTTON_QUERY_INTERVAL 500 +#define EM28XX_SNAPSHOT_KEY KEY_CAMERA +#define EM28XX_BUTTONS_DEBOUNCED_QUERY_INTERVAL 500 /* [ms] */ +#define EM28XX_BUTTONS_VOLATILE_QUERY_INTERVAL 100 /* [ms] */ static unsigned int ir_debug; module_param(ir_debug, int, 0644); @@ -442,6 +443,7 @@ static int em28xx_ir_change_protocol(struct rc_dev *rc_dev, u64 *rc_type) case CHIP_ID_EM2884: case CHIP_ID_EM2874: case CHIP_ID_EM28174: + case CHIP_ID_EM28178: return em2874_ir_change_protocol(rc_dev, rc_type); default: printk("Unrecognized em28xx chip id 0x%02x: IR not supported\n", @@ -470,54 +472,98 @@ static int em28xx_probe_i2c_ir(struct em28xx *dev) } /********************************************************** - Handle Webcam snapshot button + Handle buttons **********************************************************/ -static void em28xx_query_sbutton(struct work_struct *work) +static void em28xx_query_buttons(struct work_struct *work) { - /* Poll the register and see if the button is depressed */ struct em28xx *dev = - container_of(work, struct em28xx, sbutton_query_work.work); - int ret; - - ret = em28xx_read_reg(dev, EM28XX_R0C_USBSUSP); - - if (ret & EM28XX_R0C_USBSUSP_SNAPSHOT) { - u8 cleared; - /* Button is depressed, clear the register */ - cleared = ((u8) ret) & ~EM28XX_R0C_USBSUSP_SNAPSHOT; - em28xx_write_regs(dev, EM28XX_R0C_USBSUSP, &cleared, 1); - - /* Not emulate the keypress */ - input_report_key(dev->sbutton_input_dev, EM28XX_SNAPSHOT_KEY, - 1); - /* Now unpress the key */ - input_report_key(dev->sbutton_input_dev, EM28XX_SNAPSHOT_KEY, - 0); + container_of(work, struct em28xx, buttons_query_work.work); + u8 i, j; + int regval; + bool is_pressed, was_pressed; + const struct em28xx_led *led; + + /* Poll and evaluate all addresses */ + for (i = 0; i < dev->num_button_polling_addresses; i++) { + /* Read value from register */ + regval = em28xx_read_reg(dev, dev->button_polling_addresses[i]); + if (regval < 0) + continue; + /* Check states of the buttons and act */ + j = 0; + while (dev->board.buttons[j].role >= 0 && + dev->board.buttons[j].role < EM28XX_NUM_BUTTON_ROLES) { + struct em28xx_button *button = &dev->board.buttons[j]; + /* Check if button uses the current address */ + if (button->reg_r != dev->button_polling_addresses[i]) { + j++; + continue; + } + /* Determine if button is and was pressed last time */ + is_pressed = regval & button->mask; + was_pressed = dev->button_polling_last_values[i] + & button->mask; + if (button->inverted) { + is_pressed = !is_pressed; + was_pressed = !was_pressed; + } + /* Clear button state (if needed) */ + if (is_pressed && button->reg_clearing) + em28xx_write_reg(dev, button->reg_clearing, + (~regval & button->mask) + | (regval & ~button->mask)); + /* Handle button state */ + if (!is_pressed || was_pressed) { + j++; + continue; + } + switch (button->role) { + case EM28XX_BUTTON_SNAPSHOT: + /* Emulate the keypress */ + input_report_key(dev->sbutton_input_dev, + EM28XX_SNAPSHOT_KEY, 1); + /* Unpress the key */ + input_report_key(dev->sbutton_input_dev, + EM28XX_SNAPSHOT_KEY, 0); + break; + case EM28XX_BUTTON_ILLUMINATION: + led = em28xx_find_led(dev, + EM28XX_LED_ILLUMINATION); + /* Switch illumination LED on/off */ + if (led) + em28xx_toggle_reg_bits(dev, + led->gpio_reg, + led->gpio_mask); + break; + default: + WARN_ONCE(1, "BUG: unhandled button role."); + } + /* Next button */ + j++; + } + /* Save current value for comparison during the next polling */ + dev->button_polling_last_values[i] = regval; } - /* Schedule next poll */ - schedule_delayed_work(&dev->sbutton_query_work, - msecs_to_jiffies(EM28XX_SBUTTON_QUERY_INTERVAL)); + schedule_delayed_work(&dev->buttons_query_work, + msecs_to_jiffies(dev->button_polling_interval)); } -static void em28xx_register_snapshot_button(struct em28xx *dev) +static int em28xx_register_snapshot_button(struct em28xx *dev) { struct input_dev *input_dev; int err; em28xx_info("Registering snapshot button...\n"); input_dev = input_allocate_device(); - if (!input_dev) { - em28xx_errdev("input_allocate_device failed\n"); - return; - } + if (!input_dev) + return -ENOMEM; usb_make_path(dev->udev, dev->snapshot_button_path, sizeof(dev->snapshot_button_path)); strlcat(dev->snapshot_button_path, "/sbutton", sizeof(dev->snapshot_button_path)); - INIT_DELAYED_WORK(&dev->sbutton_query_work, em28xx_query_sbutton); input_dev->name = "em28xx snapshot button"; input_dev->phys = dev->snapshot_button_path; @@ -535,25 +581,86 @@ static void em28xx_register_snapshot_button(struct em28xx *dev) if (err) { em28xx_errdev("input_register_device failed\n"); input_free_device(input_dev); - return; + return err; } dev->sbutton_input_dev = input_dev; - schedule_delayed_work(&dev->sbutton_query_work, - msecs_to_jiffies(EM28XX_SBUTTON_QUERY_INTERVAL)); - return; + return 0; +} +static void em28xx_init_buttons(struct em28xx *dev) +{ + u8 i = 0, j = 0; + bool addr_new = 0; + + dev->button_polling_interval = EM28XX_BUTTONS_DEBOUNCED_QUERY_INTERVAL; + while (dev->board.buttons[i].role >= 0 && + dev->board.buttons[i].role < EM28XX_NUM_BUTTON_ROLES) { + struct em28xx_button *button = &dev->board.buttons[i]; + /* Check if polling address is already on the list */ + addr_new = 1; + for (j = 0; j < dev->num_button_polling_addresses; j++) { + if (button->reg_r == dev->button_polling_addresses[j]) { + addr_new = 0; + break; + } + } + /* Check if max. number of polling addresses is exceeded */ + if (addr_new && dev->num_button_polling_addresses + >= EM28XX_NUM_BUTTON_ADDRESSES_MAX) { + WARN_ONCE(1, "BUG: maximum number of button polling addresses exceeded."); + goto next_button; + } + /* Button role specific checks and actions */ + if (button->role == EM28XX_BUTTON_SNAPSHOT) { + /* Register input device */ + if (em28xx_register_snapshot_button(dev) < 0) + goto next_button; + } else if (button->role == EM28XX_BUTTON_ILLUMINATION) { + /* Check sanity */ + if (!em28xx_find_led(dev, EM28XX_LED_ILLUMINATION)) { + em28xx_errdev("BUG: illumination button defined, but no illumination LED.\n"); + goto next_button; + } + } + /* Add read address to list of polling addresses */ + if (addr_new) { + unsigned int index = dev->num_button_polling_addresses; + dev->button_polling_addresses[index] = button->reg_r; + dev->num_button_polling_addresses++; + } + /* Reduce polling interval if necessary */ + if (!button->reg_clearing) + dev->button_polling_interval = + EM28XX_BUTTONS_VOLATILE_QUERY_INTERVAL; +next_button: + /* Next button */ + i++; + } + + /* Start polling */ + if (dev->num_button_polling_addresses) { + memset(dev->button_polling_last_values, 0, + EM28XX_NUM_BUTTON_ADDRESSES_MAX); + INIT_DELAYED_WORK(&dev->buttons_query_work, + em28xx_query_buttons); + schedule_delayed_work(&dev->buttons_query_work, + msecs_to_jiffies(dev->button_polling_interval)); + } } -static void em28xx_deregister_snapshot_button(struct em28xx *dev) +static void em28xx_shutdown_buttons(struct em28xx *dev) { + /* Cancel polling */ + cancel_delayed_work_sync(&dev->buttons_query_work); + /* Clear polling addresses list */ + dev->num_button_polling_addresses = 0; + /* Deregister input devices */ if (dev->sbutton_input_dev != NULL) { em28xx_info("Deregistering snapshot button\n"); - cancel_delayed_work_sync(&dev->sbutton_query_work); input_unregister_device(dev->sbutton_input_dev); dev->sbutton_input_dev = NULL; } - return; } static int em28xx_ir_init(struct em28xx *dev) @@ -564,8 +671,13 @@ static int em28xx_ir_init(struct em28xx *dev) u64 rc_type; u16 i2c_rc_dev_addr = 0; - if (dev->board.has_snapshot_button) - em28xx_register_snapshot_button(dev); + if (dev->is_audio_only) { + /* Shouldn't initialize IR for this interface */ + return 0; + } + + if (dev->board.buttons) + em28xx_init_buttons(dev); if (dev->board.has_ir_i2c) { i2c_rc_dev_addr = em28xx_probe_i2c_ir(dev); @@ -583,6 +695,8 @@ static int em28xx_ir_init(struct em28xx *dev) return 0; } + em28xx_info("Registering input extension\n"); + ir = kzalloc(sizeof(*ir), GFP_KERNEL); rc = rc_allocate_device(); if (!ir || !rc) @@ -633,6 +747,7 @@ static int em28xx_ir_init(struct em28xx *dev) case CHIP_ID_EM2884: case CHIP_ID_EM2874: case CHIP_ID_EM28174: + case CHIP_ID_EM28178: ir->get_key = em2874_polling_getkey; rc->allowed_protos = RC_BIT_RC5 | RC_BIT_NEC | RC_BIT_RC6_0; @@ -675,6 +790,8 @@ static int em28xx_ir_init(struct em28xx *dev) if (err) goto error; + em28xx_info("Input extension successfully initalized\n"); + return 0; error: @@ -688,7 +805,14 @@ static int em28xx_ir_fini(struct em28xx *dev) { struct em28xx_IR *ir = dev->ir; - em28xx_deregister_snapshot_button(dev); + if (dev->is_audio_only) { + /* Shouldn't initialize IR for this interface */ + return 0; + } + + em28xx_info("Closing input extension"); + + em28xx_shutdown_buttons(dev); /* skip detach on non attached boards */ if (!ir) @@ -722,7 +846,8 @@ static void __exit em28xx_rc_unregister(void) MODULE_LICENSE("GPL"); MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@redhat.com>"); -MODULE_DESCRIPTION("Em28xx Input driver"); +MODULE_DESCRIPTION(DRIVER_DESC " - input interface"); +MODULE_VERSION(EM28XX_VERSION); module_init(em28xx_rc_register); module_exit(em28xx_rc_unregister); diff --git a/drivers/media/usb/em28xx/em28xx-reg.h b/drivers/media/usb/em28xx/em28xx-reg.h index 0e0477847965..311fb349dafa 100644 --- a/drivers/media/usb/em28xx/em28xx-reg.h +++ b/drivers/media/usb/em28xx/em28xx-reg.h @@ -25,10 +25,12 @@ #define EM28XX_R00_CHIPCFG 0x00 /* em28xx Chip Configuration 0x00 */ -#define EM28XX_CHIPCFG_VENDOR_AUDIO 0x80 -#define EM28XX_CHIPCFG_I2S_VOLUME_CAPABLE 0x40 -#define EM28XX_CHIPCFG_I2S_5_SAMPRATES 0x30 -#define EM28XX_CHIPCFG_I2S_3_SAMPRATES 0x20 +#define EM2860_CHIPCFG_VENDOR_AUDIO 0x80 +#define EM2860_CHIPCFG_I2S_VOLUME_CAPABLE 0x40 +#define EM2820_CHIPCFG_I2S_3_SAMPRATES 0x30 +#define EM2860_CHIPCFG_I2S_5_SAMPRATES 0x30 +#define EM2820_CHIPCFG_I2S_1_SAMPRATE 0x20 +#define EM2860_CHIPCFG_I2S_3_SAMPRATES 0x20 #define EM28XX_CHIPCFG_AC97 0x10 #define EM28XX_CHIPCFG_AUDIOMASK 0x30 @@ -245,6 +247,7 @@ enum em28xx_chip_id { CHIP_ID_EM2874 = 65, CHIP_ID_EM2884 = 68, CHIP_ID_EM28174 = 113, + CHIP_ID_EM28178 = 114, }; /* diff --git a/drivers/media/usb/em28xx/em28xx-v4l.h b/drivers/media/usb/em28xx/em28xx-v4l.h new file mode 100644 index 000000000000..bce438691e0e --- /dev/null +++ b/drivers/media/usb/em28xx/em28xx-v4l.h @@ -0,0 +1,20 @@ +/* + em28xx-video.c - driver for Empia EM2800/EM2820/2840 USB + video capture devices + + Copyright (C) 2013-2014 Mauro Carvalho Chehab <m.chehab@samsung.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation version 2 of the License. + + 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. + */ + + +int em28xx_start_analog_streaming(struct vb2_queue *vq, unsigned int count); +int em28xx_stop_vbi_streaming(struct vb2_queue *vq); +extern struct vb2_ops em28xx_vbi_qops; diff --git a/drivers/media/usb/em28xx/em28xx-vbi.c b/drivers/media/usb/em28xx/em28xx-vbi.c index 39f39c527c13..db3d655600df 100644 --- a/drivers/media/usb/em28xx/em28xx-vbi.c +++ b/drivers/media/usb/em28xx/em28xx-vbi.c @@ -27,6 +27,7 @@ #include <linux/init.h> #include "em28xx.h" +#include "em28xx-v4l.h" static unsigned int vbibufs = 5; module_param(vbibufs, int, 0644); diff --git a/drivers/media/usb/em28xx/em28xx-video.c b/drivers/media/usb/em28xx/em28xx-video.c index dd19c9ff76e0..c3c928937dcd 100644 --- a/drivers/media/usb/em28xx/em28xx-video.c +++ b/drivers/media/usb/em28xx/em28xx-video.c @@ -38,9 +38,11 @@ #include <linux/slab.h> #include "em28xx.h" +#include "em28xx-v4l.h" #include <media/v4l2-common.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-event.h> +#include <media/v4l2-clk.h> #include <media/msp3400.h> #include <media/tuner.h> @@ -49,19 +51,23 @@ "Mauro Carvalho Chehab <mchehab@infradead.org>, " \ "Sascha Sommer <saschasommer@freenet.de>" -#define DRIVER_DESC "Empia em28xx based USB video device driver" +static unsigned int isoc_debug; +module_param(isoc_debug, int, 0644); +MODULE_PARM_DESC(isoc_debug, "enable debug messages [isoc transfers]"); + +static unsigned int disable_vbi; +module_param(disable_vbi, int, 0644); +MODULE_PARM_DESC(disable_vbi, "disable vbi support"); -#define EM28XX_VERSION "0.2.0" +static int alt; +module_param(alt, int, 0644); +MODULE_PARM_DESC(alt, "alternate setting to use for video endpoint"); #define em28xx_videodbg(fmt, arg...) do {\ if (video_debug) \ printk(KERN_INFO "%s %s :"fmt, \ dev->name, __func__ , ##arg); } while (0) -static unsigned int isoc_debug; -module_param(isoc_debug, int, 0644); -MODULE_PARM_DESC(isoc_debug, "enable debug messages [isoc transfers]"); - #define em28xx_isocdbg(fmt, arg...) \ do {\ if (isoc_debug) { \ @@ -71,7 +77,7 @@ do {\ } while (0) MODULE_AUTHOR(DRIVER_AUTHOR); -MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_DESCRIPTION(DRIVER_DESC " - v4l2 interface"); MODULE_LICENSE("GPL"); MODULE_VERSION(EM28XX_VERSION); @@ -135,6 +141,257 @@ static struct em28xx_fmt format[] = { }, }; +static int em28xx_vbi_supported(struct em28xx *dev) +{ + /* Modprobe option to manually disable */ + if (disable_vbi == 1) + return 0; + + if (dev->board.is_webcam) + return 0; + + /* FIXME: check subdevices for VBI support */ + + if (dev->chip_id == CHIP_ID_EM2860 || + dev->chip_id == CHIP_ID_EM2883) + return 1; + + /* Version of em28xx that does not support VBI */ + return 0; +} + +/* + * em28xx_wake_i2c() + * configure i2c attached devices + */ +static void em28xx_wake_i2c(struct em28xx *dev) +{ + v4l2_device_call_all(&dev->v4l2_dev, 0, core, reset, 0); + v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_routing, + INPUT(dev->ctl_input)->vmux, 0, 0); + v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_stream, 0); +} + +static int em28xx_colorlevels_set_default(struct em28xx *dev) +{ + em28xx_write_reg(dev, EM28XX_R20_YGAIN, CONTRAST_DEFAULT); + em28xx_write_reg(dev, EM28XX_R21_YOFFSET, BRIGHTNESS_DEFAULT); + em28xx_write_reg(dev, EM28XX_R22_UVGAIN, SATURATION_DEFAULT); + em28xx_write_reg(dev, EM28XX_R23_UOFFSET, BLUE_BALANCE_DEFAULT); + em28xx_write_reg(dev, EM28XX_R24_VOFFSET, RED_BALANCE_DEFAULT); + em28xx_write_reg(dev, EM28XX_R25_SHARPNESS, SHARPNESS_DEFAULT); + + em28xx_write_reg(dev, EM28XX_R14_GAMMA, 0x20); + em28xx_write_reg(dev, EM28XX_R15_RGAIN, 0x20); + em28xx_write_reg(dev, EM28XX_R16_GGAIN, 0x20); + em28xx_write_reg(dev, EM28XX_R17_BGAIN, 0x20); + em28xx_write_reg(dev, EM28XX_R18_ROFFSET, 0x00); + em28xx_write_reg(dev, EM28XX_R19_GOFFSET, 0x00); + return em28xx_write_reg(dev, EM28XX_R1A_BOFFSET, 0x00); +} + +static int em28xx_set_outfmt(struct em28xx *dev) +{ + int ret; + u8 fmt, vinctrl; + + fmt = dev->format->reg; + if (!dev->is_em25xx) + fmt |= 0x20; + /* + * NOTE: it's not clear if this is really needed ! + * The datasheets say bit 5 is a reserved bit and devices seem to work + * fine without it. But the Windows driver sets it for em2710/50+em28xx + * devices and we've always been setting it, too. + * + * em2765 (em25xx, em276x/7x/8x) devices do NOT work with this bit set, + * it's likely used for an additional (compressed ?) format there. + */ + ret = em28xx_write_reg(dev, EM28XX_R27_OUTFMT, fmt); + if (ret < 0) + return ret; + + ret = em28xx_write_reg(dev, EM28XX_R10_VINMODE, dev->vinmode); + if (ret < 0) + return ret; + + vinctrl = dev->vinctl; + if (em28xx_vbi_supported(dev) == 1) { + vinctrl |= EM28XX_VINCTRL_VBI_RAW; + em28xx_write_reg(dev, EM28XX_R34_VBI_START_H, 0x00); + em28xx_write_reg(dev, EM28XX_R36_VBI_WIDTH, dev->vbi_width/4); + em28xx_write_reg(dev, EM28XX_R37_VBI_HEIGHT, dev->vbi_height); + if (dev->norm & V4L2_STD_525_60) { + /* NTSC */ + em28xx_write_reg(dev, EM28XX_R35_VBI_START_V, 0x09); + } else if (dev->norm & V4L2_STD_625_50) { + /* PAL */ + em28xx_write_reg(dev, EM28XX_R35_VBI_START_V, 0x07); + } + } + + return em28xx_write_reg(dev, EM28XX_R11_VINCTRL, vinctrl); +} + +static int em28xx_accumulator_set(struct em28xx *dev, u8 xmin, u8 xmax, + u8 ymin, u8 ymax) +{ + em28xx_videodbg("em28xx Scale: (%d,%d)-(%d,%d)\n", + xmin, ymin, xmax, ymax); + + em28xx_write_regs(dev, EM28XX_R28_XMIN, &xmin, 1); + em28xx_write_regs(dev, EM28XX_R29_XMAX, &xmax, 1); + em28xx_write_regs(dev, EM28XX_R2A_YMIN, &ymin, 1); + return em28xx_write_regs(dev, EM28XX_R2B_YMAX, &ymax, 1); +} + +static void em28xx_capture_area_set(struct em28xx *dev, u8 hstart, u8 vstart, + u16 width, u16 height) +{ + u8 cwidth = width >> 2; + u8 cheight = height >> 2; + u8 overflow = (height >> 9 & 0x02) | (width >> 10 & 0x01); + /* NOTE: size limit: 2047x1023 = 2MPix */ + + em28xx_videodbg("capture area set to (%d,%d): %dx%d\n", + hstart, vstart, + ((overflow & 2) << 9 | cwidth << 2), + ((overflow & 1) << 10 | cheight << 2)); + + em28xx_write_regs(dev, EM28XX_R1C_HSTART, &hstart, 1); + em28xx_write_regs(dev, EM28XX_R1D_VSTART, &vstart, 1); + em28xx_write_regs(dev, EM28XX_R1E_CWIDTH, &cwidth, 1); + em28xx_write_regs(dev, EM28XX_R1F_CHEIGHT, &cheight, 1); + em28xx_write_regs(dev, EM28XX_R1B_OFLOW, &overflow, 1); + + /* FIXME: function/meaning of these registers ? */ + /* FIXME: align width+height to multiples of 4 ?! */ + if (dev->is_em25xx) { + em28xx_write_reg(dev, 0x34, width >> 4); + em28xx_write_reg(dev, 0x35, height >> 4); + } +} + +static int em28xx_scaler_set(struct em28xx *dev, u16 h, u16 v) +{ + u8 mode; + /* the em2800 scaler only supports scaling down to 50% */ + + if (dev->board.is_em2800) { + mode = (v ? 0x20 : 0x00) | (h ? 0x10 : 0x00); + } else { + u8 buf[2]; + + buf[0] = h; + buf[1] = h >> 8; + em28xx_write_regs(dev, EM28XX_R30_HSCALELOW, (char *)buf, 2); + + buf[0] = v; + buf[1] = v >> 8; + em28xx_write_regs(dev, EM28XX_R32_VSCALELOW, (char *)buf, 2); + /* it seems that both H and V scalers must be active + to work correctly */ + mode = (h || v) ? 0x30 : 0x00; + } + return em28xx_write_reg_bits(dev, EM28XX_R26_COMPR, mode, 0x30); +} + +/* FIXME: this only function read values from dev */ +static int em28xx_resolution_set(struct em28xx *dev) +{ + int width, height; + width = norm_maxw(dev); + height = norm_maxh(dev); + + /* Properly setup VBI */ + dev->vbi_width = 720; + if (dev->norm & V4L2_STD_525_60) + dev->vbi_height = 12; + else + dev->vbi_height = 18; + + em28xx_set_outfmt(dev); + + em28xx_accumulator_set(dev, 1, (width - 4) >> 2, 1, (height - 4) >> 2); + + /* If we don't set the start position to 2 in VBI mode, we end up + with line 20/21 being YUYV encoded instead of being in 8-bit + greyscale. The core of the issue is that line 21 (and line 23 for + PAL WSS) are inside of active video region, and as a result they + get the pixelformatting associated with that area. So by cropping + it out, we end up with the same format as the rest of the VBI + region */ + if (em28xx_vbi_supported(dev) == 1) + em28xx_capture_area_set(dev, 0, 2, width, height); + else + em28xx_capture_area_set(dev, 0, 0, width, height); + + return em28xx_scaler_set(dev, dev->hscale, dev->vscale); +} + +/* Set USB alternate setting for analog video */ +static int em28xx_set_alternate(struct em28xx *dev) +{ + int errCode; + int i; + unsigned int min_pkt_size = dev->width * 2 + 4; + + /* NOTE: for isoc transfers, only alt settings > 0 are allowed + bulk transfers seem to work only with alt=0 ! */ + dev->alt = 0; + if ((alt > 0) && (alt < dev->num_alt)) { + em28xx_videodbg("alternate forced to %d\n", dev->alt); + dev->alt = alt; + goto set_alt; + } + if (dev->analog_xfer_bulk) + goto set_alt; + + /* When image size is bigger than a certain value, + the frame size should be increased, otherwise, only + green screen will be received. + */ + if (dev->width * 2 * dev->height > 720 * 240 * 2) + min_pkt_size *= 2; + + for (i = 0; i < dev->num_alt; i++) { + /* stop when the selected alt setting offers enough bandwidth */ + if (dev->alt_max_pkt_size_isoc[i] >= min_pkt_size) { + dev->alt = i; + break; + /* otherwise make sure that we end up with the maximum bandwidth + because the min_pkt_size equation might be wrong... + */ + } else if (dev->alt_max_pkt_size_isoc[i] > + dev->alt_max_pkt_size_isoc[dev->alt]) + dev->alt = i; + } + +set_alt: + /* NOTE: for bulk transfers, we need to call usb_set_interface() + * even if the previous settings were the same. Otherwise streaming + * fails with all urbs having status = -EOVERFLOW ! */ + if (dev->analog_xfer_bulk) { + dev->max_pkt_size = 512; /* USB 2.0 spec */ + dev->packet_multiplier = EM28XX_BULK_PACKET_MULTIPLIER; + } else { /* isoc */ + em28xx_videodbg("minimum isoc packet size: %u (alt=%d)\n", + min_pkt_size, dev->alt); + dev->max_pkt_size = + dev->alt_max_pkt_size_isoc[dev->alt]; + dev->packet_multiplier = EM28XX_NUM_ISOC_PACKETS; + } + em28xx_videodbg("setting alternate %d with wMaxPacketSize=%u\n", + dev->alt, dev->max_pkt_size); + errCode = usb_set_interface(dev->udev, dev->ifnum, dev->alt); + if (errCode < 0) { + em28xx_errdev("cannot change alternate number to %d (error=%i)\n", + dev->alt, errCode); + return errCode; + } + return 0; +} + /* ------------------------------------------------------------------ DMA and thread functions ------------------------------------------------------------------*/ @@ -763,7 +1020,7 @@ static struct vb2_ops em28xx_video_qops = { .wait_finish = vb2_ops_wait_finish, }; -int em28xx_vb2_setup(struct em28xx *dev) +static int em28xx_vb2_setup(struct em28xx *dev) { int rc; struct vb2_queue *q; @@ -831,7 +1088,7 @@ static void video_mux(struct em28xx *dev, int index) em28xx_audio_analog_set(dev); } -void em28xx_ctrl_notify(struct v4l2_ctrl *ctrl, void *priv) +static void em28xx_ctrl_notify(struct v4l2_ctrl *ctrl, void *priv) { struct em28xx *dev = priv; @@ -890,7 +1147,7 @@ static int em28xx_s_ctrl(struct v4l2_ctrl *ctrl) return (ret < 0) ? ret : 0; } -const struct v4l2_ctrl_ops em28xx_ctrl_ops = { +static const struct v4l2_ctrl_ops em28xx_ctrl_ops = { .s_ctrl = em28xx_s_ctrl, }; @@ -1368,7 +1625,7 @@ static int vidioc_g_register(struct file *file, void *priv, reg->val = ret; } else { __le16 val = 0; - ret = em28xx_read_reg_req_len(dev, USB_REQ_GET_STATUS, + ret = dev->em28xx_read_reg_req_len(dev, USB_REQ_GET_STATUS, reg->reg, (char *)&val, 2); if (ret < 0) return ret; @@ -1570,6 +1827,10 @@ static int em28xx_v4l2_open(struct file *filp) case VFL_TYPE_VBI: fh_type = V4L2_BUF_TYPE_VBI_CAPTURE; break; + case VFL_TYPE_RADIO: + break; + default: + return -EINVAL; } em28xx_videodbg("open dev=%s type=%s users=%d\n", @@ -1590,15 +1851,17 @@ static int em28xx_v4l2_open(struct file *filp) fh->type = fh_type; filp->private_data = fh; - if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && dev->users == 0) { + if (dev->users == 0) { em28xx_set_mode(dev, EM28XX_ANALOG_MODE); - em28xx_resolution_set(dev); - /* Needed, since GPIO might have disabled power of - some i2c device + if (vdev->vfl_type != VFL_TYPE_RADIO) + em28xx_resolution_set(dev); + + /* + * Needed, since GPIO might have disabled power + * of some i2c devices */ em28xx_wake_i2c(dev); - } if (vdev->vfl_type == VFL_TYPE_RADIO) { @@ -1615,40 +1878,59 @@ static int em28xx_v4l2_open(struct file *filp) } /* - * em28xx_realease_resources() + * em28xx_v4l2_fini() * unregisters the v4l2,i2c and usb devices * called when the device gets disconected or at module unload */ -void em28xx_release_analog_resources(struct em28xx *dev) +static int em28xx_v4l2_fini(struct em28xx *dev) { + if (dev->is_audio_only) { + /* Shouldn't initialize IR for this interface */ + return 0; + } + + if (!dev->has_video) { + /* This device does not support the v4l2 extension */ + return 0; + } - /*FIXME: I2C IR should be disconnected */ + em28xx_info("Closing video extension"); + + mutex_lock(&dev->lock); + + v4l2_device_disconnect(&dev->v4l2_dev); + + em28xx_uninit_usb_xfer(dev, EM28XX_ANALOG_MODE); if (dev->radio_dev) { - if (video_is_registered(dev->radio_dev)) - video_unregister_device(dev->radio_dev); - else - video_device_release(dev->radio_dev); - dev->radio_dev = NULL; + em28xx_info("V4L2 device %s deregistered\n", + video_device_node_name(dev->radio_dev)); + video_unregister_device(dev->radio_dev); } if (dev->vbi_dev) { em28xx_info("V4L2 device %s deregistered\n", video_device_node_name(dev->vbi_dev)); - if (video_is_registered(dev->vbi_dev)) - video_unregister_device(dev->vbi_dev); - else - video_device_release(dev->vbi_dev); - dev->vbi_dev = NULL; + video_unregister_device(dev->vbi_dev); } if (dev->vdev) { em28xx_info("V4L2 device %s deregistered\n", video_device_node_name(dev->vdev)); - if (video_is_registered(dev->vdev)) - video_unregister_device(dev->vdev); - else - video_device_release(dev->vdev); - dev->vdev = NULL; + video_unregister_device(dev->vdev); } + + if (dev->clk) { + v4l2_clk_unregister_fixed(dev->clk); + dev->clk = NULL; + } + + v4l2_ctrl_handler_free(&dev->ctrl_handler); + v4l2_device_unregister(&dev->v4l2_dev); + + if (dev->users) + em28xx_warn("Device is open ! Memory deallocation is deferred on last close.\n"); + mutex_unlock(&dev->lock); + + return 0; } /* @@ -1668,14 +1950,10 @@ static int em28xx_v4l2_close(struct file *filp) mutex_lock(&dev->lock); if (dev->users == 1) { - /* the device is already disconnect, - free the remaining resources */ + /* free the remaining resources if device is disconnected */ if (dev->disconnected) { - em28xx_release_resources(dev); kfree(dev->alt_max_pkt_size_isoc); - mutex_unlock(&dev->lock); - kfree(dev); - return 0; + goto exit; } /* Save some power by putting tuner to sleep */ @@ -1694,11 +1972,29 @@ static int em28xx_v4l2_close(struct file *filp) } } +exit: dev->users--; mutex_unlock(&dev->lock); return 0; } +/* + * em28xx_videodevice_release() + * called when the last user of the video device exits and frees the memeory + */ +static void em28xx_videodevice_release(struct video_device *vdev) +{ + struct em28xx *dev = video_get_drvdata(vdev); + + video_device_release(vdev); + if (vdev == dev->vdev) + dev->vdev = NULL; + else if (vdev == dev->vbi_dev) + dev->vbi_dev = NULL; + else if (vdev == dev->radio_dev) + dev->radio_dev = NULL; +} + static const struct v4l2_file_operations em28xx_v4l_fops = { .owner = THIS_MODULE, .open = em28xx_v4l2_open, @@ -1753,11 +2049,10 @@ static const struct v4l2_ioctl_ops video_ioctl_ops = { }; static const struct video_device em28xx_video_template = { - .fops = &em28xx_v4l_fops, - .release = video_device_release_empty, - .ioctl_ops = &video_ioctl_ops, - - .tvnorms = V4L2_STD_ALL, + .fops = &em28xx_v4l_fops, + .ioctl_ops = &video_ioctl_ops, + .release = em28xx_videodevice_release, + .tvnorms = V4L2_STD_ALL, }; static const struct v4l2_file_operations radio_fops = { @@ -1783,14 +2078,30 @@ static const struct v4l2_ioctl_ops radio_ioctl_ops = { }; static struct video_device em28xx_radio_template = { - .name = "em28xx-radio", - .fops = &radio_fops, - .ioctl_ops = &radio_ioctl_ops, + .fops = &radio_fops, + .ioctl_ops = &radio_ioctl_ops, + .release = em28xx_videodevice_release, }; -/******************************** usb interface ******************************/ +/* I2C possible address to saa7115, tvp5150, msp3400, tvaudio */ +static unsigned short saa711x_addrs[] = { + 0x4a >> 1, 0x48 >> 1, /* SAA7111, SAA7111A and SAA7113 */ + 0x42 >> 1, 0x40 >> 1, /* SAA7114, SAA7115 and SAA7118 */ + I2C_CLIENT_END }; +static unsigned short tvp5150_addrs[] = { + 0xb8 >> 1, + 0xba >> 1, + I2C_CLIENT_END +}; +static unsigned short msp3400_addrs[] = { + 0x80 >> 1, + 0x88 >> 1, + I2C_CLIENT_END +}; + +/******************************** usb interface ******************************/ static struct video_device *em28xx_vdev_init(struct em28xx *dev, const struct video_device *template, @@ -1817,14 +2128,198 @@ static struct video_device *em28xx_vdev_init(struct em28xx *dev, return vfd; } -int em28xx_register_analog_devices(struct em28xx *dev) +static void em28xx_tuner_setup(struct em28xx *dev) +{ + struct tuner_setup tun_setup; + struct v4l2_frequency f; + + if (dev->tuner_type == TUNER_ABSENT) + return; + + memset(&tun_setup, 0, sizeof(tun_setup)); + + tun_setup.mode_mask = T_ANALOG_TV | T_RADIO; + tun_setup.tuner_callback = em28xx_tuner_callback; + + if (dev->board.radio.type) { + tun_setup.type = dev->board.radio.type; + tun_setup.addr = dev->board.radio_addr; + + v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_type_addr, &tun_setup); + } + + if ((dev->tuner_type != TUNER_ABSENT) && (dev->tuner_type)) { + tun_setup.type = dev->tuner_type; + tun_setup.addr = dev->tuner_addr; + + v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_type_addr, &tun_setup); + } + + if (dev->tda9887_conf) { + struct v4l2_priv_tun_config tda9887_cfg; + + tda9887_cfg.tuner = TUNER_TDA9887; + tda9887_cfg.priv = &dev->tda9887_conf; + + v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_config, &tda9887_cfg); + } + + if (dev->tuner_type == TUNER_XC2028) { + struct v4l2_priv_tun_config xc2028_cfg; + struct xc2028_ctrl ctl; + + memset(&xc2028_cfg, 0, sizeof(xc2028_cfg)); + memset(&ctl, 0, sizeof(ctl)); + + em28xx_setup_xc3028(dev, &ctl); + + xc2028_cfg.tuner = TUNER_XC2028; + xc2028_cfg.priv = &ctl; + + v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_config, &xc2028_cfg); + } + + /* configure tuner */ + f.tuner = 0; + f.type = V4L2_TUNER_ANALOG_TV; + f.frequency = 9076; /* just a magic number */ + dev->ctl_freq = f.frequency; + v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, &f); +} + +static int em28xx_v4l2_init(struct em28xx *dev) { u8 val; int ret; unsigned int maxw; + struct v4l2_ctrl_handler *hdl = &dev->ctrl_handler; + + if (dev->is_audio_only) { + /* Shouldn't initialize IR for this interface */ + return 0; + } + + if (!dev->has_video) { + /* This device does not support the v4l2 extension */ + return 0; + } + + em28xx_info("Registering V4L2 extension\n"); + + mutex_lock(&dev->lock); + + ret = v4l2_device_register(&dev->udev->dev, &dev->v4l2_dev); + if (ret < 0) { + em28xx_errdev("Call to v4l2_device_register() failed!\n"); + goto err; + } + + v4l2_ctrl_handler_init(hdl, 8); + dev->v4l2_dev.ctrl_handler = hdl; + + /* + * Default format, used for tvp5150 or saa711x output formats + */ + dev->vinmode = 0x10; + dev->vinctl = EM28XX_VINCTRL_INTERLACED | + EM28XX_VINCTRL_CCIR656_ENABLE; + + /* request some modules */ + + if (dev->board.has_msp34xx) + v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap[dev->def_i2c_bus], + "msp3400", 0, msp3400_addrs); + + if (dev->board.decoder == EM28XX_SAA711X) + v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap[dev->def_i2c_bus], + "saa7115_auto", 0, saa711x_addrs); + + if (dev->board.decoder == EM28XX_TVP5150) + v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap[dev->def_i2c_bus], + "tvp5150", 0, tvp5150_addrs); + + if (dev->board.adecoder == EM28XX_TVAUDIO) + v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap[dev->def_i2c_bus], + "tvaudio", dev->board.tvaudio_addr, NULL); + + /* Initialize tuner and camera */ + + if (dev->board.tuner_type != TUNER_ABSENT) { + int has_demod = (dev->tda9887_conf & TDA9887_PRESENT); + + if (dev->board.radio.type) + v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap[dev->def_i2c_bus], + "tuner", dev->board.radio_addr, NULL); + + if (has_demod) + v4l2_i2c_new_subdev(&dev->v4l2_dev, + &dev->i2c_adap[dev->def_i2c_bus], "tuner", + 0, v4l2_i2c_tuner_addrs(ADDRS_DEMOD)); + if (dev->tuner_addr == 0) { + enum v4l2_i2c_tuner_type type = + has_demod ? ADDRS_TV_WITH_DEMOD : ADDRS_TV; + struct v4l2_subdev *sd; + + sd = v4l2_i2c_new_subdev(&dev->v4l2_dev, + &dev->i2c_adap[dev->def_i2c_bus], "tuner", + 0, v4l2_i2c_tuner_addrs(type)); + + if (sd) + dev->tuner_addr = v4l2_i2c_subdev_addr(sd); + } else { + v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap[dev->def_i2c_bus], + "tuner", dev->tuner_addr, NULL); + } + } + + em28xx_tuner_setup(dev); + em28xx_init_camera(dev); + + /* Configure audio */ + ret = em28xx_audio_setup(dev); + if (ret < 0) { + em28xx_errdev("%s: Error while setting audio - error [%d]!\n", + __func__, ret); + goto unregister_dev; + } + if (dev->audio_mode.ac97 != EM28XX_NO_AC97) { + v4l2_ctrl_new_std(hdl, &em28xx_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); + v4l2_ctrl_new_std(hdl, &em28xx_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, 0, 0x1f, 1, 0x1f); + } else { + /* install the em28xx notify callback */ + v4l2_ctrl_notify(v4l2_ctrl_find(hdl, V4L2_CID_AUDIO_MUTE), + em28xx_ctrl_notify, dev); + v4l2_ctrl_notify(v4l2_ctrl_find(hdl, V4L2_CID_AUDIO_VOLUME), + em28xx_ctrl_notify, dev); + } + + /* wake i2c devices */ + em28xx_wake_i2c(dev); + + /* init video dma queues */ + INIT_LIST_HEAD(&dev->vidq.active); + INIT_LIST_HEAD(&dev->vbiq.active); - printk(KERN_INFO "%s: v4l2 driver version %s\n", - dev->name, EM28XX_VERSION); + if (dev->board.has_msp34xx) { + /* Send a reset to other chips via gpio */ + ret = em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xf7); + if (ret < 0) { + em28xx_errdev("%s: em28xx_write_reg - msp34xx(1) failed! error [%d]\n", + __func__, ret); + goto unregister_dev; + } + msleep(3); + + ret = em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xff); + if (ret < 0) { + em28xx_errdev("%s: em28xx_write_reg - msp34xx(2) failed! error [%d]\n", + __func__, ret); + goto unregister_dev; + } + msleep(3); + } /* set default norm */ dev->norm = V4L2_STD_PAL; @@ -1888,14 +2383,16 @@ int em28xx_register_analog_devices(struct em28xx *dev) /* Reset image controls */ em28xx_colorlevels_set_default(dev); v4l2_ctrl_handler_setup(&dev->ctrl_handler); - if (dev->ctrl_handler.error) - return dev->ctrl_handler.error; + ret = dev->ctrl_handler.error; + if (ret) + goto unregister_dev; /* allocate and fill video video_device struct */ dev->vdev = em28xx_vdev_init(dev, &em28xx_video_template, "video"); if (!dev->vdev) { em28xx_errdev("cannot allocate video_device.\n"); - return -ENODEV; + ret = -ENODEV; + goto unregister_dev; } dev->vdev->queue = &dev->vb_vidq; dev->vdev->queue->lock = &dev->vb_queue_lock; @@ -1925,7 +2422,7 @@ int em28xx_register_analog_devices(struct em28xx *dev) if (ret) { em28xx_errdev("unable to register video device (error=%i).\n", ret); - return ret; + goto unregister_dev; } /* Allocate and fill vbi video_device struct */ @@ -1954,7 +2451,7 @@ int em28xx_register_analog_devices(struct em28xx *dev) vbi_nr[dev->devno]); if (ret < 0) { em28xx_errdev("unable to register vbi device\n"); - return ret; + goto unregister_dev; } } @@ -1963,13 +2460,14 @@ int em28xx_register_analog_devices(struct em28xx *dev) "radio"); if (!dev->radio_dev) { em28xx_errdev("cannot allocate video_device.\n"); - return -ENODEV; + ret = -ENODEV; + goto unregister_dev; } ret = video_register_device(dev->radio_dev, VFL_TYPE_RADIO, radio_nr[dev->devno]); if (ret < 0) { em28xx_errdev("can't register radio device\n"); - return ret; + goto unregister_dev; } em28xx_info("Registered radio device as %s\n", video_device_node_name(dev->radio_dev)); @@ -1982,5 +2480,41 @@ int em28xx_register_analog_devices(struct em28xx *dev) em28xx_info("V4L2 VBI device registered as %s\n", video_device_node_name(dev->vbi_dev)); + /* Save some power by putting tuner to sleep */ + v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_power, 0); + + /* initialize videobuf2 stuff */ + em28xx_vb2_setup(dev); + + em28xx_info("V4L2 extension successfully initialized\n"); + + mutex_unlock(&dev->lock); return 0; + +unregister_dev: + v4l2_ctrl_handler_free(&dev->ctrl_handler); + v4l2_device_unregister(&dev->v4l2_dev); +err: + mutex_unlock(&dev->lock); + return ret; +} + +static struct em28xx_ops v4l2_ops = { + .id = EM28XX_V4L2, + .name = "Em28xx v4l2 Extension", + .init = em28xx_v4l2_init, + .fini = em28xx_v4l2_fini, +}; + +static int __init em28xx_video_register(void) +{ + return em28xx_register_extension(&v4l2_ops); +} + +static void __exit em28xx_video_unregister(void) +{ + em28xx_unregister_extension(&v4l2_ops); } + +module_init(em28xx_video_register); +module_exit(em28xx_video_unregister); diff --git a/drivers/media/usb/em28xx/em28xx.h b/drivers/media/usb/em28xx/em28xx.h index f8726ad5d0a8..32d8a4bb7961 100644 --- a/drivers/media/usb/em28xx/em28xx.h +++ b/drivers/media/usb/em28xx/em28xx.h @@ -26,6 +26,9 @@ #ifndef _EM28XX_H #define _EM28XX_H +#define EM28XX_VERSION "0.2.1" +#define DRIVER_DESC "Empia em28xx device driver" + #include <linux/workqueue.h> #include <linux/i2c.h> #include <linux/mutex.h> @@ -132,6 +135,8 @@ #define EM2884_BOARD_C3TECH_DIGITAL_DUO 88 #define EM2874_BOARD_DELOCK_61959 89 #define EM2874_BOARD_KWORLD_UB435Q_V2 90 +#define EM2765_BOARD_SPEEDLINK_VAD_LAPLACE 91 +#define EM28178_BOARD_PCTV_461E 92 /* Limits minimum and default number of buffers */ #define EM28XX_MIN_BUF 4 @@ -178,8 +183,27 @@ #define EM28XX_INTERLACED_DEFAULT 1 -/* time in msecs to wait for i2c writes to finish */ -#define EM2800_I2C_XFER_TIMEOUT 20 +/* + * Time in msecs to wait for i2c xfers to finish. + * 35ms is the maximum time a SMBUS device could wait when + * clock stretching is used. As the transfer itself will take + * some time to happen, set it to 35 ms. + * + * Ok, I2C doesn't specify any limit. So, eventually, we may need + * to increase this timeout. + * + * FIXME: this assumes that an I2C message is not longer than 1ms. + * This is actually dependent on the I2C bus speed, although most + * devices use a 100kHz clock. So, this assumtion is true most of + * the time. + */ +#define EM28XX_I2C_XFER_TIMEOUT 36 + +/* time in msecs to wait for AC97 xfers to finish */ +#define EM28XX_AC97_XFER_TIMEOUT 100 + +/* max. number of button state polling addresses */ +#define EM28XX_NUM_BUTTON_ADDRESSES_MAX 5 enum em28xx_mode { EM28XX_SUSPEND, @@ -287,8 +311,7 @@ struct em28xx_audio_mode { unsigned int has_audio:1; - unsigned int i2s_3rates:1; - unsigned int i2s_5rates:1; + u8 i2s_samplerates; }; /* em28xx has two audio inputs: tuner and line in. @@ -374,6 +397,33 @@ enum em28xx_adecoder { EM28XX_TVAUDIO, }; +enum em28xx_led_role { + EM28XX_LED_ANALOG_CAPTURING = 0, + EM28XX_LED_ILLUMINATION, + EM28XX_NUM_LED_ROLES, /* must be the last */ +}; + +struct em28xx_led { + enum em28xx_led_role role; + u8 gpio_reg; + u8 gpio_mask; + bool inverted; +}; + +enum em28xx_button_role { + EM28XX_BUTTON_SNAPSHOT = 0, + EM28XX_BUTTON_ILLUMINATION, + EM28XX_NUM_BUTTON_ROLES, /* must be the last */ +}; + +struct em28xx_button { + enum em28xx_button_role role; + u8 reg_r; + u8 reg_clearing; + u8 mask; + bool inverted; +}; + struct em28xx_board { char *name; int vchannels; @@ -395,7 +445,6 @@ struct em28xx_board { unsigned int mts_firmware:1; unsigned int max_range_640_480:1; unsigned int has_dvb:1; - unsigned int has_snapshot_button:1; unsigned int is_webcam:1; unsigned int valid:1; unsigned int has_ir_i2c:1; @@ -410,6 +459,12 @@ struct em28xx_board { struct em28xx_input input[MAX_EM28XX_INPUT]; struct em28xx_input radio; char *ir_codes; + + /* LEDs that need to be controlled explicitly */ + struct em28xx_led *leds; + + /* Buttons */ + struct em28xx_button *buttons; }; struct em28xx_eeprom { @@ -426,15 +481,13 @@ struct em28xx_eeprom { u8 string_idx_table; }; -#define EM28XX_AUDIO_BUFS 5 -#define EM28XX_NUM_AUDIO_PACKETS 64 -#define EM28XX_AUDIO_MAX_PACKET_SIZE 196 /* static value */ #define EM28XX_CAPTURE_STREAM_EN 1 /* em28xx extensions */ #define EM28XX_AUDIO 0x10 #define EM28XX_DVB 0x20 #define EM28XX_RC 0x30 +#define EM28XX_V4L2 0x40 /* em28xx resource types (used for res_get/res_lock etc */ #define EM28XX_RESOURCE_VIDEO 0x01 @@ -442,8 +495,9 @@ struct em28xx_eeprom { struct em28xx_audio { char name[50]; - char *transfer_buffer[EM28XX_AUDIO_BUFS]; - struct urb *urb[EM28XX_AUDIO_BUFS]; + unsigned num_urb; + char **transfer_buffer; + struct urb **urb; struct usb_device *udev; unsigned int capture_transfer_done; struct snd_pcm_substream *capture_pcm_substream; @@ -451,6 +505,8 @@ struct em28xx_audio { unsigned int hwptr_done_capture; struct snd_card *sndcard; + size_t period; + int users; spinlock_t slock; }; @@ -485,11 +541,13 @@ struct em28xx { int model; /* index in the device_data struct */ int devno; /* marks the number of this device */ enum em28xx_chip_id chip_id; - unsigned int is_em25xx:1; /* em25xx/em276x/7x/8x family bridge */ + unsigned int is_em25xx:1; /* em25xx/em276x/7x/8x family bridge */ unsigned char disconnected:1; /* device has been diconnected */ - - int audio_ifnum; + unsigned int has_video:1; + unsigned int has_audio_class:1; + unsigned int has_alsa_audio:1; + unsigned int is_audio_only:1; struct v4l2_device v4l2_dev; struct v4l2_ctrl_handler ctrl_handler; @@ -507,10 +565,6 @@ struct em28xx { /* Vinmode/Vinctl used at the driver */ int vinmode, vinctl; - unsigned int has_audio_class:1; - unsigned int has_alsa_audio:1; - unsigned int is_audio_only:1; - /* Controls audio streaming */ struct work_struct wq_trigger; /* Trigger to start/stop audio for alsa module */ atomic_t stream_started; /* stream should be running if true */ @@ -608,6 +662,7 @@ struct em28xx { /* usb transfer */ struct usb_device *udev; /* the usb device */ + u8 ifnum; /* number of the assigned usb interface */ u8 analog_ep_isoc; /* address of isoc endpoint for analog */ u8 analog_ep_bulk; /* address of bulk endpoint for analog */ u8 dvb_ep_isoc; /* address of isoc endpoint for DVB */ @@ -639,10 +694,15 @@ struct em28xx { enum em28xx_mode mode; - /* Snapshot button */ + /* Button state polling */ + struct delayed_work buttons_query_work; + u8 button_polling_addresses[EM28XX_NUM_BUTTON_ADDRESSES_MAX]; + u8 button_polling_last_values[EM28XX_NUM_BUTTON_ADDRESSES_MAX]; + u8 num_button_polling_addresses; + u16 button_polling_interval; /* [ms] */ + /* Snapshot button input device */ char snapshot_button_path[30]; /* path of the input dev */ struct input_dev *sbutton_input_dev; - struct delayed_work sbutton_query_work; struct em28xx_dvb *dvb; }; @@ -672,6 +732,7 @@ int em28xx_write_regs(struct em28xx *dev, u16 reg, char *buf, int len); int em28xx_write_reg(struct em28xx *dev, u16 reg, u8 val); int em28xx_write_reg_bits(struct em28xx *dev, u16 reg, u8 val, u8 bitmask); +int em28xx_toggle_reg_bits(struct em28xx *dev, u16 reg, u8 bitmask); int em28xx_read_ac97(struct em28xx *dev, u8 reg); int em28xx_write_ac97(struct em28xx *dev, u8 reg, u16 val); @@ -679,12 +740,9 @@ int em28xx_write_ac97(struct em28xx *dev, u8 reg, u16 val); int em28xx_audio_analog_set(struct em28xx *dev); int em28xx_audio_setup(struct em28xx *dev); -int em28xx_colorlevels_set_default(struct em28xx *dev); +const struct em28xx_led *em28xx_find_led(struct em28xx *dev, + enum em28xx_led_role role); int em28xx_capture_start(struct em28xx *dev, int start); -int em28xx_vbi_supported(struct em28xx *dev); -int em28xx_set_outfmt(struct em28xx *dev); -int em28xx_resolution_set(struct em28xx *dev); -int em28xx_set_alternate(struct em28xx *dev); int em28xx_alloc_urbs(struct em28xx *dev, enum em28xx_mode mode, int xfer_bulk, int num_bufs, int max_pkt_size, int packet_multiplier); int em28xx_init_usb_xfer(struct em28xx *dev, enum em28xx_mode mode, @@ -696,30 +754,18 @@ void em28xx_uninit_usb_xfer(struct em28xx *dev, enum em28xx_mode mode); void em28xx_stop_urbs(struct em28xx *dev); int em28xx_set_mode(struct em28xx *dev, enum em28xx_mode set_mode); int em28xx_gpio_set(struct em28xx *dev, struct em28xx_reg_seq *gpio); -void em28xx_wake_i2c(struct em28xx *dev); int em28xx_register_extension(struct em28xx_ops *dev); void em28xx_unregister_extension(struct em28xx_ops *dev); void em28xx_init_extension(struct em28xx *dev); void em28xx_close_extension(struct em28xx *dev); -/* Provided by em28xx-video.c */ -int em28xx_vb2_setup(struct em28xx *dev); -int em28xx_register_analog_devices(struct em28xx *dev); -void em28xx_release_analog_resources(struct em28xx *dev); -void em28xx_ctrl_notify(struct v4l2_ctrl *ctrl, void *priv); -int em28xx_start_analog_streaming(struct vb2_queue *vq, unsigned int count); -int em28xx_stop_vbi_streaming(struct vb2_queue *vq); -extern const struct v4l2_ctrl_ops em28xx_ctrl_ops; - /* Provided by em28xx-cards.c */ extern struct em28xx_board em28xx_boards[]; extern struct usb_device_id em28xx_id_table[]; int em28xx_tuner_callback(void *ptr, int component, int command, int arg); +void em28xx_setup_xc3028(struct em28xx *dev, struct xc2028_ctrl *ctl); void em28xx_release_resources(struct em28xx *dev); -/* Provided by em28xx-vbi.c */ -extern struct vb2_ops em28xx_vbi_qops; - /* Provided by em28xx-camera.c */ int em28xx_detect_sensor(struct em28xx *dev); int em28xx_init_camera(struct em28xx *dev); diff --git a/drivers/media/usb/pwc/pwc-if.c b/drivers/media/usb/pwc/pwc-if.c index 78c9bc8e7f56..abf365ab025d 100644 --- a/drivers/media/usb/pwc/pwc-if.c +++ b/drivers/media/usb/pwc/pwc-if.c @@ -1078,7 +1078,6 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id /* register webcam snapshot button input device */ pdev->button_dev = input_allocate_device(); if (!pdev->button_dev) { - PWC_ERROR("Err, insufficient memory for webcam snapshot button device."); rc = -ENOMEM; goto err_video_unreg; } diff --git a/drivers/media/v4l2-core/Kconfig b/drivers/media/v4l2-core/Kconfig index 8c05565a240e..2189bfb2e828 100644 --- a/drivers/media/v4l2-core/Kconfig +++ b/drivers/media/v4l2-core/Kconfig @@ -83,14 +83,3 @@ config VIDEOBUF2_DMA_SG #depends on HAS_DMA select VIDEOBUF2_CORE select VIDEOBUF2_MEMOPS - -config VIDEO_V4L2_INT_DEVICE - tristate "V4L2 int device (DEPRECATED)" - depends on VIDEO_V4L2 - ---help--- - An early framework for a hardware-independent interface for - image sensors and bridges etc. Currently used by omap24xxcam and - tcm825x drivers that should be converted to V4L2 subdev. - - Do not use for new developments. - diff --git a/drivers/media/v4l2-core/Makefile b/drivers/media/v4l2-core/Makefile index 1a85eee581f8..c6ae7bad951e 100644 --- a/drivers/media/v4l2-core/Makefile +++ b/drivers/media/v4l2-core/Makefile @@ -15,7 +15,6 @@ ifeq ($(CONFIG_OF),y) 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 diff --git a/drivers/media/v4l2-core/v4l2-ctrls.c b/drivers/media/v4l2-core/v4l2-ctrls.c index fb46790d0eca..6ff002bd5909 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls.c +++ b/drivers/media/v4l2-core/v4l2-ctrls.c @@ -745,6 +745,11 @@ const char *v4l2_ctrl_get_name(u32 id) 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"; + case V4L2_CID_MPEG_VIDEO_VPX_MIN_QP: return "VPX Minimum QP Value"; + case V4L2_CID_MPEG_VIDEO_VPX_MAX_QP: return "VPX Maximum QP Value"; + case V4L2_CID_MPEG_VIDEO_VPX_I_FRAME_QP: return "VPX I-Frame QP Value"; + case V4L2_CID_MPEG_VIDEO_VPX_P_FRAME_QP: return "VPX P-Frame QP Value"; + case V4L2_CID_MPEG_VIDEO_VPX_PROFILE: return "VPX Profile"; /* CAMERA controls */ /* Keep the order of the 'case's the same as in videodev2.h! */ diff --git a/drivers/media/v4l2-core/v4l2-dev.c b/drivers/media/v4l2-core/v4l2-dev.c index b5aaaac427ad..0a30dbf3d05c 100644 --- a/drivers/media/v4l2-core/v4l2-dev.c +++ b/drivers/media/v4l2-core/v4l2-dev.c @@ -872,8 +872,8 @@ 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); + video_device[vdev->minor] = vdev; mutex_unlock(&videodev_lock); if (vdev->ioctl_ops) diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index 68e6b5e912ff..707aef705a47 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -28,6 +28,9 @@ #include <media/v4l2-device.h> #include <media/videobuf2-core.h> +#define CREATE_TRACE_POINTS +#include <trace/events/v4l2.h> + /* Zero out the end of the struct pointed to by p. Everything after, but * not including, the specified field is cleared. */ #define CLEAR_AFTER_FIELD(p, field) \ @@ -2338,6 +2341,12 @@ video_usercopy(struct file *file, unsigned int cmd, unsigned long arg, err = func(file, cmd, parg); if (err == -ENOIOCTLCMD) err = -ENOTTY; + if (err == 0) { + if (cmd == VIDIOC_DQBUF) + trace_v4l2_dqbuf(video_devdata(file)->minor, parg); + else if (cmd == VIDIOC_QBUF) + trace_v4l2_qbuf(video_devdata(file)->minor, parg); + } if (has_array_args) { *kernel_ptr = user_ptr; diff --git a/drivers/media/v4l2-core/v4l2-mem2mem.c b/drivers/media/v4l2-core/v4l2-mem2mem.c index 73035ee0f4de..178ce96556c6 100644 --- a/drivers/media/v4l2-core/v4l2-mem2mem.c +++ b/drivers/media/v4l2-core/v4l2-mem2mem.c @@ -558,6 +558,8 @@ unsigned int v4l2_m2m_poll(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, if (m2m_ctx->m2m_dev->m2m_ops->unlock) m2m_ctx->m2m_dev->m2m_ops->unlock(m2m_ctx->priv); + else if (m2m_ctx->q_lock) + mutex_unlock(m2m_ctx->q_lock); if (list_empty(&src_q->done_list)) poll_wait(file, &src_q->done_wq, wait); @@ -566,6 +568,8 @@ unsigned int v4l2_m2m_poll(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, if (m2m_ctx->m2m_dev->m2m_ops->lock) m2m_ctx->m2m_dev->m2m_ops->lock(m2m_ctx->priv); + else if (m2m_ctx->q_lock) + mutex_lock(m2m_ctx->q_lock); spin_lock_irqsave(&src_q->done_lock, flags); if (!list_empty(&src_q->done_list)) @@ -693,6 +697,13 @@ struct v4l2_m2m_ctx *v4l2_m2m_ctx_init(struct v4l2_m2m_dev *m2m_dev, if (ret) goto err; + /* + * If both queues use same mutex assign it as the common buffer + * queues lock to the m2m context. This lock is used in the + * v4l2_m2m_ioctl_* helpers. + */ + if (out_q_ctx->q.lock == cap_q_ctx->q.lock) + m2m_ctx->q_lock = out_q_ctx->q.lock; return m2m_ctx; err: @@ -740,3 +751,118 @@ void v4l2_m2m_buf_queue(struct v4l2_m2m_ctx *m2m_ctx, struct vb2_buffer *vb) } EXPORT_SYMBOL_GPL(v4l2_m2m_buf_queue); +/* Videobuf2 ioctl helpers */ + +int v4l2_m2m_ioctl_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *rb) +{ + struct v4l2_fh *fh = file->private_data; + + return v4l2_m2m_reqbufs(file, fh->m2m_ctx, rb); +} +EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_reqbufs); + +int v4l2_m2m_ioctl_create_bufs(struct file *file, void *priv, + struct v4l2_create_buffers *create) +{ + struct v4l2_fh *fh = file->private_data; + + return v4l2_m2m_create_bufs(file, fh->m2m_ctx, create); +} +EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_create_bufs); + +int v4l2_m2m_ioctl_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct v4l2_fh *fh = file->private_data; + + return v4l2_m2m_querybuf(file, fh->m2m_ctx, buf); +} +EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_querybuf); + +int v4l2_m2m_ioctl_qbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct v4l2_fh *fh = file->private_data; + + return v4l2_m2m_qbuf(file, fh->m2m_ctx, buf); +} +EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_qbuf); + +int v4l2_m2m_ioctl_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct v4l2_fh *fh = file->private_data; + + return v4l2_m2m_dqbuf(file, fh->m2m_ctx, buf); +} +EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_dqbuf); + +int v4l2_m2m_ioctl_expbuf(struct file *file, void *priv, + struct v4l2_exportbuffer *eb) +{ + struct v4l2_fh *fh = file->private_data; + + return v4l2_m2m_expbuf(file, fh->m2m_ctx, eb); +} +EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_expbuf); + +int v4l2_m2m_ioctl_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct v4l2_fh *fh = file->private_data; + + return v4l2_m2m_streamon(file, fh->m2m_ctx, type); +} +EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_streamon); + +int v4l2_m2m_ioctl_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct v4l2_fh *fh = file->private_data; + + return v4l2_m2m_streamoff(file, fh->m2m_ctx, type); +} +EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_streamoff); + +/* + * v4l2_file_operations helpers. It is assumed here same lock is used + * for the output and the capture buffer queue. + */ + +int v4l2_m2m_fop_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct v4l2_fh *fh = file->private_data; + struct v4l2_m2m_ctx *m2m_ctx = fh->m2m_ctx; + int ret; + + if (m2m_ctx->q_lock && mutex_lock_interruptible(m2m_ctx->q_lock)) + return -ERESTARTSYS; + + ret = v4l2_m2m_mmap(file, m2m_ctx, vma); + + if (m2m_ctx->q_lock) + mutex_unlock(m2m_ctx->q_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(v4l2_m2m_fop_mmap); + +unsigned int v4l2_m2m_fop_poll(struct file *file, poll_table *wait) +{ + struct v4l2_fh *fh = file->private_data; + struct v4l2_m2m_ctx *m2m_ctx = fh->m2m_ctx; + unsigned int ret; + + if (m2m_ctx->q_lock) + mutex_lock(m2m_ctx->q_lock); + + ret = v4l2_m2m_poll(file, m2m_ctx, wait); + + if (m2m_ctx->q_lock) + mutex_unlock(m2m_ctx->q_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(v4l2_m2m_fop_poll); + diff --git a/drivers/media/v4l2-core/v4l2-of.c b/drivers/media/v4l2-core/v4l2-of.c index a6478dca0cde..42e3e8a5e361 100644 --- a/drivers/media/v4l2-core/v4l2-of.c +++ b/drivers/media/v4l2-core/v4l2-of.c @@ -121,9 +121,11 @@ static void v4l2_of_parse_parallel_bus(const struct device_node *node, * the bus as serial CSI-2 and clock-noncontinuous isn't set, we set the * V4L2_MBUS_CSI2_CONTINUOUS_CLOCK flag. * The caller should hold a reference to @node. + * + * Return: 0. */ -void v4l2_of_parse_endpoint(const struct device_node *node, - struct v4l2_of_endpoint *endpoint) +int v4l2_of_parse_endpoint(const struct device_node *node, + struct v4l2_of_endpoint *endpoint) { struct device_node *port_node = of_get_parent(node); @@ -146,6 +148,8 @@ void v4l2_of_parse_endpoint(const struct device_node *node, v4l2_of_parse_parallel_bus(node, endpoint); of_node_put(port_node); + + return 0; } EXPORT_SYMBOL(v4l2_of_parse_endpoint); @@ -262,6 +266,6 @@ struct device_node *v4l2_of_get_remote_port(const struct device_node *node) np = of_parse_phandle(node, "remote-endpoint", 0); if (!np) return NULL; - return of_get_parent(np); + return of_get_next_parent(np); } EXPORT_SYMBOL(v4l2_of_get_remote_port); diff --git a/drivers/media/v4l2-core/videobuf2-core.c b/drivers/media/v4l2-core/videobuf2-core.c index 0edc165f418d..5a5fb7f09b7b 100644 --- a/drivers/media/v4l2-core/videobuf2-core.c +++ b/drivers/media/v4l2-core/videobuf2-core.c @@ -298,10 +298,28 @@ static void __vb2_free_mem(struct vb2_queue *q, unsigned int buffers) * related information, if no buffers are left return the queue to an * uninitialized state. Might be called even if the queue has already been freed. */ -static void __vb2_queue_free(struct vb2_queue *q, unsigned int buffers) +static int __vb2_queue_free(struct vb2_queue *q, unsigned int buffers) { unsigned int buffer; + /* + * Sanity check: when preparing a buffer the queue lock is released for + * a short while (see __buf_prepare for the details), which would allow + * a race with a reqbufs which can call this function. Removing the + * buffers from underneath __buf_prepare is obviously a bad idea, so we + * check if any of the buffers is in the state PREPARING, and if so we + * just return -EAGAIN. + */ + for (buffer = q->num_buffers - buffers; buffer < q->num_buffers; + ++buffer) { + if (q->bufs[buffer] == NULL) + continue; + if (q->bufs[buffer]->state == VB2_BUF_STATE_PREPARING) { + dprintk(1, "reqbufs: preparing buffers, cannot free\n"); + return -EAGAIN; + } + } + /* Call driver-provided cleanup function for each buffer, if provided */ if (q->ops->buf_cleanup) { for (buffer = q->num_buffers - buffers; buffer < q->num_buffers; @@ -326,6 +344,7 @@ static void __vb2_queue_free(struct vb2_queue *q, unsigned int buffers) if (!q->num_buffers) q->memory = 0; INIT_LIST_HEAD(&q->queued_list); + return 0; } /** @@ -481,6 +500,7 @@ static void __fill_v4l2_buffer(struct vb2_buffer *vb, struct v4l2_buffer *b) case VB2_BUF_STATE_PREPARED: b->flags |= V4L2_BUF_FLAG_PREPARED; break; + case VB2_BUF_STATE_PREPARING: case VB2_BUF_STATE_DEQUEUED: /* nothing */ break; @@ -657,7 +677,9 @@ static int __reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req) return -EBUSY; } - __vb2_queue_free(q, q->num_buffers); + ret = __vb2_queue_free(q, q->num_buffers); + if (ret) + return ret; /* * In case of REQBUFS(0) return immediately without calling @@ -1116,7 +1138,7 @@ static int __qbuf_dmabuf(struct vb2_buffer *vb, const struct v4l2_buffer *b) int ret; int write = !V4L2_TYPE_IS_OUTPUT(q->type); - /* Verify and copy relevant information provided by the userspace */ + /* Copy relevant information provided by the userspace */ __fill_vb2_buffer(vb, b, planes); for (plane = 0; plane < vb->num_planes; ++plane) { @@ -1135,6 +1157,8 @@ static int __qbuf_dmabuf(struct vb2_buffer *vb, const struct v4l2_buffer *b) if (planes[plane].length < planes[plane].data_offset + q->plane_sizes[plane]) { + dprintk(1, "qbuf: invalid dmabuf length for plane %d\n", + plane); ret = -EINVAL; goto err; } @@ -1226,6 +1250,7 @@ static void __enqueue_in_driver(struct vb2_buffer *vb) static int __buf_prepare(struct vb2_buffer *vb, const struct v4l2_buffer *b) { struct vb2_queue *q = vb->vb2_queue; + struct rw_semaphore *mmap_sem; int ret; ret = __verify_length(vb, b); @@ -1235,12 +1260,32 @@ static int __buf_prepare(struct vb2_buffer *vb, const struct v4l2_buffer *b) return ret; } + vb->state = VB2_BUF_STATE_PREPARING; switch (q->memory) { case V4L2_MEMORY_MMAP: ret = __qbuf_mmap(vb, b); break; case V4L2_MEMORY_USERPTR: + /* + * 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. + */ + mmap_sem = ¤t->mm->mmap_sem; + call_qop(q, wait_prepare, q); + down_read(mmap_sem); + call_qop(q, wait_finish, q); + ret = __qbuf_userptr(vb, b); + + up_read(mmap_sem); break; case V4L2_MEMORY_DMABUF: ret = __qbuf_dmabuf(vb, b); @@ -1254,105 +1299,36 @@ static int __buf_prepare(struct vb2_buffer *vb, const struct v4l2_buffer *b) ret = call_qop(q, buf_prepare, vb); if (ret) dprintk(1, "qbuf: buffer preparation failed: %d\n", ret); - else - vb->state = VB2_BUF_STATE_PREPARED; + vb->state = ret ? VB2_BUF_STATE_DEQUEUED : VB2_BUF_STATE_PREPARED; return ret; } 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 *)) + const char *opname) { - struct rw_semaphore *mmap_sem = NULL; - struct vb2_buffer *vb; - int ret; - - /* - * 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 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; - call_qop(q, wait_prepare, q); - down_read(mmap_sem); - call_qop(q, wait_finish, q); - } - - if (q->fileio) { - dprintk(1, "%s(): file io in progress\n", opname); - ret = -EBUSY; - goto unlock; - } - if (b->type != q->type) { dprintk(1, "%s(): invalid buffer type\n", opname); - ret = -EINVAL; - goto unlock; + return -EINVAL; } if (b->index >= q->num_buffers) { dprintk(1, "%s(): buffer index out of range\n", opname); - ret = -EINVAL; - goto unlock; + return -EINVAL; } - vb = q->bufs[b->index]; - if (NULL == vb) { + if (q->bufs[b->index] == NULL) { /* Should never happen */ dprintk(1, "%s(): buffer is NULL\n", opname); - ret = -EINVAL; - goto unlock; + return -EINVAL; } if (b->memory != q->memory) { 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); + return __verify_planes_array(q->bufs[b->index], b); } /** @@ -1372,22 +1348,95 @@ static int __vb2_prepare_buf(struct vb2_queue *q, struct v4l2_buffer *b, */ 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); + struct vb2_buffer *vb; + int ret; + + if (q->fileio) { + dprintk(1, "%s(): file io in progress\n", __func__); + return -EBUSY; + } + + ret = vb2_queue_or_prepare_buf(q, b, "prepare_buf"); + if (ret) + return ret; + + vb = q->bufs[b->index]; + if (vb->state != VB2_BUF_STATE_DEQUEUED) { + dprintk(1, "%s(): invalid buffer state %d\n", __func__, + vb->state); + return -EINVAL; + } + + ret = __buf_prepare(vb, b); + if (!ret) { + /* Fill buffer information for the userspace */ + __fill_v4l2_buffer(vb, b); + + dprintk(1, "%s() of buffer %d succeeded\n", __func__, vb->v4l2_buf.index); + } + return ret; } EXPORT_SYMBOL_GPL(vb2_prepare_buf); -static int __vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b, - struct vb2_buffer *vb) +/** + * vb2_start_streaming() - Attempt to start streaming. + * @q: videobuf2 queue + * + * If there are not enough buffers, then retry_start_streaming is set to + * 1 and 0 is returned. The next time a buffer is queued and + * retry_start_streaming is 1, this function will be called again to + * retry starting the DMA engine. + */ +static int vb2_start_streaming(struct vb2_queue *q) { int ret; + /* Tell the driver to start streaming */ + ret = call_qop(q, start_streaming, q, atomic_read(&q->queued_count)); + + /* + * If there are not enough buffers queued to start streaming, then + * the start_streaming operation will return -ENOBUFS and you have to + * retry when the next buffer is queued. + */ + if (ret == -ENOBUFS) { + dprintk(1, "qbuf: not enough buffers, retry when more buffers are queued.\n"); + q->retry_start_streaming = 1; + return 0; + } + if (ret) + dprintk(1, "qbuf: driver refused to start streaming\n"); + else + q->retry_start_streaming = 0; + return ret; +} + +static int vb2_internal_qbuf(struct vb2_queue *q, struct v4l2_buffer *b) +{ + int ret = vb2_queue_or_prepare_buf(q, b, "qbuf"); + struct vb2_buffer *vb; + + if (ret) + return ret; + + vb = q->bufs[b->index]; + if (vb->state != VB2_BUF_STATE_DEQUEUED) { + dprintk(1, "%s(): invalid buffer state %d\n", __func__, + vb->state); + return -EINVAL; + } + switch (vb->state) { case VB2_BUF_STATE_DEQUEUED: ret = __buf_prepare(vb, b); if (ret) return ret; + break; case VB2_BUF_STATE_PREPARED: break; + case VB2_BUF_STATE_PREPARING: + dprintk(1, "qbuf: buffer still being prepared\n"); + return -EINVAL; default: dprintk(1, "qbuf: buffer already in use\n"); return -EINVAL; @@ -1407,6 +1456,16 @@ static 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); + + if (q->retry_start_streaming) { + ret = vb2_start_streaming(q); + if (ret) + return ret; + } + + dprintk(1, "%s() of buffer %d succeeded\n", __func__, vb->v4l2_buf.index); return 0; } @@ -1429,7 +1488,12 @@ static int __vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b, */ int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b) { - return vb2_queue_or_prepare_buf(q, b, "qbuf", __vb2_qbuf); + if (q->fileio) { + dprintk(1, "%s(): file io in progress\n", __func__); + return -EBUSY; + } + + return vb2_internal_qbuf(q, b); } EXPORT_SYMBOL_GPL(vb2_qbuf); @@ -1550,7 +1614,8 @@ int vb2_wait_for_all_buffers(struct vb2_queue *q) return -EINVAL; } - wait_event(q->done_wq, !atomic_read(&q->queued_count)); + if (!q->retry_start_streaming) + wait_event(q->done_wq, !atomic_read(&q->queued_count)); return 0; } EXPORT_SYMBOL_GPL(vb2_wait_for_all_buffers); @@ -1579,37 +1644,11 @@ static void __vb2_dqbuf(struct vb2_buffer *vb) } } -/** - * vb2_dqbuf() - Dequeue a buffer to the userspace - * @q: videobuf2 queue - * @b: buffer structure passed from userspace to vidioc_dqbuf handler - * in driver - * @nonblocking: if true, this call will not sleep waiting for a buffer if no - * buffers ready for dequeuing are present. Normally the driver - * would be passing (file->f_flags & O_NONBLOCK) here - * - * Should be called from vidioc_dqbuf ioctl handler of a driver. - * This function: - * 1) verifies the passed buffer, - * 2) calls buf_finish callback in the driver (if provided), in which - * driver can perform any additional operations that may be required before - * returning the buffer to userspace, such as cache sync, - * 3) the buffer struct members are filled with relevant information for - * the userspace. - * - * The return values from this function are intended to be directly returned - * from vidioc_dqbuf handler in driver. - */ -int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking) +static int vb2_internal_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking) { struct vb2_buffer *vb = NULL; int ret; - if (q->fileio) { - dprintk(1, "dqbuf: file io in progress\n"); - return -EBUSY; - } - if (b->type != q->type) { dprintk(1, "dqbuf: invalid buffer type\n"); return -EINVAL; @@ -1648,6 +1687,36 @@ int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking) return 0; } + +/** + * vb2_dqbuf() - Dequeue a buffer to the userspace + * @q: videobuf2 queue + * @b: buffer structure passed from userspace to vidioc_dqbuf handler + * in driver + * @nonblocking: if true, this call will not sleep waiting for a buffer if no + * buffers ready for dequeuing are present. Normally the driver + * would be passing (file->f_flags & O_NONBLOCK) here + * + * Should be called from vidioc_dqbuf ioctl handler of a driver. + * This function: + * 1) verifies the passed buffer, + * 2) calls buf_finish callback in the driver (if provided), in which + * driver can perform any additional operations that may be required before + * returning the buffer to userspace, such as cache sync, + * 3) the buffer struct members are filled with relevant information for + * the userspace. + * + * The return values from this function are intended to be directly returned + * from vidioc_dqbuf handler in driver. + */ +int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking) +{ + if (q->fileio) { + dprintk(1, "dqbuf: file io in progress\n"); + return -EBUSY; + } + return vb2_internal_dqbuf(q, b, nonblocking); +} EXPORT_SYMBOL_GPL(vb2_dqbuf); /** @@ -1660,6 +1729,11 @@ static void __vb2_queue_cancel(struct vb2_queue *q) { unsigned int i; + if (q->retry_start_streaming) { + q->retry_start_streaming = 0; + q->streaming = 0; + } + /* * Tell driver to stop all transactions and release all queued * buffers. @@ -1687,37 +1761,19 @@ static void __vb2_queue_cancel(struct vb2_queue *q) __vb2_dqbuf(q->bufs[i]); } -/** - * vb2_streamon - start streaming - * @q: videobuf2 queue - * @type: type argument passed from userspace to vidioc_streamon handler - * - * Should be called from vidioc_streamon handler of a driver. - * This function: - * 1) verifies current state - * 2) passes any previously queued buffers to the driver and starts streaming - * - * The return values from this function are intended to be directly returned - * from vidioc_streamon handler in the driver. - */ -int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type) +static int vb2_internal_streamon(struct vb2_queue *q, enum v4l2_buf_type type) { struct vb2_buffer *vb; int ret; - if (q->fileio) { - dprintk(1, "streamon: file io in progress\n"); - return -EBUSY; - } - if (type != q->type) { dprintk(1, "streamon: invalid stream type\n"); return -EINVAL; } if (q->streaming) { - dprintk(1, "streamon: already streaming\n"); - return -EBUSY; + dprintk(3, "streamon successful: already streaming\n"); + return 0; } /* @@ -1727,12 +1783,9 @@ int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type) list_for_each_entry(vb, &q->queued_list, queued_entry) __enqueue_in_driver(vb); - /* - * Let driver notice that streaming state has been enabled. - */ - ret = call_qop(q, start_streaming, q, atomic_read(&q->queued_count)); + /* Tell driver to start streaming. */ + ret = vb2_start_streaming(q); if (ret) { - dprintk(1, "streamon: driver refused to start streaming\n"); __vb2_queue_cancel(q); return ret; } @@ -1742,39 +1795,40 @@ int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type) dprintk(3, "Streamon successful\n"); return 0; } -EXPORT_SYMBOL_GPL(vb2_streamon); - /** - * vb2_streamoff - stop streaming + * vb2_streamon - start streaming * @q: videobuf2 queue - * @type: type argument passed from userspace to vidioc_streamoff handler + * @type: type argument passed from userspace to vidioc_streamon handler * - * Should be called from vidioc_streamoff handler of a driver. + * Should be called from vidioc_streamon handler of a driver. * This function: - * 1) verifies current state, - * 2) stop streaming and dequeues any queued buffers, including those previously - * passed to the driver (after waiting for the driver to finish). + * 1) verifies current state + * 2) passes any previously queued buffers to the driver and starts streaming * - * This call can be used for pausing playback. * The return values from this function are intended to be directly returned - * from vidioc_streamoff handler in the driver + * from vidioc_streamon handler in the driver. */ -int vb2_streamoff(struct vb2_queue *q, enum v4l2_buf_type type) +int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type) { if (q->fileio) { - dprintk(1, "streamoff: file io in progress\n"); + dprintk(1, "streamon: file io in progress\n"); return -EBUSY; } + return vb2_internal_streamon(q, type); +} +EXPORT_SYMBOL_GPL(vb2_streamon); +static int vb2_internal_streamoff(struct vb2_queue *q, enum v4l2_buf_type type) +{ if (type != q->type) { dprintk(1, "streamoff: invalid stream type\n"); return -EINVAL; } if (!q->streaming) { - dprintk(1, "streamoff: not streaming\n"); - return -EINVAL; + dprintk(3, "streamoff successful: not streaming\n"); + return 0; } /* @@ -1786,6 +1840,30 @@ int vb2_streamoff(struct vb2_queue *q, enum v4l2_buf_type type) dprintk(3, "Streamoff successful\n"); return 0; } + +/** + * vb2_streamoff - stop streaming + * @q: videobuf2 queue + * @type: type argument passed from userspace to vidioc_streamoff handler + * + * Should be called from vidioc_streamoff handler of a driver. + * This function: + * 1) verifies current state, + * 2) stop streaming and dequeues any queued buffers, including those previously + * passed to the driver (after waiting for the driver to finish). + * + * This call can be used for pausing playback. + * The return values from this function are intended to be directly returned + * from vidioc_streamoff handler in the driver + */ +int vb2_streamoff(struct vb2_queue *q, enum v4l2_buf_type type) +{ + if (q->fileio) { + dprintk(1, "streamoff: file io in progress\n"); + return -EBUSY; + } + return vb2_internal_streamoff(q, type); +} EXPORT_SYMBOL_GPL(vb2_streamoff); /** @@ -2277,15 +2355,16 @@ static int __vb2_init_fileio(struct vb2_queue *q, int read) goto err_reqbufs; fileio->bufs[i].queued = 1; } - - /* - * Start streaming. - */ - ret = vb2_streamon(q, q->type); - if (ret) - goto err_reqbufs; + fileio->index = q->num_buffers; } + /* + * Start streaming. + */ + ret = vb2_streamon(q, q->type); + if (ret) + goto err_reqbufs; + q->fileio = fileio; return ret; @@ -2308,13 +2387,8 @@ static int __vb2_cleanup_fileio(struct vb2_queue *q) struct vb2_fileio_data *fileio = q->fileio; if (fileio) { - /* - * Hack fileio context to enable direct calls to vb2 ioctl - * interface. - */ + vb2_internal_streamoff(q, q->type); q->fileio = NULL; - - vb2_streamoff(q, q->type); fileio->req.count = 0; vb2_reqbufs(q, &fileio->req); kfree(fileio); @@ -2358,39 +2432,34 @@ static size_t __vb2_perform_fileio(struct vb2_queue *q, char __user *data, size_ fileio = q->fileio; /* - * Hack fileio context to enable direct calls to vb2 ioctl interface. - * The pointer will be restored before returning from this function. - */ - q->fileio = NULL; - - index = fileio->index; - buf = &fileio->bufs[index]; - - /* * Check if we need to dequeue the buffer. */ - if (buf->queued) { - struct vb2_buffer *vb; - + index = fileio->index; + if (index >= q->num_buffers) { /* * Call vb2_dqbuf to get buffer back. */ memset(&fileio->b, 0, sizeof(fileio->b)); fileio->b.type = q->type; fileio->b.memory = q->memory; - fileio->b.index = index; - ret = vb2_dqbuf(q, &fileio->b, nonblock); + ret = vb2_internal_dqbuf(q, &fileio->b, nonblock); dprintk(5, "file io: vb2_dqbuf result: %d\n", ret); if (ret) - goto end; + return ret; fileio->dq_count += 1; + index = fileio->b.index; + buf = &fileio->bufs[index]; + /* * Get number of bytes filled by the driver */ - vb = q->bufs[index]; - buf->size = vb2_get_plane_payload(vb, 0); + buf->pos = 0; buf->queued = 0; + buf->size = read ? vb2_get_plane_payload(q->bufs[index], 0) + : vb2_plane_size(q->bufs[index], 0); + } else { + buf = &fileio->bufs[index]; } /* @@ -2412,8 +2481,7 @@ static size_t __vb2_perform_fileio(struct vb2_queue *q, char __user *data, size_ ret = copy_from_user(buf->vaddr + buf->pos, data, count); if (ret) { dprintk(3, "file io: error copying data\n"); - ret = -EFAULT; - goto end; + return -EFAULT; } /* @@ -2433,10 +2501,6 @@ static size_t __vb2_perform_fileio(struct vb2_queue *q, char __user *data, size_ if (read && (fileio->flags & VB2_FILEIO_READ_ONCE) && fileio->dq_count == 1) { dprintk(3, "file io: read limit reached\n"); - /* - * Restore fileio pointer and release the context. - */ - q->fileio = fileio; return __vb2_cleanup_fileio(q); } @@ -2448,32 +2512,20 @@ static size_t __vb2_perform_fileio(struct vb2_queue *q, char __user *data, size_ fileio->b.memory = q->memory; fileio->b.index = index; fileio->b.bytesused = buf->pos; - ret = vb2_qbuf(q, &fileio->b); + ret = vb2_internal_qbuf(q, &fileio->b); dprintk(5, "file io: vb2_dbuf result: %d\n", ret); if (ret) - goto end; + return ret; /* * Buffer has been queued, update the status */ buf->pos = 0; buf->queued = 1; - buf->size = q->bufs[0]->v4l2_planes[0].length; + buf->size = vb2_plane_size(q->bufs[index], 0); fileio->q_count += 1; - - /* - * Switch to the next buffer - */ - fileio->index = (index + 1) % q->num_buffers; - - /* - * Start streaming if required. - */ - if (!read && !q->streaming) { - ret = vb2_streamon(q, q->type); - if (ret) - goto end; - } + if (fileio->index < q->num_buffers) + fileio->index++; } /* @@ -2481,11 +2533,6 @@ static size_t __vb2_perform_fileio(struct vb2_queue *q, char __user *data, size_ */ if (ret == 0) ret = count; -end: - /* - * Restore the fileio context and block vb2 ioctl interface. - */ - q->fileio = fileio; return ret; } @@ -2649,16 +2696,29 @@ int vb2_fop_mmap(struct file *file, struct vm_area_struct *vma) } EXPORT_SYMBOL_GPL(vb2_fop_mmap); -int vb2_fop_release(struct file *file) +int _vb2_fop_release(struct file *file, struct mutex *lock) { struct video_device *vdev = video_devdata(file); if (file->private_data == vdev->queue->owner) { + if (lock) + mutex_lock(lock); vb2_queue_release(vdev->queue); vdev->queue->owner = NULL; + if (lock) + mutex_unlock(lock); } return v4l2_fh_release(file); } +EXPORT_SYMBOL_GPL(_vb2_fop_release); + +int vb2_fop_release(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct mutex *lock = vdev->queue->lock ? vdev->queue->lock : vdev->lock; + + return _vb2_fop_release(file, lock); +} EXPORT_SYMBOL_GPL(vb2_fop_release); ssize_t vb2_fop_write(struct file *file, const char __user *buf, diff --git a/drivers/media/v4l2-core/videobuf2-dma-sg.c b/drivers/media/v4l2-core/videobuf2-dma-sg.c index 0d3a8ffe47a3..c779f210d2c6 100644 --- a/drivers/media/v4l2-core/videobuf2-dma-sg.c +++ b/drivers/media/v4l2-core/videobuf2-dma-sg.c @@ -40,6 +40,7 @@ struct vb2_dma_sg_buf { unsigned int num_pages; atomic_t refcount; struct vb2_vmarea_handler handler; + struct vm_area_struct *vma; }; static void vb2_dma_sg_put(void *buf_priv); @@ -155,12 +156,18 @@ static void vb2_dma_sg_put(void *buf_priv) } } +static inline int vma_is_io(struct vm_area_struct *vma) +{ + return !!(vma->vm_flags & (VM_IO | VM_PFNMAP)); +} + static void *vb2_dma_sg_get_userptr(void *alloc_ctx, unsigned long vaddr, unsigned long size, int write) { struct vb2_dma_sg_buf *buf; unsigned long first, last; int num_pages_from_user; + struct vm_area_struct *vma; buf = kzalloc(sizeof *buf, GFP_KERNEL); if (!buf) @@ -180,7 +187,38 @@ static void *vb2_dma_sg_get_userptr(void *alloc_ctx, unsigned long vaddr, if (!buf->pages) goto userptr_fail_alloc_pages; - num_pages_from_user = get_user_pages(current, current->mm, + vma = find_vma(current->mm, vaddr); + if (!vma) { + dprintk(1, "no vma for address %lu\n", vaddr); + goto userptr_fail_find_vma; + } + + if (vma->vm_end < vaddr + size) { + dprintk(1, "vma at %lu is too small for %lu bytes\n", + vaddr, size); + goto userptr_fail_find_vma; + } + + buf->vma = vb2_get_vma(vma); + if (!buf->vma) { + dprintk(1, "failed to copy vma\n"); + goto userptr_fail_find_vma; + } + + if (vma_is_io(buf->vma)) { + for (num_pages_from_user = 0; + num_pages_from_user < buf->num_pages; + ++num_pages_from_user, vaddr += PAGE_SIZE) { + unsigned long pfn; + + if (follow_pfn(buf->vma, vaddr, &pfn)) { + dprintk(1, "no page for address %lu\n", vaddr); + break; + } + buf->pages[num_pages_from_user] = pfn_to_page(pfn); + } + } else + num_pages_from_user = get_user_pages(current, current->mm, vaddr & PAGE_MASK, buf->num_pages, write, @@ -200,9 +238,12 @@ static void *vb2_dma_sg_get_userptr(void *alloc_ctx, unsigned long vaddr, userptr_fail_alloc_table_from_pages: userptr_fail_get_user_pages: dprintk(1, "get_user_pages requested/got: %d/%d]\n", - num_pages_from_user, buf->num_pages); - while (--num_pages_from_user >= 0) - put_page(buf->pages[num_pages_from_user]); + buf->num_pages, num_pages_from_user); + if (!vma_is_io(buf->vma)) + while (--num_pages_from_user >= 0) + put_page(buf->pages[num_pages_from_user]); + vb2_put_vma(buf->vma); +userptr_fail_find_vma: kfree(buf->pages); userptr_fail_alloc_pages: kfree(buf); @@ -226,9 +267,11 @@ static void vb2_dma_sg_put_userptr(void *buf_priv) while (--i >= 0) { if (buf->write) set_page_dirty_lock(buf->pages[i]); - put_page(buf->pages[i]); + if (!vma_is_io(buf->vma)) + put_page(buf->pages[i]); } kfree(buf->pages); + vb2_put_vma(buf->vma); kfree(buf); } diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig index 46f1e619cbd8..22b0c9d6f046 100644 --- a/drivers/staging/media/Kconfig +++ b/drivers/staging/media/Kconfig @@ -21,6 +21,8 @@ if STAGING_MEDIA # Please keep them in alphabetic order source "drivers/staging/media/as102/Kconfig" +source "drivers/staging/media/bcm2048/Kconfig" + source "drivers/staging/media/cxd2099/Kconfig" source "drivers/staging/media/davinci_vpfe/Kconfig" @@ -31,8 +33,14 @@ source "drivers/staging/media/go7007/Kconfig" source "drivers/staging/media/msi3101/Kconfig" +source "drivers/staging/media/omap24xx/Kconfig" + +source "drivers/staging/media/sn9c102/Kconfig" + source "drivers/staging/media/solo6x10/Kconfig" +source "drivers/staging/media/omap4iss/Kconfig" + # Keep LIRC at the end, as it has sub-menus source "drivers/staging/media/lirc/Kconfig" diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile index eb7f30b1ccd8..bedc62aaede6 100644 --- a/drivers/staging/media/Makefile +++ b/drivers/staging/media/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_DVB_AS102) += as102/ +obj-$(CONFIG_I2C_BCM2048) += bcm2048/ obj-$(CONFIG_DVB_CXD2099) += cxd2099/ obj-$(CONFIG_LIRC_STAGING) += lirc/ obj-$(CONFIG_SOLO6X10) += solo6x10/ @@ -6,3 +7,7 @@ obj-$(CONFIG_VIDEO_DT3155) += dt3155v4l/ obj-$(CONFIG_VIDEO_GO7007) += go7007/ obj-$(CONFIG_USB_MSI3101) += msi3101/ obj-$(CONFIG_VIDEO_DM365_VPFE) += davinci_vpfe/ +obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/ +obj-$(CONFIG_USB_SN9C102) += sn9c102/ +obj-$(CONFIG_VIDEO_OMAP2) += omap24xx/ +obj-$(CONFIG_VIDEO_TCM825X) += omap24xx/ diff --git a/drivers/staging/media/as102/as102_drv.c b/drivers/staging/media/as102/as102_drv.c index 8b7bb9547079..09d64cd67502 100644 --- a/drivers/staging/media/as102/as102_drv.c +++ b/drivers/staging/media/as102/as102_drv.c @@ -111,8 +111,6 @@ static int as10x_pid_filter(struct as102_dev_t *dev, struct as10x_bus_adapter_t *bus_adap = &dev->bus_adap; int ret = -EFAULT; - ENTER(); - if (mutex_lock_interruptible(&dev->bus_adap.lock)) { dprintk(debug, "mutex_lock_interruptible(lock) failed !\n"); return -EBUSY; @@ -133,15 +131,14 @@ static int as10x_pid_filter(struct as102_dev_t *dev, filter.pid = pid; ret = as10x_cmd_add_PID_filter(bus_adap, &filter); - dprintk(debug, "ADD_PID_FILTER([%02d -> %02d], 0x%04x) ret = %d\n", + dprintk(debug, + "ADD_PID_FILTER([%02d -> %02d], 0x%04x) ret = %d\n", index, filter.idx, filter.pid, ret); break; } } mutex_unlock(&dev->bus_adap.lock); - - LEAVE(); return ret; } @@ -151,8 +148,6 @@ static int as102_dvb_dmx_start_feed(struct dvb_demux_feed *dvbdmxfeed) struct dvb_demux *demux = dvbdmxfeed->demux; struct as102_dev_t *as102_dev = demux->priv; - ENTER(); - if (mutex_lock_interruptible(&as102_dev->sem)) return -ERESTARTSYS; @@ -164,7 +159,6 @@ static int as102_dvb_dmx_start_feed(struct dvb_demux_feed *dvbdmxfeed) ret = as102_start_stream(as102_dev); mutex_unlock(&as102_dev->sem); - LEAVE(); return ret; } @@ -173,8 +167,6 @@ static int as102_dvb_dmx_stop_feed(struct dvb_demux_feed *dvbdmxfeed) struct dvb_demux *demux = dvbdmxfeed->demux; struct as102_dev_t *as102_dev = demux->priv; - ENTER(); - if (mutex_lock_interruptible(&as102_dev->sem)) return -ERESTARTSYS; @@ -186,7 +178,6 @@ static int as102_dvb_dmx_stop_feed(struct dvb_demux_feed *dvbdmxfeed) dvbdmxfeed->pid, 0); mutex_unlock(&as102_dev->sem); - LEAVE(); return 0; } diff --git a/drivers/staging/media/as102/as102_drv.h b/drivers/staging/media/as102/as102_drv.h index b0e5a23bd532..a06837dcc05d 100644 --- a/drivers/staging/media/as102/as102_drv.h +++ b/drivers/staging/media/as102/as102_drv.h @@ -38,14 +38,6 @@ extern int elna_enable; printk(args); \ } } while (0) -#ifdef TRACE -#define ENTER() pr_debug(">> enter %s\n", __func__) -#define LEAVE() pr_debug("<< leave %s\n", __func__) -#else -#define ENTER() -#define LEAVE() -#endif - #define AS102_DEVICE_MAJOR 192 #define AS102_USB_BUF_SIZE 512 diff --git a/drivers/staging/media/as102/as102_fe.c b/drivers/staging/media/as102/as102_fe.c index 9ce8c9daa2e7..b686b7617cdc 100644 --- a/drivers/staging/media/as102/as102_fe.c +++ b/drivers/staging/media/as102/as102_fe.c @@ -34,8 +34,6 @@ static int as102_fe_set_frontend(struct dvb_frontend *fe) struct as102_dev_t *dev; struct as10x_tune_args tune_args = { 0 }; - ENTER(); - dev = (struct as102_dev_t *) fe->tuner_priv; if (dev == NULL) return -ENODEV; @@ -52,7 +50,6 @@ static int as102_fe_set_frontend(struct dvb_frontend *fe) mutex_unlock(&dev->bus_adap.lock); - LEAVE(); return (ret < 0) ? -EINVAL : 0; } @@ -63,8 +60,6 @@ static int as102_fe_get_frontend(struct dvb_frontend *fe) struct as102_dev_t *dev; struct as10x_tps tps = { 0 }; - ENTER(); - dev = (struct as102_dev_t *) fe->tuner_priv; if (dev == NULL) return -EINVAL; @@ -80,13 +75,11 @@ static int as102_fe_get_frontend(struct dvb_frontend *fe) mutex_unlock(&dev->bus_adap.lock); - LEAVE(); return (ret < 0) ? -EINVAL : 0; } static int as102_fe_get_tune_settings(struct dvb_frontend *fe, struct dvb_frontend_tune_settings *settings) { - ENTER(); #if 0 dprintk(debug, "step_size = %d\n", settings->step_size); @@ -97,7 +90,6 @@ static int as102_fe_get_tune_settings(struct dvb_frontend *fe, settings->min_delay_ms = 1000; - LEAVE(); return 0; } @@ -108,8 +100,6 @@ static int as102_fe_read_status(struct dvb_frontend *fe, fe_status_t *status) struct as102_dev_t *dev; struct as10x_tune_status tstate = { 0 }; - ENTER(); - dev = (struct as102_dev_t *) fe->tuner_priv; if (dev == NULL) return -ENODEV; @@ -151,8 +141,8 @@ static int as102_fe_read_status(struct dvb_frontend *fe, fe_status_t *status) if (as10x_cmd_get_demod_stats(&dev->bus_adap, (struct as10x_demod_stats *) &dev->demod_stats) < 0) { memset(&dev->demod_stats, 0, sizeof(dev->demod_stats)); - dprintk(debug, "as10x_cmd_get_demod_stats failed " - "(probably not tuned)\n"); + dprintk(debug, + "as10x_cmd_get_demod_stats failed (probably not tuned)\n"); } else { dprintk(debug, "demod status: fc: 0x%08x, bad fc: 0x%08x, " @@ -168,7 +158,6 @@ static int as102_fe_read_status(struct dvb_frontend *fe, fe_status_t *status) out: mutex_unlock(&dev->bus_adap.lock); - LEAVE(); return ret; } @@ -183,15 +172,12 @@ static int as102_fe_read_snr(struct dvb_frontend *fe, u16 *snr) { struct as102_dev_t *dev; - ENTER(); - dev = (struct as102_dev_t *) fe->tuner_priv; if (dev == NULL) return -ENODEV; *snr = dev->demod_stats.mer; - LEAVE(); return 0; } @@ -199,15 +185,12 @@ static int as102_fe_read_ber(struct dvb_frontend *fe, u32 *ber) { struct as102_dev_t *dev; - ENTER(); - dev = (struct as102_dev_t *) fe->tuner_priv; if (dev == NULL) return -ENODEV; *ber = dev->ber; - LEAVE(); return 0; } @@ -216,15 +199,12 @@ static int as102_fe_read_signal_strength(struct dvb_frontend *fe, { struct as102_dev_t *dev; - ENTER(); - dev = (struct as102_dev_t *) fe->tuner_priv; if (dev == NULL) return -ENODEV; *strength = (((0xffff * 400) * dev->signal_strength + 41000) * 2); - LEAVE(); return 0; } @@ -232,8 +212,6 @@ static int as102_fe_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) { struct as102_dev_t *dev; - ENTER(); - dev = (struct as102_dev_t *) fe->tuner_priv; if (dev == NULL) return -ENODEV; @@ -243,7 +221,6 @@ static int as102_fe_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) else *ucblocks = 0; - LEAVE(); return 0; } @@ -252,8 +229,6 @@ static int as102_fe_ts_bus_ctrl(struct dvb_frontend *fe, int acquire) struct as102_dev_t *dev; int ret; - ENTER(); - dev = (struct as102_dev_t *) fe->tuner_priv; if (dev == NULL) return -ENODEV; @@ -263,7 +238,8 @@ static int as102_fe_ts_bus_ctrl(struct dvb_frontend *fe, int acquire) if (acquire) { if (elna_enable) - as10x_cmd_set_context(&dev->bus_adap, CONTEXT_LNA, dev->elna_cfg); + as10x_cmd_set_context(&dev->bus_adap, + CONTEXT_LNA, dev->elna_cfg); ret = as10x_cmd_turn_on(&dev->bus_adap); } else { @@ -272,7 +248,6 @@ static int as102_fe_ts_bus_ctrl(struct dvb_frontend *fe, int acquire) mutex_unlock(&dev->bus_adap.lock); - LEAVE(); return ret; } @@ -581,8 +556,8 @@ static void as102_fe_copy_tune_parameters(struct as10x_tune_args *tune_args, as102_fe_get_code_rate(params->code_rate_LP); } - dprintk(debug, "\thierarchy: 0x%02x " - "selected: %s code_rate_%s: 0x%02x\n", + dprintk(debug, + "\thierarchy: 0x%02x selected: %s code_rate_%s: 0x%02x\n", tune_args->hierarchy, tune_args->hier_select == HIER_HIGH_PRIORITY ? "HP" : "LP", diff --git a/drivers/staging/media/as102/as102_fw.c b/drivers/staging/media/as102/as102_fw.c index b9670ee41b4e..f33f752c0aad 100644 --- a/drivers/staging/media/as102/as102_fw.c +++ b/drivers/staging/media/as102/as102_fw.c @@ -26,10 +26,10 @@ #include "as102_drv.h" #include "as102_fw.h" -char as102_st_fw1[] = "as102_data1_st.hex"; -char as102_st_fw2[] = "as102_data2_st.hex"; -char as102_dt_fw1[] = "as102_data1_dt.hex"; -char as102_dt_fw2[] = "as102_data2_dt.hex"; +static const char as102_st_fw1[] = "as102_data1_st.hex"; +static const char as102_st_fw2[] = "as102_data2_st.hex"; +static const char as102_dt_fw1[] = "as102_data1_dt.hex"; +static const char as102_dt_fw2[] = "as102_data2_dt.hex"; static unsigned char atohx(unsigned char *dst, char *src) { @@ -109,8 +109,6 @@ static int as102_firmware_upload(struct as10x_bus_adapter_t *bus_adap, int total_read_bytes = 0, errno = 0; unsigned char addr_has_changed = 0; - ENTER(); - for (total_read_bytes = 0; total_read_bytes < firmware->size; ) { int read_bytes = 0, data_len = 0; @@ -158,7 +156,6 @@ static int as102_firmware_upload(struct as10x_bus_adapter_t *bus_adap, } } error: - LEAVE(); return (errno == 0) ? total_read_bytes : errno; } @@ -167,11 +164,9 @@ int as102_fw_upload(struct as10x_bus_adapter_t *bus_adap) int errno = -EFAULT; const struct firmware *firmware = NULL; unsigned char *cmd_buf = NULL; - char *fw1, *fw2; + const char *fw1, *fw2; struct usb_device *dev = bus_adap->usb_dev; - ENTER(); - /* select fw file to upload */ if (dual_tuner) { fw1 = as102_dt_fw1; @@ -233,6 +228,5 @@ error: kfree(cmd_buf); release_firmware(firmware); - LEAVE(); return errno; } diff --git a/drivers/staging/media/as102/as102_usb_drv.c b/drivers/staging/media/as102/as102_usb_drv.c index 9f275f020150..e4a69454ebeb 100644 --- a/drivers/staging/media/as102/as102_usb_drv.c +++ b/drivers/staging/media/as102/as102_usb_drv.c @@ -92,7 +92,6 @@ static int as102_usb_xfer_cmd(struct as10x_bus_adapter_t *bus_adap, unsigned char *recv_buf, int recv_buf_len) { int ret = 0; - ENTER(); if (send_buf != NULL) { ret = usb_control_msg(bus_adap->usb_dev, @@ -140,7 +139,6 @@ static int as102_usb_xfer_cmd(struct as10x_bus_adapter_t *bus_adap, #endif } - LEAVE(); return ret; } @@ -191,7 +189,7 @@ static int as102_read_ep2(struct as10x_bus_adapter_t *bus_adap, return ret ? ret : actual_len; } -struct as102_priv_ops_t as102_priv_ops = { +static struct as102_priv_ops_t as102_priv_ops = { .upload_fw_pkt = as102_send_ep1, .xfer_cmd = as102_usb_xfer_cmd, .as102_read_ep2 = as102_read_ep2, @@ -240,8 +238,6 @@ static void as102_free_usb_stream_buffer(struct as102_dev_t *dev) { int i; - ENTER(); - for (i = 0; i < MAX_STREAM_URB; i++) usb_free_urb(dev->stream_urb[i]); @@ -249,15 +245,12 @@ static void as102_free_usb_stream_buffer(struct as102_dev_t *dev) MAX_STREAM_URB * AS102_USB_BUF_SIZE, dev->stream, dev->dma_addr); - LEAVE(); } static int as102_alloc_usb_stream_buffer(struct as102_dev_t *dev) { int i, ret = 0; - ENTER(); - dev->stream = usb_alloc_coherent(dev->bus_adap.usb_dev, MAX_STREAM_URB * AS102_USB_BUF_SIZE, GFP_KERNEL, @@ -287,7 +280,6 @@ static int as102_alloc_usb_stream_buffer(struct as102_dev_t *dev) dev->stream_urb[i] = urb; } - LEAVE(); return ret; } @@ -318,23 +310,17 @@ static void as102_usb_release(struct kref *kref) { struct as102_dev_t *as102_dev; - ENTER(); - as102_dev = container_of(kref, struct as102_dev_t, kref); if (as102_dev != NULL) { usb_put_dev(as102_dev->bus_adap.usb_dev); kfree(as102_dev); } - - LEAVE(); } static void as102_usb_disconnect(struct usb_interface *intf) { struct as102_dev_t *as102_dev; - ENTER(); - /* extract as102_dev_t from usb_device private data */ as102_dev = usb_get_intfdata(intf); @@ -353,8 +339,6 @@ static void as102_usb_disconnect(struct usb_interface *intf) kref_put(&as102_dev->kref, as102_usb_release); pr_info("%s: device has been disconnected\n", DRIVER_NAME); - - LEAVE(); } static int as102_usb_probe(struct usb_interface *intf, @@ -364,8 +348,6 @@ static int as102_usb_probe(struct usb_interface *intf, struct as102_dev_t *as102_dev; int i; - ENTER(); - /* This should never actually happen */ if (ARRAY_SIZE(as102_usb_id_table) != (sizeof(as102_device_names) / sizeof(const char *))) { @@ -419,15 +401,21 @@ static int as102_usb_probe(struct usb_interface *intf, /* request buffer allocation for streaming */ ret = as102_alloc_usb_stream_buffer(as102_dev); if (ret != 0) - goto failed; + goto failed_stream; /* register dvb layer */ ret = as102_dvb_register(as102_dev); + if (ret != 0) + goto failed_dvb; - LEAVE(); return ret; +failed_dvb: + as102_free_usb_stream_buffer(as102_dev); +failed_stream: + usb_deregister_dev(intf, &as102_usb_class_driver); failed: + usb_put_dev(as102_dev->bus_adap.usb_dev); usb_set_intfdata(intf, NULL); kfree(as102_dev); return ret; @@ -439,8 +427,6 @@ static int as102_open(struct inode *inode, struct file *file) struct usb_interface *intf = NULL; struct as102_dev_t *dev = NULL; - ENTER(); - /* read minor from inode */ minor = iminor(inode); @@ -467,7 +453,6 @@ static int as102_open(struct inode *inode, struct file *file) kref_get(&dev->kref); exit: - LEAVE(); return ret; } @@ -476,15 +461,12 @@ static int as102_release(struct inode *inode, struct file *file) int ret = 0; struct as102_dev_t *dev = NULL; - ENTER(); - dev = file->private_data; if (dev != NULL) { /* decrement the count on our device */ kref_put(&dev->kref, as102_usb_release); } - LEAVE(); return ret; } diff --git a/drivers/staging/media/as102/as10x_cmd.c b/drivers/staging/media/as102/as10x_cmd.c index a73df10982d0..9e49f15a7c9f 100644 --- a/drivers/staging/media/as102/as10x_cmd.c +++ b/drivers/staging/media/as102/as10x_cmd.c @@ -34,8 +34,6 @@ int as10x_cmd_turn_on(struct as10x_bus_adapter_t *adap) int error = AS10X_CMD_ERROR; struct as10x_cmd_t *pcmd, *prsp; - ENTER(); - pcmd = adap->cmd; prsp = adap->rsp; @@ -63,7 +61,6 @@ int as10x_cmd_turn_on(struct as10x_bus_adapter_t *adap) error = as10x_rsp_parse(prsp, CONTROL_PROC_TURNON_RSP); out: - LEAVE(); return error; } @@ -78,8 +75,6 @@ int as10x_cmd_turn_off(struct as10x_bus_adapter_t *adap) int error = AS10X_CMD_ERROR; struct as10x_cmd_t *pcmd, *prsp; - ENTER(); - pcmd = adap->cmd; prsp = adap->rsp; @@ -106,7 +101,6 @@ int as10x_cmd_turn_off(struct as10x_bus_adapter_t *adap) error = as10x_rsp_parse(prsp, CONTROL_PROC_TURNOFF_RSP); out: - LEAVE(); return error; } @@ -123,8 +117,6 @@ int as10x_cmd_set_tune(struct as10x_bus_adapter_t *adap, int error = AS10X_CMD_ERROR; struct as10x_cmd_t *preq, *prsp; - ENTER(); - preq = adap->cmd; prsp = adap->rsp; @@ -164,7 +156,6 @@ int as10x_cmd_set_tune(struct as10x_bus_adapter_t *adap, error = as10x_rsp_parse(prsp, CONTROL_PROC_SETTUNE_RSP); out: - LEAVE(); return error; } @@ -181,8 +172,6 @@ int as10x_cmd_get_tune_status(struct as10x_bus_adapter_t *adap, int error = AS10X_CMD_ERROR; struct as10x_cmd_t *preq, *prsp; - ENTER(); - preq = adap->cmd; prsp = adap->rsp; @@ -220,7 +209,6 @@ int as10x_cmd_get_tune_status(struct as10x_bus_adapter_t *adap, pstatus->BER = le16_to_cpu(prsp->body.get_tune_status.rsp.sts.BER); out: - LEAVE(); return error; } @@ -236,8 +224,6 @@ int as10x_cmd_get_tps(struct as10x_bus_adapter_t *adap, struct as10x_tps *ptps) int error = AS10X_CMD_ERROR; struct as10x_cmd_t *pcmd, *prsp; - ENTER(); - pcmd = adap->cmd; prsp = adap->rsp; @@ -281,7 +267,6 @@ int as10x_cmd_get_tps(struct as10x_bus_adapter_t *adap, struct as10x_tps *ptps) ptps->cell_ID = le16_to_cpu(prsp->body.get_tps.rsp.tps.cell_ID); out: - LEAVE(); return error; } @@ -298,8 +283,6 @@ int as10x_cmd_get_demod_stats(struct as10x_bus_adapter_t *adap, int error = AS10X_CMD_ERROR; struct as10x_cmd_t *pcmd, *prsp; - ENTER(); - pcmd = adap->cmd; prsp = adap->rsp; @@ -343,7 +326,6 @@ int as10x_cmd_get_demod_stats(struct as10x_bus_adapter_t *adap, prsp->body.get_demod_stats.rsp.stats.has_started; out: - LEAVE(); return error; } @@ -361,8 +343,6 @@ int as10x_cmd_get_impulse_resp(struct as10x_bus_adapter_t *adap, int error = AS10X_CMD_ERROR; struct as10x_cmd_t *pcmd, *prsp; - ENTER(); - pcmd = adap->cmd; prsp = adap->rsp; @@ -397,7 +377,6 @@ int as10x_cmd_get_impulse_resp(struct as10x_bus_adapter_t *adap, *is_ready = prsp->body.get_impulse_rsp.rsp.is_ready; out: - LEAVE(); return error; } diff --git a/drivers/staging/media/as102/as10x_cmd_cfg.c b/drivers/staging/media/as102/as10x_cmd_cfg.c index 4a2bbd766655..b1e300d88753 100644 --- a/drivers/staging/media/as102/as10x_cmd_cfg.c +++ b/drivers/staging/media/as102/as10x_cmd_cfg.c @@ -40,8 +40,6 @@ int as10x_cmd_get_context(struct as10x_bus_adapter_t *adap, uint16_t tag, int error; struct as10x_cmd_t *pcmd, *prsp; - ENTER(); - pcmd = adap->cmd; prsp = adap->rsp; @@ -81,7 +79,6 @@ int as10x_cmd_get_context(struct as10x_bus_adapter_t *adap, uint16_t tag, } out: - LEAVE(); return error; } @@ -99,8 +96,6 @@ int as10x_cmd_set_context(struct as10x_bus_adapter_t *adap, uint16_t tag, int error; struct as10x_cmd_t *pcmd, *prsp; - ENTER(); - pcmd = adap->cmd; prsp = adap->rsp; @@ -136,7 +131,6 @@ int as10x_cmd_set_context(struct as10x_bus_adapter_t *adap, uint16_t tag, error = as10x_context_rsp_parse(prsp, CONTROL_PROC_CONTEXT_RSP); out: - LEAVE(); return error; } @@ -156,8 +150,6 @@ int as10x_cmd_eLNA_change_mode(struct as10x_bus_adapter_t *adap, uint8_t mode) int error; struct as10x_cmd_t *pcmd, *prsp; - ENTER(); - pcmd = adap->cmd; prsp = adap->rsp; @@ -188,7 +180,6 @@ int as10x_cmd_eLNA_change_mode(struct as10x_bus_adapter_t *adap, uint8_t mode) error = as10x_rsp_parse(prsp, CONTROL_PROC_ELNA_CHANGE_MODE_RSP); out: - LEAVE(); return error; } diff --git a/drivers/staging/media/as102/as10x_cmd_stream.c b/drivers/staging/media/as102/as10x_cmd_stream.c index 6d000f60fb0e..1088ca1fe92f 100644 --- a/drivers/staging/media/as102/as10x_cmd_stream.c +++ b/drivers/staging/media/as102/as10x_cmd_stream.c @@ -34,8 +34,6 @@ int as10x_cmd_add_PID_filter(struct as10x_bus_adapter_t *adap, int error; struct as10x_cmd_t *pcmd, *prsp; - ENTER(); - pcmd = adap->cmd; prsp = adap->rsp; @@ -77,7 +75,6 @@ int as10x_cmd_add_PID_filter(struct as10x_bus_adapter_t *adap, } out: - LEAVE(); return error; } @@ -94,8 +91,6 @@ int as10x_cmd_del_PID_filter(struct as10x_bus_adapter_t *adap, int error; struct as10x_cmd_t *pcmd, *prsp; - ENTER(); - pcmd = adap->cmd; prsp = adap->rsp; @@ -126,7 +121,6 @@ int as10x_cmd_del_PID_filter(struct as10x_bus_adapter_t *adap, error = as10x_rsp_parse(prsp, CONTROL_PROC_REMOVEFILTER_RSP); out: - LEAVE(); return error; } @@ -141,8 +135,6 @@ int as10x_cmd_start_streaming(struct as10x_bus_adapter_t *adap) int error; struct as10x_cmd_t *pcmd, *prsp; - ENTER(); - pcmd = adap->cmd; prsp = adap->rsp; @@ -172,7 +164,6 @@ int as10x_cmd_start_streaming(struct as10x_bus_adapter_t *adap) error = as10x_rsp_parse(prsp, CONTROL_PROC_START_STREAMING_RSP); out: - LEAVE(); return error; } @@ -187,8 +178,6 @@ int as10x_cmd_stop_streaming(struct as10x_bus_adapter_t *adap) int8_t error; struct as10x_cmd_t *pcmd, *prsp; - ENTER(); - pcmd = adap->cmd; prsp = adap->rsp; @@ -218,6 +207,5 @@ int as10x_cmd_stop_streaming(struct as10x_bus_adapter_t *adap) error = as10x_rsp_parse(prsp, CONTROL_PROC_STOP_STREAMING_RSP); out: - LEAVE(); return error; } diff --git a/drivers/staging/media/bcm2048/Kconfig b/drivers/staging/media/bcm2048/Kconfig new file mode 100644 index 000000000000..a9fc6e186494 --- /dev/null +++ b/drivers/staging/media/bcm2048/Kconfig @@ -0,0 +1,13 @@ +# +# Multimedia Video device configuration +# + +config I2C_BCM2048 + tristate "Broadcom BCM2048 FM Radio Receiver support" + depends on I2C && VIDEO_V4L2 && RADIO_ADAPTERS + ---help--- + Say Y here if you want support to BCM2048 FM Radio Receiver. + This device driver supports only i2c bus. + + To compile this driver as a module, choose M here: the + module will be called radio-bcm2048. diff --git a/drivers/staging/media/bcm2048/Makefile b/drivers/staging/media/bcm2048/Makefile new file mode 100644 index 000000000000..b4f5663d1408 --- /dev/null +++ b/drivers/staging/media/bcm2048/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_I2C_BCM2048) += radio-bcm2048.o diff --git a/drivers/staging/media/bcm2048/TODO b/drivers/staging/media/bcm2048/TODO new file mode 100644 index 000000000000..051f85dbe89e --- /dev/null +++ b/drivers/staging/media/bcm2048/TODO @@ -0,0 +1,24 @@ +TODO: + +From the initial code review: + +The main thing you need to do is to implement all the controls using the +control framework (see Documentation/video4linux/v4l2-controls.txt). +Most drivers are by now converted to the control framework, so you will +find many examples of how to do this in drivers/media/radio. + +The sysfs stuff should be replaced by controls as well. A lot of the RDS +support is now available as controls (although there may well be some +missing features, but that is easy enough to add). Since the RDS data is +actually read() from the device I am not sure whether the RDS +properties/controls should be there at all. + +Correct Coding Style, as this driver also violates several Style +rules, and do evil tricks, like returning from a function inside a +macro. + +Finally this driver should probably be split up into two parts: one +v4l2_subdev-based core driver and one platform driver. See e.g. +radio-si4713/si4713-i2c.c as a good example. But I would wait with that +until the rest of the driver is cleaned up. Then I have a better idea of +whether this is necessary or not. diff --git a/drivers/staging/media/bcm2048/radio-bcm2048.c b/drivers/staging/media/bcm2048/radio-bcm2048.c new file mode 100644 index 000000000000..b2cd3a85166d --- /dev/null +++ b/drivers/staging/media/bcm2048/radio-bcm2048.c @@ -0,0 +1,2744 @@ +/* + * drivers/staging/media/radio-bcm2048.c + * + * Driver for I2C Broadcom BCM2048 FM Radio Receiver: + * + * Copyright (C) Nokia Corporation + * Contact: Eero Nurkkala <ext-eero.nurkkala@nokia.com> + * + * Copyright (C) Nils Faerber <nils.faerber@kernelconcepts.de> + * + * 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. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +/* + * History: + * Eero Nurkkala <ext-eero.nurkkala@nokia.com> + * Version 0.0.1 + * - Initial implementation + * 2010-02-21 Nils Faerber <nils.faerber@kernelconcepts.de> + * Version 0.0.2 + * - Add support for interrupt driven rds data reading + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/version.h> +#include <linux/interrupt.h> +#include <linux/sysfs.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> +#include "radio-bcm2048.h" + +/* driver definitions */ +#define BCM2048_DRIVER_AUTHOR "Eero Nurkkala <ext-eero.nurkkala@nokia.com>" +#define BCM2048_DRIVER_NAME BCM2048_NAME +#define BCM2048_DRIVER_VERSION KERNEL_VERSION(0, 0, 1) +#define BCM2048_DRIVER_CARD "Broadcom bcm2048 FM Radio Receiver" +#define BCM2048_DRIVER_DESC "I2C driver for BCM2048 FM Radio Receiver" + +/* I2C Control Registers */ +#define BCM2048_I2C_FM_RDS_SYSTEM 0x00 +#define BCM2048_I2C_FM_CTRL 0x01 +#define BCM2048_I2C_RDS_CTRL0 0x02 +#define BCM2048_I2C_RDS_CTRL1 0x03 +#define BCM2048_I2C_FM_AUDIO_PAUSE 0x04 +#define BCM2048_I2C_FM_AUDIO_CTRL0 0x05 +#define BCM2048_I2C_FM_AUDIO_CTRL1 0x06 +#define BCM2048_I2C_FM_SEARCH_CTRL0 0x07 +#define BCM2048_I2C_FM_SEARCH_CTRL1 0x08 +#define BCM2048_I2C_FM_SEARCH_TUNE_MODE 0x09 +#define BCM2048_I2C_FM_FREQ0 0x0a +#define BCM2048_I2C_FM_FREQ1 0x0b +#define BCM2048_I2C_FM_AF_FREQ0 0x0c +#define BCM2048_I2C_FM_AF_FREQ1 0x0d +#define BCM2048_I2C_FM_CARRIER 0x0e +#define BCM2048_I2C_FM_RSSI 0x0f +#define BCM2048_I2C_FM_RDS_MASK0 0x10 +#define BCM2048_I2C_FM_RDS_MASK1 0x11 +#define BCM2048_I2C_FM_RDS_FLAG0 0x12 +#define BCM2048_I2C_FM_RDS_FLAG1 0x13 +#define BCM2048_I2C_RDS_WLINE 0x14 +#define BCM2048_I2C_RDS_BLKB_MATCH0 0x16 +#define BCM2048_I2C_RDS_BLKB_MATCH1 0x17 +#define BCM2048_I2C_RDS_BLKB_MASK0 0x18 +#define BCM2048_I2C_RDS_BLKB_MASK1 0x19 +#define BCM2048_I2C_RDS_PI_MATCH0 0x1a +#define BCM2048_I2C_RDS_PI_MATCH1 0x1b +#define BCM2048_I2C_RDS_PI_MASK0 0x1c +#define BCM2048_I2C_RDS_PI_MASK1 0x1d +#define BCM2048_I2C_SPARE1 0x20 +#define BCM2048_I2C_SPARE2 0x21 +#define BCM2048_I2C_FM_RDS_REV 0x28 +#define BCM2048_I2C_SLAVE_CONFIGURATION 0x29 +#define BCM2048_I2C_RDS_DATA 0x80 +#define BCM2048_I2C_FM_BEST_TUNE_MODE 0x90 + +/* BCM2048_I2C_FM_RDS_SYSTEM */ +#define BCM2048_FM_ON 0x01 +#define BCM2048_RDS_ON 0x02 + +/* BCM2048_I2C_FM_CTRL */ +#define BCM2048_BAND_SELECT 0x01 +#define BCM2048_STEREO_MONO_AUTO_SELECT 0x02 +#define BCM2048_STEREO_MONO_MANUAL_SELECT 0x04 +#define BCM2048_STEREO_MONO_BLEND_SWITCH 0x08 +#define BCM2048_HI_LO_INJECTION 0x10 + +/* BCM2048_I2C_RDS_CTRL0 */ +#define BCM2048_RBDS_RDS_SELECT 0x01 +#define BCM2048_FLUSH_FIFO 0x02 + +/* BCM2048_I2C_FM_AUDIO_PAUSE */ +#define BCM2048_AUDIO_PAUSE_RSSI_TRESH 0x0f +#define BCM2048_AUDIO_PAUSE_DURATION 0xf0 + +/* BCM2048_I2C_FM_AUDIO_CTRL0 */ +#define BCM2048_RF_MUTE 0x01 +#define BCM2048_MANUAL_MUTE 0x02 +#define BCM2048_DAC_OUTPUT_LEFT 0x04 +#define BCM2048_DAC_OUTPUT_RIGHT 0x08 +#define BCM2048_AUDIO_ROUTE_DAC 0x10 +#define BCM2048_AUDIO_ROUTE_I2S 0x20 +#define BCM2048_DE_EMPHASIS_SELECT 0x40 +#define BCM2048_AUDIO_BANDWIDTH_SELECT 0x80 + +/* BCM2048_I2C_FM_SEARCH_CTRL0 */ +#define BCM2048_SEARCH_RSSI_THRESHOLD 0x7f +#define BCM2048_SEARCH_DIRECTION 0x80 + +/* BCM2048_I2C_FM_SEARCH_TUNE_MODE */ +#define BCM2048_FM_AUTO_SEARCH 0x03 + +/* BCM2048_I2C_FM_RSSI */ +#define BCM2048_RSSI_VALUE 0xff + +/* BCM2048_I2C_FM_RDS_MASK0 */ +/* BCM2048_I2C_FM_RDS_MASK1 */ +#define BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED 0x01 +#define BCM2048_FM_FLAG_SEARCH_TUNE_FAIL 0x02 +#define BCM2048_FM_FLAG_RSSI_LOW 0x04 +#define BCM2048_FM_FLAG_CARRIER_ERROR_HIGH 0x08 +#define BCM2048_FM_FLAG_AUDIO_PAUSE_INDICATION 0x10 +#define BCM2048_FLAG_STEREO_DETECTED 0x20 +#define BCM2048_FLAG_STEREO_ACTIVE 0x40 + +/* BCM2048_I2C_RDS_DATA */ +#define BCM2048_SLAVE_ADDRESS 0x3f +#define BCM2048_SLAVE_ENABLE 0x80 + +/* BCM2048_I2C_FM_BEST_TUNE_MODE */ +#define BCM2048_BEST_TUNE_MODE 0x80 + +#define BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED 0x01 +#define BCM2048_FM_FLAG_SEARCH_TUNE_FAIL 0x02 +#define BCM2048_FM_FLAG_RSSI_LOW 0x04 +#define BCM2048_FM_FLAG_CARRIER_ERROR_HIGH 0x08 +#define BCM2048_FM_FLAG_AUDIO_PAUSE_INDICATION 0x10 +#define BCM2048_FLAG_STEREO_DETECTED 0x20 +#define BCM2048_FLAG_STEREO_ACTIVE 0x40 + +#define BCM2048_RDS_FLAG_FIFO_WLINE 0x02 +#define BCM2048_RDS_FLAG_B_BLOCK_MATCH 0x08 +#define BCM2048_RDS_FLAG_SYNC_LOST 0x10 +#define BCM2048_RDS_FLAG_PI_MATCH 0x20 + +#define BCM2048_RDS_MARK_END_BYTE0 0x7C +#define BCM2048_RDS_MARK_END_BYTEN 0xFF + +#define BCM2048_FM_FLAGS_ALL (FM_FLAG_SEARCH_TUNE_FINISHED | \ + FM_FLAG_SEARCH_TUNE_FAIL | \ + FM_FLAG_RSSI_LOW | \ + FM_FLAG_CARRIER_ERROR_HIGH | \ + FM_FLAG_AUDIO_PAUSE_INDICATION | \ + FLAG_STEREO_DETECTED | FLAG_STEREO_ACTIVE) + +#define BCM2048_RDS_FLAGS_ALL (RDS_FLAG_FIFO_WLINE | \ + RDS_FLAG_B_BLOCK_MATCH | \ + RDS_FLAG_SYNC_LOST | RDS_FLAG_PI_MATCH) + +#define BCM2048_DEFAULT_TIMEOUT 1500 +#define BCM2048_AUTO_SEARCH_TIMEOUT 3000 + + +#define BCM2048_FREQDEV_UNIT 10000 +#define BCM2048_FREQV4L2_MULTI 625 +#define dev_to_v4l2(f) ((f * BCM2048_FREQDEV_UNIT) / BCM2048_FREQV4L2_MULTI) +#define v4l2_to_dev(f) ((f * BCM2048_FREQV4L2_MULTI) / BCM2048_FREQDEV_UNIT) + +#define msb(x) ((u8)((u16) x >> 8)) +#define lsb(x) ((u8)((u16) x & 0x00FF)) +#define compose_u16(msb, lsb) (((u16)msb << 8) | lsb) + +#define BCM2048_DEFAULT_POWERING_DELAY 20 +#define BCM2048_DEFAULT_REGION 0x02 +#define BCM2048_DEFAULT_MUTE 0x01 +#define BCM2048_DEFAULT_RSSI_THRESHOLD 0x64 +#define BCM2048_DEFAULT_RDS_WLINE 0x7E + +#define BCM2048_FM_SEARCH_INACTIVE 0x00 +#define BCM2048_FM_PRE_SET_MODE 0x01 +#define BCM2048_FM_AUTO_SEARCH_MODE 0x02 +#define BCM2048_FM_AF_JUMP_MODE 0x03 + +#define BCM2048_FREQUENCY_BASE 64000 + +#define BCM2048_POWER_ON 0x01 +#define BCM2048_POWER_OFF 0x00 + +#define BCM2048_ITEM_ENABLED 0x01 +#define BCM2048_SEARCH_DIRECTION_UP 0x01 + +#define BCM2048_DE_EMPHASIS_75us 75 +#define BCM2048_DE_EMPHASIS_50us 50 + +#define BCM2048_SCAN_FAIL 0x00 +#define BCM2048_SCAN_OK 0x01 + +#define BCM2048_FREQ_ERROR_FLOOR -20 +#define BCM2048_FREQ_ERROR_ROOF 20 + +/* -60 dB is reported as full signal strenght */ +#define BCM2048_RSSI_LEVEL_BASE -60 +#define BCM2048_RSSI_LEVEL_ROOF -100 +#define BCM2048_RSSI_LEVEL_ROOF_NEG 100 +#define BCM2048_SIGNAL_MULTIPLIER (0xFFFF / \ + (BCM2048_RSSI_LEVEL_ROOF_NEG + \ + BCM2048_RSSI_LEVEL_BASE)) + +#define BCM2048_RDS_FIFO_DUPLE_SIZE 0x03 +#define BCM2048_RDS_CRC_MASK 0x0F +#define BCM2048_RDS_CRC_NONE 0x00 +#define BCM2048_RDS_CRC_MAX_2BITS 0x04 +#define BCM2048_RDS_CRC_LEAST_2BITS 0x08 +#define BCM2048_RDS_CRC_UNRECOVARABLE 0x0C + +#define BCM2048_RDS_BLOCK_MASK 0xF0 +#define BCM2048_RDS_BLOCK_A 0x00 +#define BCM2048_RDS_BLOCK_B 0x10 +#define BCM2048_RDS_BLOCK_C 0x20 +#define BCM2048_RDS_BLOCK_D 0x30 +#define BCM2048_RDS_BLOCK_C_SCORED 0x40 +#define BCM2048_RDS_BLOCK_E 0x60 + +#define BCM2048_RDS_RT 0x20 +#define BCM2048_RDS_PS 0x00 + +#define BCM2048_RDS_GROUP_AB_MASK 0x08 +#define BCM2048_RDS_GROUP_A 0x00 +#define BCM2048_RDS_GROUP_B 0x08 + +#define BCM2048_RDS_RT_AB_MASK 0x10 +#define BCM2048_RDS_RT_A 0x00 +#define BCM2048_RDS_RT_B 0x10 +#define BCM2048_RDS_RT_INDEX 0x0F + +#define BCM2048_RDS_PS_INDEX 0x03 + +struct rds_info { + u16 rds_pi; +#define BCM2048_MAX_RDS_RT (64 + 1) + u8 rds_rt[BCM2048_MAX_RDS_RT]; + u8 rds_rt_group_b; + u8 rds_rt_ab; +#define BCM2048_MAX_RDS_PS (8 + 1) + u8 rds_ps[BCM2048_MAX_RDS_PS]; + u8 rds_ps_group; + u8 rds_ps_group_cnt; +#define BCM2048_MAX_RDS_RADIO_TEXT 255 + u8 radio_text[BCM2048_MAX_RDS_RADIO_TEXT + 3]; + u8 text_len; +}; + +struct region_info { + u32 bottom_frequency; + u32 top_frequency; + u8 deemphasis; + u8 channel_spacing; + u8 region; +}; + +struct bcm2048_device { + struct i2c_client *client; + struct video_device *videodev; + struct work_struct work; + struct completion compl; + struct mutex mutex; + struct bcm2048_platform_data *platform_data; + struct rds_info rds_info; + struct region_info region_info; + u16 frequency; + u8 cache_fm_rds_system; + u8 cache_fm_ctrl; + u8 cache_fm_audio_ctrl0; + u8 cache_fm_search_ctrl0; + u8 power_state; + u8 rds_state; + u8 fifo_size; + u8 scan_state; + u8 mute_state; + + /* for rds data device read */ + wait_queue_head_t read_queue; + unsigned int users; + unsigned char rds_data_available; + unsigned int rd_index; +}; + +static int radio_nr = -1; /* radio device minor (-1 ==> auto assign) */ +module_param(radio_nr, int, 0); +MODULE_PARM_DESC(radio_nr, + "Minor number for radio device (-1 ==> auto assign)"); + +static struct region_info region_configs[] = { + /* USA */ + { + .channel_spacing = 20, + .bottom_frequency = 87500, + .top_frequency = 108000, + .deemphasis = 75, + .region = 0, + }, + /* Australia */ + { + .channel_spacing = 20, + .bottom_frequency = 87500, + .top_frequency = 108000, + .deemphasis = 50, + .region = 1, + }, + /* Europe */ + { + .channel_spacing = 10, + .bottom_frequency = 87500, + .top_frequency = 108000, + .deemphasis = 50, + .region = 2, + }, + /* Japan */ + { + .channel_spacing = 10, + .bottom_frequency = 76000, + .top_frequency = 90000, + .deemphasis = 50, + .region = 3, + }, + /* Japan wide band */ + { + .channel_spacing = 10, + .bottom_frequency = 76000, + .top_frequency = 108000, + .deemphasis = 50, + .region = 4, + }, +}; + +/* + * I2C Interface read / write + */ +static int bcm2048_send_command(struct bcm2048_device *bdev, unsigned int reg, + unsigned int value) +{ + struct i2c_client *client = bdev->client; + u8 data[2]; + + if (!bdev->power_state) { + dev_err(&bdev->client->dev, "bcm2048: chip not powered!\n"); + return -EIO; + } + + data[0] = reg & 0xff; + data[1] = value & 0xff; + + if (i2c_master_send(client, data, 2) == 2) { + return 0; + } else { + dev_err(&bdev->client->dev, "BCM I2C error!\n"); + dev_err(&bdev->client->dev, "Is Bluetooth up and running?\n"); + return -EIO; + } +} + +static int bcm2048_recv_command(struct bcm2048_device *bdev, unsigned int reg, + u8 *value) +{ + struct i2c_client *client = bdev->client; + + if (!bdev->power_state) { + dev_err(&bdev->client->dev, "bcm2048: chip not powered!\n"); + return -EIO; + } + + value[0] = i2c_smbus_read_byte_data(client, reg & 0xff); + + return 0; +} + +static int bcm2048_recv_duples(struct bcm2048_device *bdev, unsigned int reg, + u8 *value, u8 duples) +{ + struct i2c_client *client = bdev->client; + struct i2c_adapter *adap = client->adapter; + struct i2c_msg msg[2]; + u8 buf; + + if (!bdev->power_state) { + dev_err(&bdev->client->dev, "bcm2048: chip not powered!\n"); + return -EIO; + } + + buf = reg & 0xff; + + msg[0].addr = client->addr; + msg[0].flags = client->flags & I2C_M_TEN; + msg[0].len = 1; + msg[0].buf = &buf; + + msg[1].addr = client->addr; + msg[1].flags = client->flags & I2C_M_TEN; + msg[1].flags |= I2C_M_RD; + msg[1].len = duples; + msg[1].buf = value; + + return i2c_transfer(adap, msg, 2); +} + +/* + * BCM2048 - I2C register programming helpers + */ +static int bcm2048_set_power_state(struct bcm2048_device *bdev, u8 power) +{ + int err = 0; + + mutex_lock(&bdev->mutex); + + if (power) { + bdev->power_state = BCM2048_POWER_ON; + bdev->cache_fm_rds_system |= BCM2048_FM_ON; + } else { + bdev->cache_fm_rds_system &= ~BCM2048_FM_ON; + } + + /* + * Warning! FM cannot be turned off because then + * the I2C communications get ruined! + * Comment off the "if (power)" when the chip works! + */ + if (power) + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM, + bdev->cache_fm_rds_system); + msleep(BCM2048_DEFAULT_POWERING_DELAY); + + if (!power) + bdev->power_state = BCM2048_POWER_OFF; + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_power_state(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM, &value); + + mutex_unlock(&bdev->mutex); + + if (!err && (value & BCM2048_FM_ON)) + return BCM2048_POWER_ON; + + return err; +} + +static int bcm2048_set_rds_no_lock(struct bcm2048_device *bdev, u8 rds_on) +{ + int err; + u8 flags; + + bdev->cache_fm_rds_system &= ~BCM2048_RDS_ON; + + if (rds_on) { + bdev->cache_fm_rds_system |= BCM2048_RDS_ON; + bdev->rds_state = BCM2048_RDS_ON; + flags = BCM2048_RDS_FLAG_FIFO_WLINE; + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK1, + flags); + } else { + flags = 0; + bdev->rds_state = 0; + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK1, + flags); + memset(&bdev->rds_info, 0, sizeof(bdev->rds_info)); + } + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM, + bdev->cache_fm_rds_system); + + return err; +} + +static int bcm2048_get_rds_no_lock(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM, &value); + + if (!err && (value & BCM2048_RDS_ON)) + return BCM2048_ITEM_ENABLED; + + return err; +} + +static int bcm2048_set_rds(struct bcm2048_device *bdev, u8 rds_on) +{ + int err; + + mutex_lock(&bdev->mutex); + + err = bcm2048_set_rds_no_lock(bdev, rds_on); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_rds(struct bcm2048_device *bdev) +{ + int err; + + mutex_lock(&bdev->mutex); + + err = bcm2048_get_rds_no_lock(bdev); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_rds_pi(struct bcm2048_device *bdev) +{ + return bdev->rds_info.rds_pi; +} + +static int bcm2048_set_fm_automatic_stereo_mono(struct bcm2048_device *bdev, + u8 enabled) +{ + int err; + + mutex_lock(&bdev->mutex); + + bdev->cache_fm_ctrl &= ~BCM2048_STEREO_MONO_AUTO_SELECT; + + if (enabled) + bdev->cache_fm_ctrl |= BCM2048_STEREO_MONO_AUTO_SELECT; + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_CTRL, + bdev->cache_fm_ctrl); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_set_fm_hi_lo_injection(struct bcm2048_device *bdev, + u8 hi_lo) +{ + int err; + + mutex_lock(&bdev->mutex); + + bdev->cache_fm_ctrl &= ~BCM2048_HI_LO_INJECTION; + + if (hi_lo) + bdev->cache_fm_ctrl |= BCM2048_HI_LO_INJECTION; + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_CTRL, + bdev->cache_fm_ctrl); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_fm_hi_lo_injection(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_CTRL, &value); + + mutex_unlock(&bdev->mutex); + + if (!err && (value & BCM2048_HI_LO_INJECTION)) + return BCM2048_ITEM_ENABLED; + + return err; +} + +static int bcm2048_set_fm_frequency(struct bcm2048_device *bdev, u32 frequency) +{ + int err; + + if (frequency < bdev->region_info.bottom_frequency || + frequency > bdev->region_info.top_frequency) + return -EDOM; + + frequency -= BCM2048_FREQUENCY_BASE; + + mutex_lock(&bdev->mutex); + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_FREQ0, lsb(frequency)); + err |= bcm2048_send_command(bdev, BCM2048_I2C_FM_FREQ1, + msb(frequency)); + + if (!err) + bdev->frequency = frequency; + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_fm_frequency(struct bcm2048_device *bdev) +{ + int err; + u8 lsb, msb; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_FREQ0, &lsb); + err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_FREQ1, &msb); + + mutex_unlock(&bdev->mutex); + + if (err) + return err; + + err = compose_u16(msb, lsb); + err += BCM2048_FREQUENCY_BASE; + + return err; +} + +static int bcm2048_set_fm_af_frequency(struct bcm2048_device *bdev, + u32 frequency) +{ + int err; + + if (frequency < bdev->region_info.bottom_frequency || + frequency > bdev->region_info.top_frequency) + return -EDOM; + + frequency -= BCM2048_FREQUENCY_BASE; + + mutex_lock(&bdev->mutex); + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AF_FREQ0, + lsb(frequency)); + err |= bcm2048_send_command(bdev, BCM2048_I2C_FM_AF_FREQ1, + msb(frequency)); + if (!err) + bdev->frequency = frequency; + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_fm_af_frequency(struct bcm2048_device *bdev) +{ + int err; + u8 lsb, msb; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AF_FREQ0, &lsb); + err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_AF_FREQ1, &msb); + + mutex_unlock(&bdev->mutex); + + if (err) + return err; + + err = compose_u16(msb, lsb); + err += BCM2048_FREQUENCY_BASE; + + return err; +} + +static int bcm2048_set_fm_deemphasis(struct bcm2048_device *bdev, int d) +{ + int err; + u8 deemphasis; + + if (d == BCM2048_DE_EMPHASIS_75us) + deemphasis = BCM2048_DE_EMPHASIS_SELECT; + else + deemphasis = 0; + + mutex_lock(&bdev->mutex); + + bdev->cache_fm_audio_ctrl0 &= ~BCM2048_DE_EMPHASIS_SELECT; + bdev->cache_fm_audio_ctrl0 |= deemphasis; + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, + bdev->cache_fm_audio_ctrl0); + + if (!err) + bdev->region_info.deemphasis = d; + + mutex_unlock(&bdev->mutex); + + return err; +} + +static int bcm2048_get_fm_deemphasis(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, &value); + + mutex_unlock(&bdev->mutex); + + if (!err) { + if (value & BCM2048_DE_EMPHASIS_SELECT) + return BCM2048_DE_EMPHASIS_75us; + else + return BCM2048_DE_EMPHASIS_50us; + } + + return err; +} + +static int bcm2048_set_region(struct bcm2048_device *bdev, u8 region) +{ + int err; + u32 new_frequency = 0; + + if (region > ARRAY_SIZE(region_configs)) + return -EINVAL; + + mutex_lock(&bdev->mutex); + bdev->region_info = region_configs[region]; + mutex_unlock(&bdev->mutex); + + if (bdev->frequency < region_configs[region].bottom_frequency || + bdev->frequency > region_configs[region].top_frequency) + new_frequency = region_configs[region].bottom_frequency; + + if (new_frequency > 0) { + err = bcm2048_set_fm_frequency(bdev, new_frequency); + + if (err) + goto done; + } + + err = bcm2048_set_fm_deemphasis(bdev, + region_configs[region].deemphasis); + +done: + return err; +} + +static int bcm2048_get_region(struct bcm2048_device *bdev) +{ + int err; + + mutex_lock(&bdev->mutex); + err = bdev->region_info.region; + mutex_unlock(&bdev->mutex); + + return err; +} + +static int bcm2048_set_mute(struct bcm2048_device *bdev, u16 mute) +{ + int err; + + mutex_lock(&bdev->mutex); + + bdev->cache_fm_audio_ctrl0 &= ~(BCM2048_RF_MUTE | BCM2048_MANUAL_MUTE); + + if (mute) + bdev->cache_fm_audio_ctrl0 |= (BCM2048_RF_MUTE | + BCM2048_MANUAL_MUTE); + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, + bdev->cache_fm_audio_ctrl0); + + if (!err) + bdev->mute_state = mute; + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_mute(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + if (bdev->power_state) { + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, + &value); + if (!err) + err = value & (BCM2048_RF_MUTE | BCM2048_MANUAL_MUTE); + } else { + err = bdev->mute_state; + } + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_set_audio_route(struct bcm2048_device *bdev, u8 route) +{ + int err; + + mutex_lock(&bdev->mutex); + + route &= (BCM2048_AUDIO_ROUTE_DAC | BCM2048_AUDIO_ROUTE_I2S); + bdev->cache_fm_audio_ctrl0 &= ~(BCM2048_AUDIO_ROUTE_DAC | + BCM2048_AUDIO_ROUTE_I2S); + bdev->cache_fm_audio_ctrl0 |= route; + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, + bdev->cache_fm_audio_ctrl0); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_audio_route(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, &value); + + mutex_unlock(&bdev->mutex); + + if (!err) + return value & (BCM2048_AUDIO_ROUTE_DAC | + BCM2048_AUDIO_ROUTE_I2S); + + return err; +} + +static int bcm2048_set_dac_output(struct bcm2048_device *bdev, u8 channels) +{ + int err; + + mutex_lock(&bdev->mutex); + + bdev->cache_fm_audio_ctrl0 &= ~(BCM2048_DAC_OUTPUT_LEFT | + BCM2048_DAC_OUTPUT_RIGHT); + bdev->cache_fm_audio_ctrl0 |= channels; + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, + bdev->cache_fm_audio_ctrl0); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_dac_output(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, &value); + + mutex_unlock(&bdev->mutex); + + if (!err) + return value & (BCM2048_DAC_OUTPUT_LEFT | + BCM2048_DAC_OUTPUT_RIGHT); + + return err; +} + +static int bcm2048_set_fm_search_rssi_threshold(struct bcm2048_device *bdev, + u8 threshold) +{ + int err; + + mutex_lock(&bdev->mutex); + + threshold &= BCM2048_SEARCH_RSSI_THRESHOLD; + bdev->cache_fm_search_ctrl0 &= ~BCM2048_SEARCH_RSSI_THRESHOLD; + bdev->cache_fm_search_ctrl0 |= threshold; + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0, + bdev->cache_fm_search_ctrl0); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_fm_search_rssi_threshold(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0, &value); + + mutex_unlock(&bdev->mutex); + + if (!err) + return value & BCM2048_SEARCH_RSSI_THRESHOLD; + + return err; +} + +static int bcm2048_set_fm_search_mode_direction(struct bcm2048_device *bdev, + u8 direction) +{ + int err; + + mutex_lock(&bdev->mutex); + + bdev->cache_fm_search_ctrl0 &= ~BCM2048_SEARCH_DIRECTION; + + if (direction) + bdev->cache_fm_search_ctrl0 |= BCM2048_SEARCH_DIRECTION; + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0, + bdev->cache_fm_search_ctrl0); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_fm_search_mode_direction(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0, &value); + + mutex_unlock(&bdev->mutex); + + if (!err && (value & BCM2048_SEARCH_DIRECTION)) + return BCM2048_SEARCH_DIRECTION_UP; + + return err; +} + +static int bcm2048_set_fm_search_tune_mode(struct bcm2048_device *bdev, + u8 mode) +{ + int err, timeout, restart_rds = 0; + u8 value, flags; + + value = mode & BCM2048_FM_AUTO_SEARCH; + + flags = BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED | + BCM2048_FM_FLAG_SEARCH_TUNE_FAIL; + + mutex_lock(&bdev->mutex); + + /* + * If RDS is enabled, and frequency is changed, RDS quits working. + * Thus, always restart RDS if it's enabled. Moreover, RDS must + * not be enabled while changing the frequency because it can + * provide a race to the mutex from the workqueue handler if RDS + * IRQ occurs while waiting for frequency changed IRQ. + */ + if (bcm2048_get_rds_no_lock(bdev)) { + err = bcm2048_set_rds_no_lock(bdev, 0); + if (err) + goto unlock; + restart_rds = 1; + } + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK0, flags); + + if (err) + goto unlock; + + bcm2048_send_command(bdev, BCM2048_I2C_FM_SEARCH_TUNE_MODE, value); + + if (mode != BCM2048_FM_AUTO_SEARCH_MODE) + timeout = BCM2048_DEFAULT_TIMEOUT; + else + timeout = BCM2048_AUTO_SEARCH_TIMEOUT; + + if (!wait_for_completion_timeout(&bdev->compl, + msecs_to_jiffies(timeout))) + dev_err(&bdev->client->dev, "IRQ timeout.\n"); + + if (value) + if (!bdev->scan_state) + err = -EIO; + +unlock: + if (restart_rds) + err |= bcm2048_set_rds_no_lock(bdev, 1); + + mutex_unlock(&bdev->mutex); + + return err; +} + +static int bcm2048_get_fm_search_tune_mode(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_SEARCH_TUNE_MODE, + &value); + + mutex_unlock(&bdev->mutex); + + if (!err) + return value & BCM2048_FM_AUTO_SEARCH; + + return err; +} + +static int bcm2048_set_rds_b_block_mask(struct bcm2048_device *bdev, u16 mask) +{ + int err; + + mutex_lock(&bdev->mutex); + + err = bcm2048_send_command(bdev, + BCM2048_I2C_RDS_BLKB_MASK0, lsb(mask)); + err |= bcm2048_send_command(bdev, + BCM2048_I2C_RDS_BLKB_MASK1, msb(mask)); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_rds_b_block_mask(struct bcm2048_device *bdev) +{ + int err; + u8 lsb, msb; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, + BCM2048_I2C_RDS_BLKB_MASK0, &lsb); + err |= bcm2048_recv_command(bdev, + BCM2048_I2C_RDS_BLKB_MASK1, &msb); + + mutex_unlock(&bdev->mutex); + + if (!err) + return compose_u16(msb, lsb); + + return err; +} + +static int bcm2048_set_rds_b_block_match(struct bcm2048_device *bdev, + u16 match) +{ + int err; + + mutex_lock(&bdev->mutex); + + err = bcm2048_send_command(bdev, + BCM2048_I2C_RDS_BLKB_MATCH0, lsb(match)); + err |= bcm2048_send_command(bdev, + BCM2048_I2C_RDS_BLKB_MATCH1, msb(match)); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_rds_b_block_match(struct bcm2048_device *bdev) +{ + int err; + u8 lsb, msb; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, + BCM2048_I2C_RDS_BLKB_MATCH0, &lsb); + err |= bcm2048_recv_command(bdev, + BCM2048_I2C_RDS_BLKB_MATCH1, &msb); + + mutex_unlock(&bdev->mutex); + + if (!err) + return compose_u16(msb, lsb); + + return err; +} + +static int bcm2048_set_rds_pi_mask(struct bcm2048_device *bdev, u16 mask) +{ + int err; + + mutex_lock(&bdev->mutex); + + err = bcm2048_send_command(bdev, + BCM2048_I2C_RDS_PI_MASK0, lsb(mask)); + err |= bcm2048_send_command(bdev, + BCM2048_I2C_RDS_PI_MASK1, msb(mask)); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_rds_pi_mask(struct bcm2048_device *bdev) +{ + int err; + u8 lsb, msb; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, + BCM2048_I2C_RDS_PI_MASK0, &lsb); + err |= bcm2048_recv_command(bdev, + BCM2048_I2C_RDS_PI_MASK1, &msb); + + mutex_unlock(&bdev->mutex); + + if (!err) + return compose_u16(msb, lsb); + + return err; +} + +static int bcm2048_set_rds_pi_match(struct bcm2048_device *bdev, u16 match) +{ + int err; + + mutex_lock(&bdev->mutex); + + err = bcm2048_send_command(bdev, + BCM2048_I2C_RDS_PI_MATCH0, lsb(match)); + err |= bcm2048_send_command(bdev, + BCM2048_I2C_RDS_PI_MATCH1, msb(match)); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_rds_pi_match(struct bcm2048_device *bdev) +{ + int err; + u8 lsb, msb; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, + BCM2048_I2C_RDS_PI_MATCH0, &lsb); + err |= bcm2048_recv_command(bdev, + BCM2048_I2C_RDS_PI_MATCH1, &msb); + + mutex_unlock(&bdev->mutex); + + if (!err) + return compose_u16(msb, lsb); + + return err; +} + +static int bcm2048_set_fm_rds_mask(struct bcm2048_device *bdev, u16 mask) +{ + int err; + + mutex_lock(&bdev->mutex); + + err = bcm2048_send_command(bdev, + BCM2048_I2C_FM_RDS_MASK0, lsb(mask)); + err |= bcm2048_send_command(bdev, + BCM2048_I2C_FM_RDS_MASK1, msb(mask)); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_fm_rds_mask(struct bcm2048_device *bdev) +{ + int err; + u8 value0, value1; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_MASK0, &value0); + err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_MASK1, &value1); + + mutex_unlock(&bdev->mutex); + + if (!err) + return compose_u16(value1, value0); + + return err; +} + +static int bcm2048_get_fm_rds_flags(struct bcm2048_device *bdev) +{ + int err; + u8 value0, value1; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG0, &value0); + err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG1, &value1); + + mutex_unlock(&bdev->mutex); + + if (!err) + return compose_u16(value1, value0); + + return err; +} + +static int bcm2048_get_region_bottom_frequency(struct bcm2048_device *bdev) +{ + return bdev->region_info.bottom_frequency; +} + +static int bcm2048_get_region_top_frequency(struct bcm2048_device *bdev) +{ + return bdev->region_info.top_frequency; +} + +static int bcm2048_set_fm_best_tune_mode(struct bcm2048_device *bdev, u8 mode) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + /* Perform read as the manual indicates */ + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_BEST_TUNE_MODE, + &value); + value &= ~BCM2048_BEST_TUNE_MODE; + + if (mode) + value |= BCM2048_BEST_TUNE_MODE; + err |= bcm2048_send_command(bdev, BCM2048_I2C_FM_BEST_TUNE_MODE, + value); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_fm_best_tune_mode(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_BEST_TUNE_MODE, + &value); + + mutex_unlock(&bdev->mutex); + + if (!err && (value & BCM2048_BEST_TUNE_MODE)) + return BCM2048_ITEM_ENABLED; + + return err; +} + +static int bcm2048_get_fm_carrier_error(struct bcm2048_device *bdev) +{ + int err = 0; + s8 value; + + mutex_lock(&bdev->mutex); + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_CARRIER, &value); + mutex_unlock(&bdev->mutex); + + if (!err) + return value; + + return err; +} + +static int bcm2048_get_fm_rssi(struct bcm2048_device *bdev) +{ + int err; + s8 value; + + mutex_lock(&bdev->mutex); + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RSSI, &value); + mutex_unlock(&bdev->mutex); + + if (!err) + return value; + + return err; +} + +static int bcm2048_set_rds_wline(struct bcm2048_device *bdev, u8 wline) +{ + int err; + + mutex_lock(&bdev->mutex); + + err = bcm2048_send_command(bdev, BCM2048_I2C_RDS_WLINE, wline); + + if (!err) + bdev->fifo_size = wline; + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_rds_wline(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_RDS_WLINE, &value); + + mutex_unlock(&bdev->mutex); + + if (!err) { + bdev->fifo_size = value; + return value; + } + + return err; +} + +static int bcm2048_checkrev(struct bcm2048_device *bdev) +{ + int err; + u8 version; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_REV, &version); + + mutex_unlock(&bdev->mutex); + + if (!err) { + dev_info(&bdev->client->dev, "BCM2048 Version 0x%x\n", + version); + return version; + } + + return err; +} + +static int bcm2048_get_rds_rt(struct bcm2048_device *bdev, char *data) +{ + int err = 0, i, j = 0, ce = 0, cr = 0; + char data_buffer[BCM2048_MAX_RDS_RT+1]; + + mutex_lock(&bdev->mutex); + + if (!bdev->rds_info.text_len) { + err = -EINVAL; + goto unlock; + } + + for (i = 0; i < BCM2048_MAX_RDS_RT; i++) { + if (bdev->rds_info.rds_rt[i]) { + ce = i; + /* Skip the carriage return */ + if (bdev->rds_info.rds_rt[i] != 0x0d) { + data_buffer[j++] = bdev->rds_info.rds_rt[i]; + } else { + cr = i; + break; + } + } + } + + if (j <= BCM2048_MAX_RDS_RT) + data_buffer[j] = 0; + + for (i = 0; i < BCM2048_MAX_RDS_RT; i++) { + if (!bdev->rds_info.rds_rt[i]) { + if (cr && (i < cr)) { + err = -EBUSY; + goto unlock; + } + if (i < ce) { + if (cr && (i >= cr)) + break; + err = -EBUSY; + goto unlock; + } + } + } + + memcpy(data, data_buffer, sizeof(data_buffer)); + +unlock: + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_rds_ps(struct bcm2048_device *bdev, char *data) +{ + int err = 0, i, j = 0; + char data_buffer[BCM2048_MAX_RDS_PS+1]; + + mutex_lock(&bdev->mutex); + + if (!bdev->rds_info.text_len) { + err = -EINVAL; + goto unlock; + } + + for (i = 0; i < BCM2048_MAX_RDS_PS; i++) { + if (bdev->rds_info.rds_ps[i]) { + data_buffer[j++] = bdev->rds_info.rds_ps[i]; + } else { + if (i < (BCM2048_MAX_RDS_PS - 1)) { + err = -EBUSY; + goto unlock; + } + } + } + + if (j <= BCM2048_MAX_RDS_PS) + data_buffer[j] = 0; + + memcpy(data, data_buffer, sizeof(data_buffer)); + +unlock: + mutex_unlock(&bdev->mutex); + return err; +} + +static void bcm2048_parse_rds_pi(struct bcm2048_device *bdev) +{ + int i, cnt = 0; + u16 pi; + + for (i = 0; i < bdev->fifo_size; i += BCM2048_RDS_FIFO_DUPLE_SIZE) { + + /* Block A match, only data without crc errors taken */ + if (bdev->rds_info.radio_text[i] == BCM2048_RDS_BLOCK_A) { + + pi = ((bdev->rds_info.radio_text[i+1] << 8) + + bdev->rds_info.radio_text[i+2]); + + if (!bdev->rds_info.rds_pi) { + bdev->rds_info.rds_pi = pi; + return; + } + if (pi != bdev->rds_info.rds_pi) { + cnt++; + if (cnt > 3) { + bdev->rds_info.rds_pi = pi; + cnt = 0; + } + } else { + cnt = 0; + } + } + } +} + +static int bcm2048_rds_block_crc(struct bcm2048_device *bdev, int i) +{ + return bdev->rds_info.radio_text[i] & BCM2048_RDS_CRC_MASK; +} + +static void bcm2048_parse_rds_rt_block(struct bcm2048_device *bdev, int i, + int index, int crc) +{ + /* Good data will overwrite poor data */ + if (crc) { + if (!bdev->rds_info.rds_rt[index]) + bdev->rds_info.rds_rt[index] = + bdev->rds_info.radio_text[i+1]; + if (!bdev->rds_info.rds_rt[index+1]) + bdev->rds_info.rds_rt[index+1] = + bdev->rds_info.radio_text[i+2]; + } else { + bdev->rds_info.rds_rt[index] = bdev->rds_info.radio_text[i+1]; + bdev->rds_info.rds_rt[index+1] = + bdev->rds_info.radio_text[i+2]; + } +} + +static int bcm2048_parse_rt_match_b(struct bcm2048_device *bdev, int i) +{ + int crc, rt_id, rt_group_b, rt_ab, index = 0; + + crc = bcm2048_rds_block_crc(bdev, i); + + if (crc == BCM2048_RDS_CRC_UNRECOVARABLE) + return -EIO; + + if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) == + BCM2048_RDS_BLOCK_B) { + + rt_id = (bdev->rds_info.radio_text[i+1] & + BCM2048_RDS_BLOCK_MASK); + rt_group_b = bdev->rds_info.radio_text[i+1] & + BCM2048_RDS_GROUP_AB_MASK; + rt_ab = bdev->rds_info.radio_text[i+2] & + BCM2048_RDS_RT_AB_MASK; + + if (rt_group_b != bdev->rds_info.rds_rt_group_b) { + memset(bdev->rds_info.rds_rt, 0, + sizeof(bdev->rds_info.rds_rt)); + bdev->rds_info.rds_rt_group_b = rt_group_b; + } + + if (rt_id == BCM2048_RDS_RT) { + /* A to B or (vice versa), means: clear screen */ + if (rt_ab != bdev->rds_info.rds_rt_ab) { + memset(bdev->rds_info.rds_rt, 0, + sizeof(bdev->rds_info.rds_rt)); + bdev->rds_info.rds_rt_ab = rt_ab; + } + + index = bdev->rds_info.radio_text[i+2] & + BCM2048_RDS_RT_INDEX; + + if (bdev->rds_info.rds_rt_group_b) + index <<= 1; + else + index <<= 2; + + return index; + } + } + + return -EIO; +} + +static int bcm2048_parse_rt_match_c(struct bcm2048_device *bdev, int i, + int index) +{ + int crc; + + crc = bcm2048_rds_block_crc(bdev, i); + + if (crc == BCM2048_RDS_CRC_UNRECOVARABLE) + return 0; + + BUG_ON((index+2) >= BCM2048_MAX_RDS_RT); + + if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) == + BCM2048_RDS_BLOCK_C) { + if (bdev->rds_info.rds_rt_group_b) + return 1; + bcm2048_parse_rds_rt_block(bdev, i, index, crc); + return 1; + } + + return 0; +} + +static void bcm2048_parse_rt_match_d(struct bcm2048_device *bdev, int i, + int index) +{ + int crc; + + crc = bcm2048_rds_block_crc(bdev, i); + + if (crc == BCM2048_RDS_CRC_UNRECOVARABLE) + return; + + BUG_ON((index+4) >= BCM2048_MAX_RDS_RT); + + if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) == + BCM2048_RDS_BLOCK_D) + bcm2048_parse_rds_rt_block(bdev, i, index+2, crc); +} + +static int bcm2048_parse_rds_rt(struct bcm2048_device *bdev) +{ + int i, index = 0, crc, match_b = 0, match_c = 0, match_d = 0; + + for (i = 0; i < bdev->fifo_size; i += BCM2048_RDS_FIFO_DUPLE_SIZE) { + + if (match_b) { + match_b = 0; + index = bcm2048_parse_rt_match_b(bdev, i); + if (index >= 0 && index <= (BCM2048_MAX_RDS_RT - 5)) + match_c = 1; + continue; + } else if (match_c) { + match_c = 0; + if (bcm2048_parse_rt_match_c(bdev, i, index)) + match_d = 1; + continue; + } else if (match_d) { + match_d = 0; + bcm2048_parse_rt_match_d(bdev, i, index); + continue; + } + + /* Skip erroneous blocks due to messed up A block altogether */ + if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) + == BCM2048_RDS_BLOCK_A) { + crc = bcm2048_rds_block_crc(bdev, i); + if (crc == BCM2048_RDS_CRC_UNRECOVARABLE) + continue; + /* Syncronize to a good RDS PI */ + if (((bdev->rds_info.radio_text[i+1] << 8) + + bdev->rds_info.radio_text[i+2]) == + bdev->rds_info.rds_pi) + match_b = 1; + } + } + + return 0; +} + +static void bcm2048_parse_rds_ps_block(struct bcm2048_device *bdev, int i, + int index, int crc) +{ + /* Good data will overwrite poor data */ + if (crc) { + if (!bdev->rds_info.rds_ps[index]) + bdev->rds_info.rds_ps[index] = + bdev->rds_info.radio_text[i+1]; + if (!bdev->rds_info.rds_ps[index+1]) + bdev->rds_info.rds_ps[index+1] = + bdev->rds_info.radio_text[i+2]; + } else { + bdev->rds_info.rds_ps[index] = bdev->rds_info.radio_text[i+1]; + bdev->rds_info.rds_ps[index+1] = + bdev->rds_info.radio_text[i+2]; + } +} + +static int bcm2048_parse_ps_match_c(struct bcm2048_device *bdev, int i, + int index) +{ + int crc; + + crc = bcm2048_rds_block_crc(bdev, i); + + if (crc == BCM2048_RDS_CRC_UNRECOVARABLE) + return 0; + + if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) == + BCM2048_RDS_BLOCK_C) + return 1; + + return 0; +} + +static void bcm2048_parse_ps_match_d(struct bcm2048_device *bdev, int i, + int index) +{ + int crc; + + crc = bcm2048_rds_block_crc(bdev, i); + + if (crc == BCM2048_RDS_CRC_UNRECOVARABLE) + return; + + if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) == + BCM2048_RDS_BLOCK_D) + bcm2048_parse_rds_ps_block(bdev, i, index, crc); +} + +static int bcm2048_parse_ps_match_b(struct bcm2048_device *bdev, int i) +{ + int crc, index, ps_id, ps_group; + + crc = bcm2048_rds_block_crc(bdev, i); + + if (crc == BCM2048_RDS_CRC_UNRECOVARABLE) + return -EIO; + + /* Block B Radio PS match */ + if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) == + BCM2048_RDS_BLOCK_B) { + ps_id = bdev->rds_info.radio_text[i+1] & + BCM2048_RDS_BLOCK_MASK; + ps_group = bdev->rds_info.radio_text[i+1] & + BCM2048_RDS_GROUP_AB_MASK; + + /* + * Poor RSSI will lead to RDS data corruption + * So using 3 (same) sequential values to justify major changes + */ + if (ps_group != bdev->rds_info.rds_ps_group) { + if (crc == BCM2048_RDS_CRC_NONE) { + bdev->rds_info.rds_ps_group_cnt++; + if (bdev->rds_info.rds_ps_group_cnt > 2) { + bdev->rds_info.rds_ps_group = ps_group; + bdev->rds_info.rds_ps_group_cnt = 0; + dev_err(&bdev->client->dev, + "RDS PS Group change!\n"); + } else { + return -EIO; + } + } else { + bdev->rds_info.rds_ps_group_cnt = 0; + } + } + + if (ps_id == BCM2048_RDS_PS) { + index = bdev->rds_info.radio_text[i+2] & + BCM2048_RDS_PS_INDEX; + index <<= 1; + return index; + } + } + + return -EIO; +} + +static void bcm2048_parse_rds_ps(struct bcm2048_device *bdev) +{ + int i, index = 0, crc, match_b = 0, match_c = 0, match_d = 0; + + for (i = 0; i < bdev->fifo_size; i += BCM2048_RDS_FIFO_DUPLE_SIZE) { + + if (match_b) { + match_b = 0; + index = bcm2048_parse_ps_match_b(bdev, i); + if (index >= 0 && index < (BCM2048_MAX_RDS_PS - 1)) + match_c = 1; + continue; + } else if (match_c) { + match_c = 0; + if (bcm2048_parse_ps_match_c(bdev, i, index)) + match_d = 1; + continue; + } else if (match_d) { + match_d = 0; + bcm2048_parse_ps_match_d(bdev, i, index); + continue; + } + + /* Skip erroneous blocks due to messed up A block altogether */ + if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) + == BCM2048_RDS_BLOCK_A) { + crc = bcm2048_rds_block_crc(bdev, i); + if (crc == BCM2048_RDS_CRC_UNRECOVARABLE) + continue; + /* Syncronize to a good RDS PI */ + if (((bdev->rds_info.radio_text[i+1] << 8) + + bdev->rds_info.radio_text[i+2]) == + bdev->rds_info.rds_pi) + match_b = 1; + } + } +} + +static void bcm2048_rds_fifo_receive(struct bcm2048_device *bdev) +{ + int err; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_duples(bdev, BCM2048_I2C_RDS_DATA, + bdev->rds_info.radio_text, bdev->fifo_size); + if (err != 2) { + dev_err(&bdev->client->dev, "RDS Read problem\n"); + mutex_unlock(&bdev->mutex); + return; + } + + bdev->rds_info.text_len = bdev->fifo_size; + + bcm2048_parse_rds_pi(bdev); + bcm2048_parse_rds_rt(bdev); + bcm2048_parse_rds_ps(bdev); + + mutex_unlock(&bdev->mutex); + + wake_up_interruptible(&bdev->read_queue); +} + +static int bcm2048_get_rds_data(struct bcm2048_device *bdev, char *data) +{ + int err = 0, i, p = 0; + char *data_buffer; + + mutex_lock(&bdev->mutex); + + if (!bdev->rds_info.text_len) { + err = -EINVAL; + goto unlock; + } + + data_buffer = kzalloc(BCM2048_MAX_RDS_RADIO_TEXT*5, GFP_KERNEL); + if (!data_buffer) { + err = -ENOMEM; + goto unlock; + } + + for (i = 0; i < bdev->rds_info.text_len; i++) { + p += sprintf(data_buffer+p, "%x ", + bdev->rds_info.radio_text[i]); + } + + memcpy(data, data_buffer, p); + kfree(data_buffer); + +unlock: + mutex_unlock(&bdev->mutex); + return err; +} + +/* + * BCM2048 default initialization sequence + */ +static int bcm2048_init(struct bcm2048_device *bdev) +{ + int err; + + err = bcm2048_set_power_state(bdev, BCM2048_POWER_ON); + if (err < 0) + goto exit; + + err = bcm2048_set_audio_route(bdev, BCM2048_AUDIO_ROUTE_DAC); + if (err < 0) + goto exit; + + err = bcm2048_set_dac_output(bdev, BCM2048_DAC_OUTPUT_LEFT | + BCM2048_DAC_OUTPUT_RIGHT); + +exit: + return err; +} + +/* + * BCM2048 default deinitialization sequence + */ +static int bcm2048_deinit(struct bcm2048_device *bdev) +{ + int err; + + err = bcm2048_set_audio_route(bdev, 0); + if (err < 0) + goto exit; + + err = bcm2048_set_dac_output(bdev, 0); + if (err < 0) + goto exit; + + err = bcm2048_set_power_state(bdev, BCM2048_POWER_OFF); + if (err < 0) + goto exit; + +exit: + return err; +} + +/* + * BCM2048 probe sequence + */ +static int bcm2048_probe(struct bcm2048_device *bdev) +{ + int err; + + err = bcm2048_set_power_state(bdev, BCM2048_POWER_ON); + if (err < 0) + goto unlock; + + err = bcm2048_checkrev(bdev); + if (err < 0) + goto unlock; + + err = bcm2048_set_mute(bdev, BCM2048_DEFAULT_MUTE); + if (err < 0) + goto unlock; + + err = bcm2048_set_region(bdev, BCM2048_DEFAULT_REGION); + if (err < 0) + goto unlock; + + err = bcm2048_set_fm_search_rssi_threshold(bdev, + BCM2048_DEFAULT_RSSI_THRESHOLD); + if (err < 0) + goto unlock; + + err = bcm2048_set_fm_automatic_stereo_mono(bdev, BCM2048_ITEM_ENABLED); + if (err < 0) + goto unlock; + + err = bcm2048_get_rds_wline(bdev); + if (err < BCM2048_DEFAULT_RDS_WLINE) + err = bcm2048_set_rds_wline(bdev, BCM2048_DEFAULT_RDS_WLINE); + if (err < 0) + goto unlock; + + err = bcm2048_set_power_state(bdev, BCM2048_POWER_OFF); + + init_waitqueue_head(&bdev->read_queue); + bdev->rds_data_available = 0; + bdev->rd_index = 0; + bdev->users = 0; + +unlock: + return err; +} + +/* + * BCM2048 workqueue handler + */ +static void bcm2048_work(struct work_struct *work) +{ + struct bcm2048_device *bdev; + u8 flag_lsb, flag_msb, flags; + + bdev = container_of(work, struct bcm2048_device, work); + bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG0, &flag_lsb); + bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG1, &flag_msb); + + if (flag_lsb & (BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED | + BCM2048_FM_FLAG_SEARCH_TUNE_FAIL)) { + + if (flag_lsb & BCM2048_FM_FLAG_SEARCH_TUNE_FAIL) + bdev->scan_state = BCM2048_SCAN_FAIL; + else + bdev->scan_state = BCM2048_SCAN_OK; + + complete(&bdev->compl); + } + + if (flag_msb & BCM2048_RDS_FLAG_FIFO_WLINE) { + bcm2048_rds_fifo_receive(bdev); + if (bdev->rds_state) { + flags = BCM2048_RDS_FLAG_FIFO_WLINE; + bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK1, + flags); + } + bdev->rds_data_available = 1; + bdev->rd_index = 0; /* new data, new start */ + } +} + +/* + * BCM2048 interrupt handler + */ +static irqreturn_t bcm2048_handler(int irq, void *dev) +{ + struct bcm2048_device *bdev = dev; + + dev_dbg(&bdev->client->dev, "IRQ called, queuing work\n"); + if (bdev->power_state) + schedule_work(&bdev->work); + + return IRQ_HANDLED; +} + +/* + * BCM2048 sysfs interface definitions + */ +#define property_write(prop, type, mask, check) \ +static ssize_t bcm2048_##prop##_write(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, \ + size_t count) \ +{ \ + struct bcm2048_device *bdev = dev_get_drvdata(dev); \ + type value; \ + int err; \ + \ + if (!bdev) \ + return -ENODEV; \ + \ + sscanf(buf, mask, &value); \ + \ + if (check) \ + return -EDOM; \ + \ + err = bcm2048_set_##prop(bdev, value); \ + \ + return err < 0 ? err : count; \ +} + +#define property_read(prop, size, mask) \ +static ssize_t bcm2048_##prop##_read(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct bcm2048_device *bdev = dev_get_drvdata(dev); \ + int value; \ + \ + if (!bdev) \ + return -ENODEV; \ + \ + value = bcm2048_get_##prop(bdev); \ + \ + if (value >= 0) \ + value = sprintf(buf, mask "\n", value); \ + \ + return value; \ +} + +#define property_signed_read(prop, size, mask) \ +static ssize_t bcm2048_##prop##_read(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct bcm2048_device *bdev = dev_get_drvdata(dev); \ + size value; \ + \ + if (!bdev) \ + return -ENODEV; \ + \ + value = bcm2048_get_##prop(bdev); \ + \ + value = sprintf(buf, mask "\n", value); \ + \ + return value; \ +} + +#define DEFINE_SYSFS_PROPERTY(prop, signal, size, mask, check) \ +property_write(prop, signal size, mask, check) \ +property_read(prop, size, mask) + +#define property_str_read(prop, size) \ +static ssize_t bcm2048_##prop##_read(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct bcm2048_device *bdev = dev_get_drvdata(dev); \ + int count; \ + u8 *out; \ + \ + if (!bdev) \ + return -ENODEV; \ + \ + out = kzalloc(size + 1, GFP_KERNEL); \ + if (!out) \ + return -ENOMEM; \ + \ + bcm2048_get_##prop(bdev, out); \ + count = sprintf(buf, "%s\n", out); \ + \ + kfree(out); \ + \ + return count; \ +} + +DEFINE_SYSFS_PROPERTY(power_state, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(mute, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(audio_route, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(dac_output, unsigned, int, "%u", 0) + +DEFINE_SYSFS_PROPERTY(fm_hi_lo_injection, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(fm_frequency, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(fm_af_frequency, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(fm_deemphasis, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(fm_rds_mask, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(fm_best_tune_mode, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(fm_search_rssi_threshold, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(fm_search_mode_direction, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(fm_search_tune_mode, unsigned, int, "%u", value > 3) + +DEFINE_SYSFS_PROPERTY(rds, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(rds_b_block_mask, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(rds_b_block_match, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(rds_pi_mask, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(rds_pi_match, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(rds_wline, unsigned, int, "%u", 0) +property_read(rds_pi, unsigned int, "%x") +property_str_read(rds_rt, (BCM2048_MAX_RDS_RT + 1)) +property_str_read(rds_ps, (BCM2048_MAX_RDS_PS + 1)) + +property_read(fm_rds_flags, unsigned int, "%u") +property_str_read(rds_data, BCM2048_MAX_RDS_RADIO_TEXT*5) + +property_read(region_bottom_frequency, unsigned int, "%u") +property_read(region_top_frequency, unsigned int, "%u") +property_signed_read(fm_carrier_error, int, "%d") +property_signed_read(fm_rssi, int, "%d") +DEFINE_SYSFS_PROPERTY(region, unsigned, int, "%u", 0) + +static struct device_attribute attrs[] = { + __ATTR(power_state, S_IRUGO | S_IWUSR, bcm2048_power_state_read, + bcm2048_power_state_write), + __ATTR(mute, S_IRUGO | S_IWUSR, bcm2048_mute_read, + bcm2048_mute_write), + __ATTR(audio_route, S_IRUGO | S_IWUSR, bcm2048_audio_route_read, + bcm2048_audio_route_write), + __ATTR(dac_output, S_IRUGO | S_IWUSR, bcm2048_dac_output_read, + bcm2048_dac_output_write), + __ATTR(fm_hi_lo_injection, S_IRUGO | S_IWUSR, + bcm2048_fm_hi_lo_injection_read, + bcm2048_fm_hi_lo_injection_write), + __ATTR(fm_frequency, S_IRUGO | S_IWUSR, bcm2048_fm_frequency_read, + bcm2048_fm_frequency_write), + __ATTR(fm_af_frequency, S_IRUGO | S_IWUSR, + bcm2048_fm_af_frequency_read, + bcm2048_fm_af_frequency_write), + __ATTR(fm_deemphasis, S_IRUGO | S_IWUSR, bcm2048_fm_deemphasis_read, + bcm2048_fm_deemphasis_write), + __ATTR(fm_rds_mask, S_IRUGO | S_IWUSR, bcm2048_fm_rds_mask_read, + bcm2048_fm_rds_mask_write), + __ATTR(fm_best_tune_mode, S_IRUGO | S_IWUSR, + bcm2048_fm_best_tune_mode_read, + bcm2048_fm_best_tune_mode_write), + __ATTR(fm_search_rssi_threshold, S_IRUGO | S_IWUSR, + bcm2048_fm_search_rssi_threshold_read, + bcm2048_fm_search_rssi_threshold_write), + __ATTR(fm_search_mode_direction, S_IRUGO | S_IWUSR, + bcm2048_fm_search_mode_direction_read, + bcm2048_fm_search_mode_direction_write), + __ATTR(fm_search_tune_mode, S_IRUGO | S_IWUSR, + bcm2048_fm_search_tune_mode_read, + bcm2048_fm_search_tune_mode_write), + __ATTR(rds, S_IRUGO | S_IWUSR, bcm2048_rds_read, + bcm2048_rds_write), + __ATTR(rds_b_block_mask, S_IRUGO | S_IWUSR, + bcm2048_rds_b_block_mask_read, + bcm2048_rds_b_block_mask_write), + __ATTR(rds_b_block_match, S_IRUGO | S_IWUSR, + bcm2048_rds_b_block_match_read, + bcm2048_rds_b_block_match_write), + __ATTR(rds_pi_mask, S_IRUGO | S_IWUSR, bcm2048_rds_pi_mask_read, + bcm2048_rds_pi_mask_write), + __ATTR(rds_pi_match, S_IRUGO | S_IWUSR, bcm2048_rds_pi_match_read, + bcm2048_rds_pi_match_write), + __ATTR(rds_wline, S_IRUGO | S_IWUSR, bcm2048_rds_wline_read, + bcm2048_rds_wline_write), + __ATTR(rds_pi, S_IRUGO, bcm2048_rds_pi_read, NULL), + __ATTR(rds_rt, S_IRUGO, bcm2048_rds_rt_read, NULL), + __ATTR(rds_ps, S_IRUGO, bcm2048_rds_ps_read, NULL), + __ATTR(fm_rds_flags, S_IRUGO, bcm2048_fm_rds_flags_read, NULL), + __ATTR(region_bottom_frequency, S_IRUGO, + bcm2048_region_bottom_frequency_read, NULL), + __ATTR(region_top_frequency, S_IRUGO, + bcm2048_region_top_frequency_read, NULL), + __ATTR(fm_carrier_error, S_IRUGO, + bcm2048_fm_carrier_error_read, NULL), + __ATTR(fm_rssi, S_IRUGO, + bcm2048_fm_rssi_read, NULL), + __ATTR(region, S_IRUGO | S_IWUSR, bcm2048_region_read, + bcm2048_region_write), + __ATTR(rds_data, S_IRUGO, bcm2048_rds_data_read, NULL), +}; + +static int bcm2048_sysfs_unregister_properties(struct bcm2048_device *bdev, + int size) +{ + int i; + + for (i = 0; i < size; i++) + device_remove_file(&bdev->client->dev, &attrs[i]); + + return 0; +} + +static int bcm2048_sysfs_register_properties(struct bcm2048_device *bdev) +{ + int err = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(attrs); i++) { + if (device_create_file(&bdev->client->dev, &attrs[i]) != 0) { + dev_err(&bdev->client->dev, + "could not register sysfs entry\n"); + err = -EBUSY; + bcm2048_sysfs_unregister_properties(bdev, i); + break; + } + } + + return err; +} + + +static int bcm2048_fops_open(struct file *file) +{ + struct bcm2048_device *bdev = video_drvdata(file); + + bdev->users++; + bdev->rd_index = 0; + bdev->rds_data_available = 0; + + return 0; +} + +static int bcm2048_fops_release(struct file *file) +{ + struct bcm2048_device *bdev = video_drvdata(file); + + bdev->users--; + + return 0; +} + +static unsigned int bcm2048_fops_poll(struct file *file, + struct poll_table_struct *pts) +{ + struct bcm2048_device *bdev = video_drvdata(file); + int retval = 0; + + poll_wait(file, &bdev->read_queue, pts); + + if (bdev->rds_data_available) + retval = POLLIN | POLLRDNORM; + + return retval; +} + +static ssize_t bcm2048_fops_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct bcm2048_device *bdev = video_drvdata(file); + int i; + int retval = 0; + + /* we return at least 3 bytes, one block */ + count = (count / 3) * 3; /* only multiples of 3 */ + if (count < 3) + return -ENOBUFS; + + while (!bdev->rds_data_available) { + if (file->f_flags & O_NONBLOCK) { + retval = -EWOULDBLOCK; + goto done; + } + /* interruptible_sleep_on(&bdev->read_queue); */ + if (wait_event_interruptible(bdev->read_queue, + bdev->rds_data_available) < 0) { + retval = -EINTR; + goto done; + } + } + + mutex_lock(&bdev->mutex); + /* copy data to userspace */ + i = bdev->fifo_size - bdev->rd_index; + if (count > i) + count = (i / 3) * 3; + + i = 0; + while (i < count) { + unsigned char tmpbuf[3]; + tmpbuf[i] = bdev->rds_info.radio_text[bdev->rd_index+i+2]; + tmpbuf[i+1] = bdev->rds_info.radio_text[bdev->rd_index+i+1]; + tmpbuf[i+2] = ((bdev->rds_info.radio_text[bdev->rd_index+i] + & 0xf0) >> 4); + if ((bdev->rds_info.radio_text[bdev->rd_index+i] & + BCM2048_RDS_CRC_MASK) == BCM2048_RDS_CRC_UNRECOVARABLE) + tmpbuf[i+2] |= 0x80; + if (copy_to_user(buf+i, tmpbuf, 3)) { + retval = -EFAULT; + break; + } + i += 3; + } + + bdev->rd_index += i; + if (bdev->rd_index >= bdev->fifo_size) + bdev->rds_data_available = 0; + + mutex_unlock(&bdev->mutex); + if (retval == 0) + retval = i; + +done: + return retval; +} + +/* + * bcm2048_fops - file operations interface + */ +static const struct v4l2_file_operations bcm2048_fops = { + .owner = THIS_MODULE, + .ioctl = video_ioctl2, + /* for RDS read support */ + .open = bcm2048_fops_open, + .release = bcm2048_fops_release, + .read = bcm2048_fops_read, + .poll = bcm2048_fops_poll +}; + +/* + * Video4Linux Interface + */ +static struct v4l2_queryctrl bcm2048_v4l2_queryctrl[] = { + { + .id = V4L2_CID_AUDIO_VOLUME, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_BALANCE, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_BASS, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_TREBLE, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_MUTE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_AUDIO_LOUDNESS, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, +}; + +static int bcm2048_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *capability) +{ + struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file)); + + strlcpy(capability->driver, BCM2048_DRIVER_NAME, + sizeof(capability->driver)); + strlcpy(capability->card, BCM2048_DRIVER_CARD, + sizeof(capability->card)); + snprintf(capability->bus_info, 32, "I2C: 0x%X", bdev->client->addr); + capability->version = BCM2048_DRIVER_VERSION; + capability->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO | + V4L2_CAP_HW_FREQ_SEEK; + + return 0; +} + +static int bcm2048_vidioc_g_input(struct file *filp, void *priv, + unsigned int *i) +{ + *i = 0; + + return 0; +} + +static int bcm2048_vidioc_s_input(struct file *filp, void *priv, + unsigned int i) +{ + if (i) + return -EINVAL; + + return 0; +} + +static int bcm2048_vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bcm2048_v4l2_queryctrl); i++) { + if (qc->id && qc->id == bcm2048_v4l2_queryctrl[i].id) { + *qc = bcm2048_v4l2_queryctrl[i]; + return 0; + } + } + + return -EINVAL; +} + +static int bcm2048_vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file)); + int err = 0; + + if (!bdev) + return -ENODEV; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + err = bcm2048_get_mute(bdev); + if (err >= 0) + ctrl->value = err; + break; + } + + return err; +} + +static int bcm2048_vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file)); + int err = 0; + + if (!bdev) + return -ENODEV; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + if (ctrl->value) { + if (bdev->power_state) { + err = bcm2048_set_mute(bdev, ctrl->value); + err |= bcm2048_deinit(bdev); + } + } else { + if (!bdev->power_state) { + err = bcm2048_init(bdev); + err |= bcm2048_set_mute(bdev, ctrl->value); + } + } + break; + } + + return err; +} + +static int bcm2048_vidioc_g_audio(struct file *file, void *priv, + struct v4l2_audio *audio) +{ + if (audio->index > 1) + return -EINVAL; + + strncpy(audio->name, "Radio", 32); + audio->capability = V4L2_AUDCAP_STEREO; + + return 0; +} + +static int bcm2048_vidioc_s_audio(struct file *file, void *priv, + const struct v4l2_audio *audio) +{ + if (audio->index != 0) + return -EINVAL; + + return 0; +} + +static int bcm2048_vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file)); + s8 f_error; + s8 rssi; + + if (!bdev) + return -ENODEV; + + if (tuner->index > 0) + return -EINVAL; + + strncpy(tuner->name, "FM Receiver", 32); + tuner->type = V4L2_TUNER_RADIO; + tuner->rangelow = + dev_to_v4l2(bcm2048_get_region_bottom_frequency(bdev)); + tuner->rangehigh = + dev_to_v4l2(bcm2048_get_region_top_frequency(bdev)); + tuner->rxsubchans = V4L2_TUNER_SUB_STEREO; + tuner->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LOW; + tuner->audmode = V4L2_TUNER_MODE_STEREO; + tuner->afc = 0; + if (bdev->power_state) { + /* + * Report frequencies with high carrier errors to have zero + * signal level + */ + f_error = bcm2048_get_fm_carrier_error(bdev); + if (f_error < BCM2048_FREQ_ERROR_FLOOR || + f_error > BCM2048_FREQ_ERROR_ROOF) { + tuner->signal = 0; + } else { + /* + * RSSI level -60 dB is defined to report full + * signal strenght + */ + rssi = bcm2048_get_fm_rssi(bdev); + if (rssi >= BCM2048_RSSI_LEVEL_BASE) { + tuner->signal = 0xFFFF; + } else if (rssi > BCM2048_RSSI_LEVEL_ROOF) { + tuner->signal = (rssi + + BCM2048_RSSI_LEVEL_ROOF_NEG) + * BCM2048_SIGNAL_MULTIPLIER; + } else { + tuner->signal = 0; + } + } + } else { + tuner->signal = 0; + } + + return 0; +} + +static int bcm2048_vidioc_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *tuner) +{ + struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file)); + + if (!bdev) + return -ENODEV; + + if (tuner->index > 0) + return -EINVAL; + + return 0; +} + +static int bcm2048_vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *freq) +{ + struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file)); + int err = 0; + int f; + + if (!bdev->power_state) + return -ENODEV; + + freq->type = V4L2_TUNER_RADIO; + f = bcm2048_get_fm_frequency(bdev); + + if (f < 0) + err = f; + else + freq->frequency = dev_to_v4l2(f); + + return err; +} + +static int bcm2048_vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *freq) +{ + struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file)); + int err; + + if (freq->type != V4L2_TUNER_RADIO) + return -EINVAL; + + if (!bdev->power_state) + return -ENODEV; + + err = bcm2048_set_fm_frequency(bdev, v4l2_to_dev(freq->frequency)); + err |= bcm2048_set_fm_search_tune_mode(bdev, BCM2048_FM_PRE_SET_MODE); + + return err; +} + +static int bcm2048_vidioc_s_hw_freq_seek(struct file *file, void *priv, + const struct v4l2_hw_freq_seek *seek) +{ + struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file)); + int err; + + if (!bdev->power_state) + return -ENODEV; + + if ((seek->tuner != 0) || (seek->type != V4L2_TUNER_RADIO)) + return -EINVAL; + + err = bcm2048_set_fm_search_mode_direction(bdev, seek->seek_upward); + err |= bcm2048_set_fm_search_tune_mode(bdev, + BCM2048_FM_AUTO_SEARCH_MODE); + + return err; +} + +static struct v4l2_ioctl_ops bcm2048_ioctl_ops = { + .vidioc_querycap = bcm2048_vidioc_querycap, + .vidioc_g_input = bcm2048_vidioc_g_input, + .vidioc_s_input = bcm2048_vidioc_s_input, + .vidioc_queryctrl = bcm2048_vidioc_queryctrl, + .vidioc_g_ctrl = bcm2048_vidioc_g_ctrl, + .vidioc_s_ctrl = bcm2048_vidioc_s_ctrl, + .vidioc_g_audio = bcm2048_vidioc_g_audio, + .vidioc_s_audio = bcm2048_vidioc_s_audio, + .vidioc_g_tuner = bcm2048_vidioc_g_tuner, + .vidioc_s_tuner = bcm2048_vidioc_s_tuner, + .vidioc_g_frequency = bcm2048_vidioc_g_frequency, + .vidioc_s_frequency = bcm2048_vidioc_s_frequency, + .vidioc_s_hw_freq_seek = bcm2048_vidioc_s_hw_freq_seek, +}; + +/* + * bcm2048_viddev_template - video device interface + */ +static struct video_device bcm2048_viddev_template = { + .fops = &bcm2048_fops, + .name = BCM2048_DRIVER_NAME, + .release = video_device_release, + .ioctl_ops = &bcm2048_ioctl_ops, +}; + +/* + * I2C driver interface + */ +static int bcm2048_i2c_driver_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bcm2048_device *bdev; + int err, skip_release = 0; + + bdev = kzalloc(sizeof(*bdev), GFP_KERNEL); + if (!bdev) { + dev_dbg(&client->dev, "Failed to alloc video device.\n"); + err = -ENOMEM; + goto exit; + } + + bdev->videodev = video_device_alloc(); + if (!bdev->videodev) { + dev_dbg(&client->dev, "Failed to alloc video device.\n"); + err = -ENOMEM; + goto free_bdev; + } + + bdev->client = client; + i2c_set_clientdata(client, bdev); + mutex_init(&bdev->mutex); + init_completion(&bdev->compl); + INIT_WORK(&bdev->work, bcm2048_work); + + if (client->irq) { + err = request_irq(client->irq, + bcm2048_handler, IRQF_TRIGGER_FALLING | IRQF_DISABLED, + client->name, bdev); + if (err < 0) { + dev_err(&client->dev, "Could not request IRQ\n"); + goto free_vdev; + } + dev_dbg(&client->dev, "IRQ requested.\n"); + } else { + dev_dbg(&client->dev, "IRQ not configured. Using timeouts.\n"); + } + + *bdev->videodev = bcm2048_viddev_template; + video_set_drvdata(bdev->videodev, bdev); + if (video_register_device(bdev->videodev, VFL_TYPE_RADIO, radio_nr)) { + dev_dbg(&client->dev, "Could not register video device.\n"); + err = -EIO; + goto free_irq; + } + + err = bcm2048_sysfs_register_properties(bdev); + if (err < 0) { + dev_dbg(&client->dev, "Could not register sysfs interface.\n"); + goto free_registration; + } + + err = bcm2048_probe(bdev); + if (err < 0) { + dev_dbg(&client->dev, "Failed to probe device information.\n"); + goto free_sysfs; + } + + return 0; + +free_sysfs: + bcm2048_sysfs_unregister_properties(bdev, ARRAY_SIZE(attrs)); +free_registration: + video_unregister_device(bdev->videodev); + /* video_unregister_device frees bdev->videodev */ + bdev->videodev = NULL; + skip_release = 1; +free_irq: + if (client->irq) + free_irq(client->irq, bdev); +free_vdev: + if (!skip_release) + video_device_release(bdev->videodev); + i2c_set_clientdata(client, NULL); +free_bdev: + kfree(bdev); +exit: + return err; +} + +static int __exit bcm2048_i2c_driver_remove(struct i2c_client *client) +{ + struct bcm2048_device *bdev = i2c_get_clientdata(client); + struct video_device *vd; + + if (!client->adapter) + return -ENODEV; + + if (bdev) { + vd = bdev->videodev; + + bcm2048_sysfs_unregister_properties(bdev, ARRAY_SIZE(attrs)); + + if (vd) + video_unregister_device(vd); + + if (bdev->power_state) + bcm2048_set_power_state(bdev, BCM2048_POWER_OFF); + + if (client->irq > 0) + free_irq(client->irq, bdev); + + cancel_work_sync(&bdev->work); + + kfree(bdev); + } + + i2c_set_clientdata(client, NULL); + + return 0; +} + +/* + * bcm2048_i2c_driver - i2c driver interface + */ +static const struct i2c_device_id bcm2048_id[] = { + { "bcm2048" , 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, bcm2048_id); + +static struct i2c_driver bcm2048_i2c_driver = { + .driver = { + .name = BCM2048_DRIVER_NAME, + }, + .probe = bcm2048_i2c_driver_probe, + .remove = __exit_p(bcm2048_i2c_driver_remove), + .id_table = bcm2048_id, +}; + +/* + * Module Interface + */ +static int __init bcm2048_module_init(void) +{ + pr_info(BCM2048_DRIVER_DESC "\n"); + + return i2c_add_driver(&bcm2048_i2c_driver); +} +module_init(bcm2048_module_init); + +static void __exit bcm2048_module_exit(void) +{ + i2c_del_driver(&bcm2048_i2c_driver); +} +module_exit(bcm2048_module_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR(BCM2048_DRIVER_AUTHOR); +MODULE_DESCRIPTION(BCM2048_DRIVER_DESC); +MODULE_VERSION("0.0.2"); diff --git a/drivers/staging/media/bcm2048/radio-bcm2048.h b/drivers/staging/media/bcm2048/radio-bcm2048.h new file mode 100644 index 000000000000..4c90a32db795 --- /dev/null +++ b/drivers/staging/media/bcm2048/radio-bcm2048.h @@ -0,0 +1,30 @@ +/* + * drivers/staging/media/radio-bcm2048.h + * + * Property and command definitions for bcm2048 radio receiver chip. + * + * Copyright (C) Nokia Corporation + * Contact: Eero Nurkkala <ext-eero.nurkkala@nokia.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. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef BCM2048_H +#define BCM2048_H + +#define BCM2048_NAME "bcm2048" +#define BCM2048_I2C_ADDR 0x22 + +#endif /* ifndef BCM2048_H */ diff --git a/drivers/staging/media/davinci_vpfe/dm365_isif.c b/drivers/staging/media/davinci_vpfe/dm365_isif.c index ff48fce94fcb..b942bf73c43f 100644 --- a/drivers/staging/media/davinci_vpfe/dm365_isif.c +++ b/drivers/staging/media/davinci_vpfe/dm365_isif.c @@ -19,6 +19,7 @@ * Prabhakar Lad <prabhakar.lad@ti.com> */ +#include <linux/delay.h> #include "dm365_isif.h" #include "vpfe_mc_capture.h" @@ -918,7 +919,7 @@ isif_config_dfc(struct vpfe_isif_device *isif, struct vpfe_isif_dfc *vdfc) (0 << ISIF_VDFC_EN_SHIFT), DFCCTL); isif_write(isif->isif_cfg.base_addr, 0x6, DFCMEMCTL); - for (i = 0 ; i < vdfc->num_vdefects; i++) { + for (i = 0; i < vdfc->num_vdefects; i++) { count = DFC_WRITE_WAIT_COUNT; while (count && (isif_read(isif->isif_cfg.base_addr, DFCMEMCTL) & 0x2)) diff --git a/drivers/staging/media/davinci_vpfe/vpfe_video.c b/drivers/staging/media/davinci_vpfe/vpfe_video.c index 24d98a6866bb..1f3b0f9a8d10 100644 --- a/drivers/staging/media/davinci_vpfe/vpfe_video.c +++ b/drivers/staging/media/davinci_vpfe/vpfe_video.c @@ -346,7 +346,7 @@ static int vpfe_pipeline_disable(struct vpfe_pipeline *pipe) } mutex_unlock(&mdev->graph_mutex); - return (ret == 0) ? ret : -ETIMEDOUT ; + return ret ? -ETIMEDOUT : 0; } /* @@ -1201,6 +1201,8 @@ static int vpfe_start_streaming(struct vb2_queue *vq, unsigned int count) unsigned long addr; int ret; + if (count == 0) + return -ENOBUFS; ret = mutex_lock_interruptible(&video->lock); if (ret) goto streamoff; diff --git a/drivers/staging/media/lirc/lirc_parallel.c b/drivers/staging/media/lirc/lirc_parallel.c index 41d110f8bc02..0b589892351a 100644 --- a/drivers/staging/media/lirc/lirc_parallel.c +++ b/drivers/staging/media/lirc/lirc_parallel.c @@ -220,7 +220,7 @@ static void rbuf_write(int signal) wptr = nwptr; } -static void irq_handler(void *blah) +static void lirc_lirc_irq_handler(void *blah) { struct timeval tv; static struct timeval lasttv; @@ -659,7 +659,7 @@ static int __init lirc_parallel_init(void) goto exit_device_put; } ppdevice = parport_register_device(pport, LIRC_DRIVER_NAME, - pf, kf, irq_handler, 0, NULL); + pf, kf, lirc_lirc_irq_handler, 0, NULL); parport_put_port(pport); if (ppdevice == NULL) { pr_notice("parport_register_device() failed\n"); diff --git a/drivers/staging/media/lirc/lirc_serial.c b/drivers/staging/media/lirc/lirc_serial.c index abe0d5caa20b..10c685d5de7c 100644 --- a/drivers/staging/media/lirc/lirc_serial.c +++ b/drivers/staging/media/lirc/lirc_serial.c @@ -650,7 +650,7 @@ static void frbwrite(int l) rbwrite(l); } -static irqreturn_t irq_handler(int i, void *blah) +static irqreturn_t lirc_irq_handler(int i, void *blah) { struct timeval tv; int counter, dcd; @@ -852,7 +852,7 @@ static int lirc_serial_probe(struct platform_device *dev) return result; #endif - result = request_irq(irq, irq_handler, + result = request_irq(irq, lirc_irq_handler, (share_irq ? IRQF_SHARED : 0), LIRC_DRIVER_NAME, (void *)&hardware); if (result < 0) { diff --git a/drivers/staging/media/omap24xx/Kconfig b/drivers/staging/media/omap24xx/Kconfig new file mode 100644 index 000000000000..82e569a21c46 --- /dev/null +++ b/drivers/staging/media/omap24xx/Kconfig @@ -0,0 +1,35 @@ +config VIDEO_V4L2_INT_DEVICE + tristate + +config VIDEO_OMAP2 + tristate "OMAP2 Camera Capture Interface driver (DEPRECATED)" + depends on VIDEO_DEV && ARCH_OMAP2 + select VIDEOBUF_DMA_SG + select VIDEO_V4L2_INT_DEVICE + ---help--- + This is a v4l2 driver for the TI OMAP2 camera capture interface + + It uses the deprecated int-device API. Since this driver is no + longer actively maintained and nobody is interested in converting + it to the subdev API, this driver will be removed soon. + + If you do want to keep this driver in the kernel, and are willing + to convert it to the subdev API, then please contact the linux-media + mailinglist. + +config VIDEO_TCM825X + tristate "TCM825x camera sensor support (DEPRECATED)" + depends on I2C && VIDEO_V4L2 + depends on MEDIA_CAMERA_SUPPORT + select VIDEO_V4L2_INT_DEVICE + ---help--- + This is a driver for the Toshiba TCM825x VGA camera sensor. + It is used for example in Nokia N800. + + It uses the deprecated int-device API. Since this driver is no + longer actively maintained and nobody is interested in converting + it to the subdev API, this driver will be removed soon. + + If you do want to keep this driver in the kernel, and are willing + to convert it to the subdev API, then please contact the linux-media + mailinglist. diff --git a/drivers/staging/media/omap24xx/Makefile b/drivers/staging/media/omap24xx/Makefile new file mode 100644 index 000000000000..c2e7175599c2 --- /dev/null +++ b/drivers/staging/media/omap24xx/Makefile @@ -0,0 +1,5 @@ +omap2cam-objs := omap24xxcam.o omap24xxcam-dma.o + +obj-$(CONFIG_VIDEO_OMAP2) += omap2cam.o +obj-$(CONFIG_VIDEO_TCM825X) += tcm825x.o +obj-$(CONFIG_VIDEO_V4L2_INT_DEVICE) += v4l2-int-device.o diff --git a/drivers/media/platform/omap24xxcam-dma.c b/drivers/staging/media/omap24xx/omap24xxcam-dma.c index 9c00776d6583..9c00776d6583 100644 --- a/drivers/media/platform/omap24xxcam-dma.c +++ b/drivers/staging/media/omap24xx/omap24xxcam-dma.c diff --git a/drivers/media/platform/omap24xxcam.c b/drivers/staging/media/omap24xx/omap24xxcam.c index d2b440c842b3..d2b440c842b3 100644 --- a/drivers/media/platform/omap24xxcam.c +++ b/drivers/staging/media/omap24xx/omap24xxcam.c diff --git a/drivers/media/platform/omap24xxcam.h b/drivers/staging/media/omap24xx/omap24xxcam.h index 7f6f79155537..233bb40cfec3 100644 --- a/drivers/media/platform/omap24xxcam.h +++ b/drivers/staging/media/omap24xx/omap24xxcam.h @@ -28,8 +28,8 @@ #define OMAP24XXCAM_H #include <media/videobuf-dma-sg.h> -#include <media/v4l2-int-device.h> #include <media/v4l2-device.h> +#include "v4l2-int-device.h" /* * diff --git a/drivers/media/i2c/tcm825x.c b/drivers/staging/media/omap24xx/tcm825x.c index 9252529fc5dd..b1ae8e9c7e14 100644 --- a/drivers/media/i2c/tcm825x.c +++ b/drivers/staging/media/omap24xx/tcm825x.c @@ -28,7 +28,7 @@ #include <linux/i2c.h> #include <linux/module.h> -#include <media/v4l2-int-device.h> +#include "v4l2-int-device.h" #include "tcm825x.h" diff --git a/drivers/media/i2c/tcm825x.h b/drivers/staging/media/omap24xx/tcm825x.h index 8ebab953963f..e2d1bcd0bcbe 100644 --- a/drivers/media/i2c/tcm825x.h +++ b/drivers/staging/media/omap24xx/tcm825x.h @@ -17,7 +17,7 @@ #include <linux/videodev2.h> -#include <media/v4l2-int-device.h> +#include "v4l2-int-device.h" #define TCM825X_NAME "tcm825x" diff --git a/drivers/media/v4l2-core/v4l2-int-device.c b/drivers/staging/media/omap24xx/v4l2-int-device.c index f4473494af7a..427a89033a1d 100644 --- a/drivers/media/v4l2-core/v4l2-int-device.c +++ b/drivers/staging/media/omap24xx/v4l2-int-device.c @@ -28,7 +28,7 @@ #include <linux/string.h> #include <linux/module.h> -#include <media/v4l2-int-device.h> +#include "v4l2-int-device.h" static DEFINE_MUTEX(mutex); static LIST_HEAD(int_list); diff --git a/drivers/staging/media/omap24xx/v4l2-int-device.h b/drivers/staging/media/omap24xx/v4l2-int-device.h new file mode 100644 index 000000000000..0286c95814ff --- /dev/null +++ b/drivers/staging/media/omap24xx/v4l2-int-device.h @@ -0,0 +1,305 @@ +/* + * include/media/v4l2-int-device.h + * + * V4L2 internal ioctl interface. + * + * Copyright (C) 2007 Nokia Corporation. + * + * Contact: Sakari Ailus <sakari.ailus@nokia.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. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef V4L2_INT_DEVICE_H +#define V4L2_INT_DEVICE_H + +#include <media/v4l2-common.h> + +#define V4L2NAMESIZE 32 + +/* + * + * The internal V4L2 device interface core. + * + */ + +enum v4l2_int_type { + v4l2_int_type_master = 1, + v4l2_int_type_slave +}; + +struct module; + +struct v4l2_int_device; + +struct v4l2_int_master { + int (*attach)(struct v4l2_int_device *slave); + void (*detach)(struct v4l2_int_device *slave); +}; + +typedef int (v4l2_int_ioctl_func)(struct v4l2_int_device *); +typedef int (v4l2_int_ioctl_func_0)(struct v4l2_int_device *); +typedef int (v4l2_int_ioctl_func_1)(struct v4l2_int_device *, void *); + +struct v4l2_int_ioctl_desc { + int num; + v4l2_int_ioctl_func *func; +}; + +struct v4l2_int_slave { + /* Don't touch master. */ + struct v4l2_int_device *master; + + char attach_to[V4L2NAMESIZE]; + + int num_ioctls; + struct v4l2_int_ioctl_desc *ioctls; +}; + +struct v4l2_int_device { + /* Don't touch head. */ + struct list_head head; + + struct module *module; + + char name[V4L2NAMESIZE]; + + enum v4l2_int_type type; + union { + struct v4l2_int_master *master; + struct v4l2_int_slave *slave; + } u; + + void *priv; +}; + +void v4l2_int_device_try_attach_all(void); + +int v4l2_int_device_register(struct v4l2_int_device *d); +void v4l2_int_device_unregister(struct v4l2_int_device *d); + +int v4l2_int_ioctl_0(struct v4l2_int_device *d, int cmd); +int v4l2_int_ioctl_1(struct v4l2_int_device *d, int cmd, void *arg); + +/* + * + * Types and definitions for IOCTL commands. + * + */ + +enum v4l2_power { + V4L2_POWER_OFF = 0, + V4L2_POWER_ON, + V4L2_POWER_STANDBY, +}; + +/* Slave interface type. */ +enum v4l2_if_type { + /* + * Parallel 8-, 10- or 12-bit interface, used by for example + * on certain image sensors. + */ + V4L2_IF_TYPE_BT656, +}; + +enum v4l2_if_type_bt656_mode { + /* + * Modes without Bt synchronisation codes. Separate + * synchronisation signal lines are used. + */ + V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT, + V4L2_IF_TYPE_BT656_MODE_NOBT_10BIT, + V4L2_IF_TYPE_BT656_MODE_NOBT_12BIT, + /* + * Use Bt synchronisation codes. The vertical and horizontal + * synchronisation is done based on synchronisation codes. + */ + V4L2_IF_TYPE_BT656_MODE_BT_8BIT, + V4L2_IF_TYPE_BT656_MODE_BT_10BIT, +}; + +struct v4l2_if_type_bt656 { + /* + * 0: Frame begins when vsync is high. + * 1: Frame begins when vsync changes from low to high. + */ + unsigned frame_start_on_rising_vs:1; + /* Use Bt synchronisation codes for sync correction. */ + unsigned bt_sync_correct:1; + /* Swap every two adjacent image data elements. */ + unsigned swap:1; + /* Inverted latch clock polarity from slave. */ + unsigned latch_clk_inv:1; + /* Hs polarity. 0 is active high, 1 active low. */ + unsigned nobt_hs_inv:1; + /* Vs polarity. 0 is active high, 1 active low. */ + unsigned nobt_vs_inv:1; + enum v4l2_if_type_bt656_mode mode; + /* Minimum accepted bus clock for slave (in Hz). */ + u32 clock_min; + /* Maximum accepted bus clock for slave. */ + u32 clock_max; + /* + * Current wish of the slave. May only change in response to + * ioctls that affect image capture. + */ + u32 clock_curr; +}; + +struct v4l2_ifparm { + enum v4l2_if_type if_type; + union { + struct v4l2_if_type_bt656 bt656; + } u; +}; + +/* IOCTL command numbers. */ +enum v4l2_int_ioctl_num { + /* + * + * "Proper" V4L ioctls, as in struct video_device. + * + */ + vidioc_int_enum_fmt_cap_num = 1, + vidioc_int_g_fmt_cap_num, + vidioc_int_s_fmt_cap_num, + vidioc_int_try_fmt_cap_num, + vidioc_int_queryctrl_num, + vidioc_int_g_ctrl_num, + vidioc_int_s_ctrl_num, + vidioc_int_cropcap_num, + vidioc_int_g_crop_num, + vidioc_int_s_crop_num, + vidioc_int_g_parm_num, + vidioc_int_s_parm_num, + vidioc_int_querystd_num, + vidioc_int_s_std_num, + vidioc_int_s_video_routing_num, + + /* + * + * Strictly internal ioctls. + * + */ + /* Initialise the device when slave attaches to the master. */ + vidioc_int_dev_init_num = 1000, + /* Delinitialise the device at slave detach. */ + vidioc_int_dev_exit_num, + /* Set device power state. */ + vidioc_int_s_power_num, + /* + * Get slave private data, e.g. platform-specific slave + * configuration used by the master. + */ + vidioc_int_g_priv_num, + /* Get slave interface parameters. */ + vidioc_int_g_ifparm_num, + /* Does the slave need to be reset after VIDIOC_DQBUF? */ + vidioc_int_g_needs_reset_num, + vidioc_int_enum_framesizes_num, + vidioc_int_enum_frameintervals_num, + + /* + * + * VIDIOC_INT_* ioctls. + * + */ + /* VIDIOC_INT_RESET */ + vidioc_int_reset_num, + /* VIDIOC_INT_INIT */ + vidioc_int_init_num, + + /* + * + * Start of private ioctls. + * + */ + vidioc_int_priv_start_num = 2000, +}; + +/* + * + * IOCTL wrapper functions for better type checking. + * + */ + +#define V4L2_INT_WRAPPER_0(name) \ + static inline int vidioc_int_##name(struct v4l2_int_device *d) \ + { \ + return v4l2_int_ioctl_0(d, vidioc_int_##name##_num); \ + } \ + \ + static inline struct v4l2_int_ioctl_desc \ + vidioc_int_##name##_cb(int (*func) \ + (struct v4l2_int_device *)) \ + { \ + struct v4l2_int_ioctl_desc desc; \ + \ + desc.num = vidioc_int_##name##_num; \ + desc.func = (v4l2_int_ioctl_func *)func; \ + \ + return desc; \ + } + +#define V4L2_INT_WRAPPER_1(name, arg_type, asterisk) \ + static inline int vidioc_int_##name(struct v4l2_int_device *d, \ + arg_type asterisk arg) \ + { \ + return v4l2_int_ioctl_1(d, vidioc_int_##name##_num, \ + (void *)(unsigned long)arg); \ + } \ + \ + static inline struct v4l2_int_ioctl_desc \ + vidioc_int_##name##_cb(int (*func) \ + (struct v4l2_int_device *, \ + arg_type asterisk)) \ + { \ + struct v4l2_int_ioctl_desc desc; \ + \ + desc.num = vidioc_int_##name##_num; \ + desc.func = (v4l2_int_ioctl_func *)func; \ + \ + return desc; \ + } + +V4L2_INT_WRAPPER_1(enum_fmt_cap, struct v4l2_fmtdesc, *); +V4L2_INT_WRAPPER_1(g_fmt_cap, struct v4l2_format, *); +V4L2_INT_WRAPPER_1(s_fmt_cap, struct v4l2_format, *); +V4L2_INT_WRAPPER_1(try_fmt_cap, struct v4l2_format, *); +V4L2_INT_WRAPPER_1(queryctrl, struct v4l2_queryctrl, *); +V4L2_INT_WRAPPER_1(g_ctrl, struct v4l2_control, *); +V4L2_INT_WRAPPER_1(s_ctrl, struct v4l2_control, *); +V4L2_INT_WRAPPER_1(cropcap, struct v4l2_cropcap, *); +V4L2_INT_WRAPPER_1(g_crop, struct v4l2_crop, *); +V4L2_INT_WRAPPER_1(s_crop, struct v4l2_crop, *); +V4L2_INT_WRAPPER_1(g_parm, struct v4l2_streamparm, *); +V4L2_INT_WRAPPER_1(s_parm, struct v4l2_streamparm, *); +V4L2_INT_WRAPPER_1(querystd, v4l2_std_id, *); +V4L2_INT_WRAPPER_1(s_std, v4l2_std_id, *); +V4L2_INT_WRAPPER_1(s_video_routing, struct v4l2_routing, *); + +V4L2_INT_WRAPPER_0(dev_init); +V4L2_INT_WRAPPER_0(dev_exit); +V4L2_INT_WRAPPER_1(s_power, enum v4l2_power, ); +V4L2_INT_WRAPPER_1(g_priv, void, *); +V4L2_INT_WRAPPER_1(g_ifparm, struct v4l2_ifparm, *); +V4L2_INT_WRAPPER_1(g_needs_reset, void, *); +V4L2_INT_WRAPPER_1(enum_framesizes, struct v4l2_frmsizeenum, *); +V4L2_INT_WRAPPER_1(enum_frameintervals, struct v4l2_frmivalenum, *); + +V4L2_INT_WRAPPER_0(reset); +V4L2_INT_WRAPPER_0(init); + +#endif diff --git a/drivers/staging/media/omap4iss/Kconfig b/drivers/staging/media/omap4iss/Kconfig new file mode 100644 index 000000000000..b9fe753969bd --- /dev/null +++ b/drivers/staging/media/omap4iss/Kconfig @@ -0,0 +1,12 @@ +config VIDEO_OMAP4 + bool "OMAP 4 Camera support" + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && I2C && ARCH_OMAP4 + select VIDEOBUF2_DMA_CONTIG + ---help--- + Driver for an OMAP 4 ISS controller. + +config VIDEO_OMAP4_DEBUG + bool "OMAP 4 Camera debug messages" + depends on VIDEO_OMAP4 + ---help--- + Enable debug messages on OMAP 4 ISS controller driver. diff --git a/drivers/staging/media/omap4iss/Makefile b/drivers/staging/media/omap4iss/Makefile new file mode 100644 index 000000000000..a716ce936cf6 --- /dev/null +++ b/drivers/staging/media/omap4iss/Makefile @@ -0,0 +1,6 @@ +# Makefile for OMAP4 ISS driver + +omap4-iss-objs += \ + iss.o iss_csi2.o iss_csiphy.o iss_ipipeif.o iss_ipipe.o iss_resizer.o iss_video.o + +obj-$(CONFIG_VIDEO_OMAP4) += omap4-iss.o diff --git a/drivers/staging/media/omap4iss/TODO b/drivers/staging/media/omap4iss/TODO new file mode 100644 index 000000000000..fcde88860a2c --- /dev/null +++ b/drivers/staging/media/omap4iss/TODO @@ -0,0 +1,4 @@ +* Make the driver compile as a module +* Fix FIFO/buffer overflows and underflows +* Replace dummy resizer code with a real implementation +* Fix checkpatch errors and warnings diff --git a/drivers/staging/media/omap4iss/iss.c b/drivers/staging/media/omap4iss/iss.c new file mode 100644 index 000000000000..61fbfcd13582 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss.c @@ -0,0 +1,1563 @@ +/* + * TI OMAP4 ISS V4L2 Driver + * + * Copyright (C) 2012, Texas Instruments + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.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/dma-mapping.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/vmalloc.h> + +#include <media/v4l2-common.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> + +#include "iss.h" +#include "iss_regs.h" + +#define ISS_PRINT_REGISTER(iss, name)\ + dev_dbg(iss->dev, "###ISS " #name "=0x%08x\n", \ + iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_##name)) + +static void iss_print_status(struct iss_device *iss) +{ + dev_dbg(iss->dev, "-------------ISS HL Register dump-------------\n"); + + ISS_PRINT_REGISTER(iss, HL_REVISION); + ISS_PRINT_REGISTER(iss, HL_SYSCONFIG); + ISS_PRINT_REGISTER(iss, HL_IRQSTATUS(5)); + ISS_PRINT_REGISTER(iss, HL_IRQENABLE_SET(5)); + ISS_PRINT_REGISTER(iss, HL_IRQENABLE_CLR(5)); + ISS_PRINT_REGISTER(iss, CTRL); + ISS_PRINT_REGISTER(iss, CLKCTRL); + ISS_PRINT_REGISTER(iss, CLKSTAT); + + dev_dbg(iss->dev, "-----------------------------------------------\n"); +} + +/* + * omap4iss_flush - Post pending L3 bus writes by doing a register readback + * @iss: OMAP4 ISS device + * + * In order to force posting of pending writes, we need to write and + * readback the same register, in this case the revision register. + * + * See this link for reference: + * http://www.mail-archive.com/linux-omap@vger.kernel.org/msg08149.html + */ +void omap4iss_flush(struct iss_device *iss) +{ + iss_reg_write(iss, OMAP4_ISS_MEM_TOP, ISS_HL_REVISION, 0); + iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_HL_REVISION); +} + +/* + * iss_isp_enable_interrupts - Enable ISS ISP interrupts. + * @iss: OMAP4 ISS device + */ +static void omap4iss_isp_enable_interrupts(struct iss_device *iss) +{ + static const u32 isp_irq = ISP5_IRQ_OCP_ERR | + ISP5_IRQ_RSZ_FIFO_IN_BLK_ERR | + ISP5_IRQ_RSZ_FIFO_OVF | + ISP5_IRQ_RSZ_INT_DMA | + ISP5_IRQ_ISIF_INT(0); + + /* Enable ISP interrupts */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_IRQSTATUS(0), isp_irq); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_IRQENABLE_SET(0), + isp_irq); +} + +/* + * iss_isp_disable_interrupts - Disable ISS interrupts. + * @iss: OMAP4 ISS device + */ +static void omap4iss_isp_disable_interrupts(struct iss_device *iss) +{ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_IRQENABLE_CLR(0), ~0); +} + +/* + * iss_enable_interrupts - Enable ISS interrupts. + * @iss: OMAP4 ISS device + */ +static void iss_enable_interrupts(struct iss_device *iss) +{ + static const u32 hl_irq = ISS_HL_IRQ_CSIA | ISS_HL_IRQ_CSIB + | ISS_HL_IRQ_ISP(0); + + /* Enable HL interrupts */ + iss_reg_write(iss, OMAP4_ISS_MEM_TOP, ISS_HL_IRQSTATUS(5), hl_irq); + iss_reg_write(iss, OMAP4_ISS_MEM_TOP, ISS_HL_IRQENABLE_SET(5), hl_irq); + + if (iss->regs[OMAP4_ISS_MEM_ISP_SYS1]) + omap4iss_isp_enable_interrupts(iss); +} + +/* + * iss_disable_interrupts - Disable ISS interrupts. + * @iss: OMAP4 ISS device + */ +static void iss_disable_interrupts(struct iss_device *iss) +{ + if (iss->regs[OMAP4_ISS_MEM_ISP_SYS1]) + omap4iss_isp_disable_interrupts(iss); + + iss_reg_write(iss, OMAP4_ISS_MEM_TOP, ISS_HL_IRQENABLE_CLR(5), ~0); +} + +int omap4iss_get_external_info(struct iss_pipeline *pipe, + struct media_link *link) +{ + struct iss_device *iss = + container_of(pipe, struct iss_video, pipe)->iss; + struct v4l2_subdev_format fmt; + struct v4l2_ctrl *ctrl; + int ret; + + if (!pipe->external) + return 0; + + if (pipe->external_rate) + return 0; + + memset(&fmt, 0, sizeof(fmt)); + + fmt.pad = link->source->index; + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(media_entity_to_v4l2_subdev(link->sink->entity), + pad, get_fmt, NULL, &fmt); + if (ret < 0) + return -EPIPE; + + pipe->external_bpp = omap4iss_video_format_info(fmt.format.code)->bpp; + + ctrl = v4l2_ctrl_find(pipe->external->ctrl_handler, + V4L2_CID_PIXEL_RATE); + if (ctrl == NULL) { + dev_warn(iss->dev, "no pixel rate control in subdev %s\n", + pipe->external->name); + return -EPIPE; + } + + pipe->external_rate = v4l2_ctrl_g_ctrl_int64(ctrl); + + return 0; +} + +/* + * Configure the bridge. Valid inputs are + * + * IPIPEIF_INPUT_CSI2A: CSI2a receiver + * IPIPEIF_INPUT_CSI2B: CSI2b receiver + * + * The bridge and lane shifter are configured according to the selected input + * and the ISP platform data. + */ +void omap4iss_configure_bridge(struct iss_device *iss, + enum ipipeif_input_entity input) +{ + u32 issctrl_val; + u32 isp5ctrl_val; + + issctrl_val = iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_CTRL); + issctrl_val &= ~ISS_CTRL_INPUT_SEL_MASK; + issctrl_val &= ~ISS_CTRL_CLK_DIV_MASK; + + isp5ctrl_val = iss_reg_read(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_CTRL); + + switch (input) { + case IPIPEIF_INPUT_CSI2A: + issctrl_val |= ISS_CTRL_INPUT_SEL_CSI2A; + break; + + case IPIPEIF_INPUT_CSI2B: + issctrl_val |= ISS_CTRL_INPUT_SEL_CSI2B; + break; + + default: + return; + } + + issctrl_val |= ISS_CTRL_SYNC_DETECT_VS_RAISING; + + isp5ctrl_val |= ISP5_CTRL_VD_PULSE_EXT | ISP5_CTRL_PSYNC_CLK_SEL | + ISP5_CTRL_SYNC_ENABLE; + + iss_reg_write(iss, OMAP4_ISS_MEM_TOP, ISS_CTRL, issctrl_val); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_CTRL, isp5ctrl_val); +} + +#if defined(DEBUG) && defined(ISS_ISR_DEBUG) +static void iss_isr_dbg(struct iss_device *iss, u32 irqstatus) +{ + static const char * const name[] = { + "ISP_0", + "ISP_1", + "ISP_2", + "ISP_3", + "CSIA", + "CSIB", + "CCP2_0", + "CCP2_1", + "CCP2_2", + "CCP2_3", + "CBUFF", + "BTE", + "SIMCOP_0", + "SIMCOP_1", + "SIMCOP_2", + "SIMCOP_3", + "CCP2_8", + "HS_VS", + "18", + "19", + "20", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "28", + "29", + "30", + "31", + }; + unsigned int i; + + dev_dbg(iss->dev, "ISS IRQ: "); + + for (i = 0; i < ARRAY_SIZE(name); i++) { + if ((1 << i) & irqstatus) + pr_cont("%s ", name[i]); + } + pr_cont("\n"); +} + +static void iss_isp_isr_dbg(struct iss_device *iss, u32 irqstatus) +{ + static const char * const name[] = { + "ISIF_0", + "ISIF_1", + "ISIF_2", + "ISIF_3", + "IPIPEREQ", + "IPIPELAST_PIX", + "IPIPEDMA", + "IPIPEBSC", + "IPIPEHST", + "IPIPEIF", + "AEW", + "AF", + "H3A", + "RSZ_REG", + "RSZ_LAST_PIX", + "RSZ_DMA", + "RSZ_CYC_RZA", + "RSZ_CYC_RZB", + "RSZ_FIFO_OVF", + "RSZ_FIFO_IN_BLK_ERR", + "20", + "21", + "RSZ_EOF0", + "RSZ_EOF1", + "H3A_EOF", + "IPIPE_EOF", + "26", + "IPIPE_DPC_INI", + "IPIPE_DPC_RNEW0", + "IPIPE_DPC_RNEW1", + "30", + "OCP_ERR", + }; + unsigned int i; + + dev_dbg(iss->dev, "ISP IRQ: "); + + for (i = 0; i < ARRAY_SIZE(name); i++) { + if ((1 << i) & irqstatus) + pr_cont("%s ", name[i]); + } + pr_cont("\n"); +} +#endif + +/* + * iss_isr - Interrupt Service Routine for ISS module. + * @irq: Not used currently. + * @_iss: Pointer to the OMAP4 ISS device + * + * Handles the corresponding callback if plugged in. + * + * Returns IRQ_HANDLED when IRQ was correctly handled, or IRQ_NONE when the + * IRQ wasn't handled. + */ +static irqreturn_t iss_isr(int irq, void *_iss) +{ + static const u32 ipipeif_events = ISP5_IRQ_IPIPEIF_IRQ | + ISP5_IRQ_ISIF_INT(0); + static const u32 resizer_events = ISP5_IRQ_RSZ_FIFO_IN_BLK_ERR | + ISP5_IRQ_RSZ_FIFO_OVF | + ISP5_IRQ_RSZ_INT_DMA; + struct iss_device *iss = _iss; + u32 irqstatus; + + irqstatus = iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_HL_IRQSTATUS(5)); + iss_reg_write(iss, OMAP4_ISS_MEM_TOP, ISS_HL_IRQSTATUS(5), irqstatus); + + if (irqstatus & ISS_HL_IRQ_CSIA) + omap4iss_csi2_isr(&iss->csi2a); + + if (irqstatus & ISS_HL_IRQ_CSIB) + omap4iss_csi2_isr(&iss->csi2b); + + if (irqstatus & ISS_HL_IRQ_ISP(0)) { + u32 isp_irqstatus = iss_reg_read(iss, OMAP4_ISS_MEM_ISP_SYS1, + ISP5_IRQSTATUS(0)); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_IRQSTATUS(0), + isp_irqstatus); + + if (isp_irqstatus & ISP5_IRQ_OCP_ERR) + dev_dbg(iss->dev, "ISP5 OCP Error!\n"); + + if (isp_irqstatus & ipipeif_events) { + omap4iss_ipipeif_isr(&iss->ipipeif, + isp_irqstatus & ipipeif_events); + } + + if (isp_irqstatus & resizer_events) + omap4iss_resizer_isr(&iss->resizer, + isp_irqstatus & resizer_events); + +#if defined(DEBUG) && defined(ISS_ISR_DEBUG) + iss_isp_isr_dbg(iss, isp_irqstatus); +#endif + } + + omap4iss_flush(iss); + +#if defined(DEBUG) && defined(ISS_ISR_DEBUG) + iss_isr_dbg(iss, irqstatus); +#endif + + return IRQ_HANDLED; +} + +/* ----------------------------------------------------------------------------- + * Pipeline power management + * + * Entities must be powered up when part of a pipeline that contains at least + * one open video device node. + * + * To achieve this use the entity use_count field to track the number of users. + * For entities corresponding to video device nodes the use_count field stores + * the users count of the node. For entities corresponding to subdevs the + * use_count field stores the total number of users of all video device nodes + * in the pipeline. + * + * The omap4iss_pipeline_pm_use() function must be called in the open() and + * close() handlers of video device nodes. It increments or decrements the use + * count of all subdev entities in the pipeline. + * + * To react to link management on powered pipelines, the link setup notification + * callback updates the use count of all entities in the source and sink sides + * of the link. + */ + +/* + * iss_pipeline_pm_use_count - Count the number of users of a pipeline + * @entity: The entity + * + * Return the total number of users of all video device nodes in the pipeline. + */ +static int iss_pipeline_pm_use_count(struct media_entity *entity) +{ + struct media_entity_graph graph; + int use = 0; + + media_entity_graph_walk_start(&graph, entity); + + while ((entity = media_entity_graph_walk_next(&graph))) { + if (media_entity_type(entity) == MEDIA_ENT_T_DEVNODE) + use += entity->use_count; + } + + return use; +} + +/* + * iss_pipeline_pm_power_one - Apply power change to an entity + * @entity: The entity + * @change: Use count change + * + * Change the entity use count by @change. If the entity is a subdev update its + * power state by calling the core::s_power operation when the use count goes + * from 0 to != 0 or from != 0 to 0. + * + * Return 0 on success or a negative error code on failure. + */ +static int iss_pipeline_pm_power_one(struct media_entity *entity, int change) +{ + struct v4l2_subdev *subdev; + + subdev = media_entity_type(entity) == MEDIA_ENT_T_V4L2_SUBDEV + ? media_entity_to_v4l2_subdev(entity) : NULL; + + if (entity->use_count == 0 && change > 0 && subdev != NULL) { + int ret; + + ret = v4l2_subdev_call(subdev, core, s_power, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) + return ret; + } + + entity->use_count += change; + WARN_ON(entity->use_count < 0); + + if (entity->use_count == 0 && change < 0 && subdev != NULL) + v4l2_subdev_call(subdev, core, s_power, 0); + + return 0; +} + +/* + * iss_pipeline_pm_power - Apply power change to all entities in a pipeline + * @entity: The entity + * @change: Use count change + * + * Walk the pipeline to update the use count and the power state of all non-node + * entities. + * + * Return 0 on success or a negative error code on failure. + */ +static int iss_pipeline_pm_power(struct media_entity *entity, int change) +{ + struct media_entity_graph graph; + struct media_entity *first = entity; + int ret = 0; + + if (!change) + return 0; + + media_entity_graph_walk_start(&graph, entity); + + while (!ret && (entity = media_entity_graph_walk_next(&graph))) + if (media_entity_type(entity) != MEDIA_ENT_T_DEVNODE) + ret = iss_pipeline_pm_power_one(entity, change); + + if (!ret) + return 0; + + media_entity_graph_walk_start(&graph, first); + + while ((first = media_entity_graph_walk_next(&graph)) + && first != entity) + if (media_entity_type(first) != MEDIA_ENT_T_DEVNODE) + iss_pipeline_pm_power_one(first, -change); + + return ret; +} + +/* + * omap4iss_pipeline_pm_use - Update the use count of an entity + * @entity: The entity + * @use: Use (1) or stop using (0) the entity + * + * Update the use count of all entities in the pipeline and power entities on or + * off accordingly. + * + * Return 0 on success or a negative error code on failure. Powering entities + * off is assumed to never fail. No failure can occur when the use parameter is + * set to 0. + */ +int omap4iss_pipeline_pm_use(struct media_entity *entity, int use) +{ + int change = use ? 1 : -1; + int ret; + + mutex_lock(&entity->parent->graph_mutex); + + /* Apply use count to node. */ + entity->use_count += change; + WARN_ON(entity->use_count < 0); + + /* Apply power change to connected non-nodes. */ + ret = iss_pipeline_pm_power(entity, change); + if (ret < 0) + entity->use_count -= change; + + mutex_unlock(&entity->parent->graph_mutex); + + return ret; +} + +/* + * iss_pipeline_link_notify - Link management notification callback + * @link: The link + * @flags: New link flags that will be applied + * + * React to link management on powered pipelines by updating the use count of + * all entities in the source and sink sides of the link. Entities are powered + * on or off accordingly. + * + * Return 0 on success or a negative error code on failure. Powering entities + * off is assumed to never fail. This function will not fail for disconnection + * events. + */ +static int iss_pipeline_link_notify(struct media_link *link, u32 flags, + unsigned int notification) +{ + struct media_entity *source = link->source->entity; + struct media_entity *sink = link->sink->entity; + int source_use = iss_pipeline_pm_use_count(source); + int sink_use = iss_pipeline_pm_use_count(sink); + int ret; + + if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH && + !(link->flags & MEDIA_LNK_FL_ENABLED)) { + /* Powering off entities is assumed to never fail. */ + iss_pipeline_pm_power(source, -sink_use); + iss_pipeline_pm_power(sink, -source_use); + return 0; + } + + if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH && + (flags & MEDIA_LNK_FL_ENABLED)) { + ret = iss_pipeline_pm_power(source, sink_use); + if (ret < 0) + return ret; + + ret = iss_pipeline_pm_power(sink, source_use); + if (ret < 0) + iss_pipeline_pm_power(source, -sink_use); + + return ret; + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Pipeline stream management + */ + +/* + * iss_pipeline_enable - Enable streaming on a pipeline + * @pipe: ISS pipeline + * @mode: Stream mode (single shot or continuous) + * + * Walk the entities chain starting at the pipeline output video node and start + * all modules in the chain in the given mode. + * + * Return 0 if successful, or the return value of the failed video::s_stream + * operation otherwise. + */ +static int iss_pipeline_enable(struct iss_pipeline *pipe, + enum iss_pipeline_stream_state mode) +{ + struct iss_device *iss = pipe->output->iss; + struct media_entity *entity; + struct media_pad *pad; + struct v4l2_subdev *subdev; + unsigned long flags; + int ret; + + /* If one of the entities in the pipeline has crashed it will not work + * properly. Refuse to start streaming in that case. This check must be + * performed before the loop below to avoid starting entities if the + * pipeline won't start anyway (those entities would then likely fail to + * stop, making the problem worse). + */ + if (pipe->entities & iss->crashed) + return -EIO; + + spin_lock_irqsave(&pipe->lock, flags); + pipe->state &= ~(ISS_PIPELINE_IDLE_INPUT | ISS_PIPELINE_IDLE_OUTPUT); + spin_unlock_irqrestore(&pipe->lock, flags); + + pipe->do_propagation = false; + + entity = &pipe->output->video.entity; + while (1) { + pad = &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + pad = media_entity_remote_pad(pad); + if (pad == NULL || + media_entity_type(pad->entity) != MEDIA_ENT_T_V4L2_SUBDEV) + break; + + entity = pad->entity; + subdev = media_entity_to_v4l2_subdev(entity); + + ret = v4l2_subdev_call(subdev, video, s_stream, mode); + if (ret < 0 && ret != -ENOIOCTLCMD) + return ret; + } + iss_print_status(pipe->output->iss); + return 0; +} + +/* + * iss_pipeline_disable - Disable streaming on a pipeline + * @pipe: ISS pipeline + * + * Walk the entities chain starting at the pipeline output video node and stop + * all modules in the chain. Wait synchronously for the modules to be stopped if + * necessary. + */ +static int iss_pipeline_disable(struct iss_pipeline *pipe) +{ + struct iss_device *iss = pipe->output->iss; + struct media_entity *entity; + struct media_pad *pad; + struct v4l2_subdev *subdev; + int failure = 0; + int ret; + + entity = &pipe->output->video.entity; + while (1) { + pad = &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + pad = media_entity_remote_pad(pad); + if (pad == NULL || + media_entity_type(pad->entity) != MEDIA_ENT_T_V4L2_SUBDEV) + break; + + entity = pad->entity; + subdev = media_entity_to_v4l2_subdev(entity); + + ret = v4l2_subdev_call(subdev, video, s_stream, 0); + if (ret < 0) { + dev_dbg(iss->dev, "%s: module stop timeout.\n", + subdev->name); + /* If the entity failed to stopped, assume it has + * crashed. Mark it as such, the ISS will be reset when + * applications will release it. + */ + iss->crashed |= 1U << subdev->entity.id; + failure = -ETIMEDOUT; + } + } + + return failure; +} + +/* + * omap4iss_pipeline_set_stream - Enable/disable streaming on a pipeline + * @pipe: ISS pipeline + * @state: Stream state (stopped, single shot or continuous) + * + * Set the pipeline to the given stream state. Pipelines can be started in + * single-shot or continuous mode. + * + * Return 0 if successful, or the return value of the failed video::s_stream + * operation otherwise. The pipeline state is not updated when the operation + * fails, except when stopping the pipeline. + */ +int omap4iss_pipeline_set_stream(struct iss_pipeline *pipe, + enum iss_pipeline_stream_state state) +{ + int ret; + + if (state == ISS_PIPELINE_STREAM_STOPPED) + ret = iss_pipeline_disable(pipe); + else + ret = iss_pipeline_enable(pipe, state); + + if (ret == 0 || state == ISS_PIPELINE_STREAM_STOPPED) + pipe->stream_state = state; + + return ret; +} + +/* + * omap4iss_pipeline_cancel_stream - Cancel stream on a pipeline + * @pipe: ISS pipeline + * + * Cancelling a stream mark all buffers on all video nodes in the pipeline as + * erroneous and makes sure no new buffer can be queued. This function is called + * when a fatal error that prevents any further operation on the pipeline + * occurs. + */ +void omap4iss_pipeline_cancel_stream(struct iss_pipeline *pipe) +{ + if (pipe->input) + omap4iss_video_cancel_stream(pipe->input); + if (pipe->output) + omap4iss_video_cancel_stream(pipe->output); +} + +/* + * iss_pipeline_is_last - Verify if entity has an enabled link to the output + * video node + * @me: ISS module's media entity + * + * Returns 1 if the entity has an enabled link to the output video node or 0 + * otherwise. It's true only while pipeline can have no more than one output + * node. + */ +static int iss_pipeline_is_last(struct media_entity *me) +{ + struct iss_pipeline *pipe; + struct media_pad *pad; + + if (!me->pipe) + return 0; + pipe = to_iss_pipeline(me); + if (pipe->stream_state == ISS_PIPELINE_STREAM_STOPPED) + return 0; + pad = media_entity_remote_pad(&pipe->output->pad); + return pad->entity == me; +} + +static int iss_reset(struct iss_device *iss) +{ + unsigned long timeout = 0; + + iss_reg_set(iss, OMAP4_ISS_MEM_TOP, ISS_HL_SYSCONFIG, + ISS_HL_SYSCONFIG_SOFTRESET); + + while (iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_HL_SYSCONFIG) & + ISS_HL_SYSCONFIG_SOFTRESET) { + if (timeout++ > 100) { + dev_alert(iss->dev, "cannot reset ISS\n"); + return -ETIMEDOUT; + } + usleep_range(10, 10); + } + + iss->crashed = 0; + return 0; +} + +static int iss_isp_reset(struct iss_device *iss) +{ + unsigned long timeout = 0; + + /* Fist, ensure that the ISP is IDLE (no transactions happening) */ + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_SYSCONFIG, + ISP5_SYSCONFIG_STANDBYMODE_MASK, + ISP5_SYSCONFIG_STANDBYMODE_SMART); + + iss_reg_set(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_CTRL, ISP5_CTRL_MSTANDBY); + + for (;;) { + if (iss_reg_read(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_CTRL) & + ISP5_CTRL_MSTANDBY_WAIT) + break; + if (timeout++ > 1000) { + dev_alert(iss->dev, "cannot set ISP5 to standby\n"); + return -ETIMEDOUT; + } + usleep_range(1000, 1500); + } + + /* Now finally, do the reset */ + iss_reg_set(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_SYSCONFIG, + ISP5_SYSCONFIG_SOFTRESET); + + timeout = 0; + while (iss_reg_read(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_SYSCONFIG) & + ISP5_SYSCONFIG_SOFTRESET) { + if (timeout++ > 1000) { + dev_alert(iss->dev, "cannot reset ISP5\n"); + return -ETIMEDOUT; + } + usleep_range(1000, 1500); + } + + return 0; +} + +/* + * iss_module_sync_idle - Helper to sync module with its idle state + * @me: ISS submodule's media entity + * @wait: ISS submodule's wait queue for streamoff/interrupt synchronization + * @stopping: flag which tells module wants to stop + * + * This function checks if ISS submodule needs to wait for next interrupt. If + * yes, makes the caller to sleep while waiting for such event. + */ +int omap4iss_module_sync_idle(struct media_entity *me, wait_queue_head_t *wait, + atomic_t *stopping) +{ + struct iss_pipeline *pipe = to_iss_pipeline(me); + struct iss_video *video = pipe->output; + unsigned long flags; + + if (pipe->stream_state == ISS_PIPELINE_STREAM_STOPPED || + (pipe->stream_state == ISS_PIPELINE_STREAM_SINGLESHOT && + !iss_pipeline_ready(pipe))) + return 0; + + /* + * atomic_set() doesn't include memory barrier on ARM platform for SMP + * scenario. We'll call it here to avoid race conditions. + */ + atomic_set(stopping, 1); + smp_wmb(); + + /* + * If module is the last one, it's writing to memory. In this case, + * it's necessary to check if the module is already paused due to + * DMA queue underrun or if it has to wait for next interrupt to be + * idle. + * If it isn't the last one, the function won't sleep but *stopping + * will still be set to warn next submodule caller's interrupt the + * module wants to be idle. + */ + if (!iss_pipeline_is_last(me)) + return 0; + + spin_lock_irqsave(&video->qlock, flags); + if (video->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_UNDERRUN) { + spin_unlock_irqrestore(&video->qlock, flags); + atomic_set(stopping, 0); + smp_wmb(); + return 0; + } + spin_unlock_irqrestore(&video->qlock, flags); + if (!wait_event_timeout(*wait, !atomic_read(stopping), + msecs_to_jiffies(1000))) { + atomic_set(stopping, 0); + smp_wmb(); + return -ETIMEDOUT; + } + + return 0; +} + +/* + * omap4iss_module_sync_is_stopped - Helper to verify if module was stopping + * @wait: ISS submodule's wait queue for streamoff/interrupt synchronization + * @stopping: flag which tells module wants to stop + * + * This function checks if ISS submodule was stopping. In case of yes, it + * notices the caller by setting stopping to 0 and waking up the wait queue. + * Returns 1 if it was stopping or 0 otherwise. + */ +int omap4iss_module_sync_is_stopping(wait_queue_head_t *wait, + atomic_t *stopping) +{ + if (atomic_cmpxchg(stopping, 1, 0)) { + wake_up(wait); + return 1; + } + + return 0; +} + +/* -------------------------------------------------------------------------- + * Clock management + */ + +#define ISS_CLKCTRL_MASK (ISS_CLKCTRL_CSI2_A |\ + ISS_CLKCTRL_CSI2_B |\ + ISS_CLKCTRL_ISP) + +static int __iss_subclk_update(struct iss_device *iss) +{ + u32 clk = 0; + int ret = 0, timeout = 1000; + + if (iss->subclk_resources & OMAP4_ISS_SUBCLK_CSI2_A) + clk |= ISS_CLKCTRL_CSI2_A; + + if (iss->subclk_resources & OMAP4_ISS_SUBCLK_CSI2_B) + clk |= ISS_CLKCTRL_CSI2_B; + + if (iss->subclk_resources & OMAP4_ISS_SUBCLK_ISP) + clk |= ISS_CLKCTRL_ISP; + + iss_reg_update(iss, OMAP4_ISS_MEM_TOP, ISS_CLKCTRL, + ISS_CLKCTRL_MASK, clk); + + /* Wait for HW assertion */ + while (--timeout > 0) { + udelay(1); + if ((iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_CLKSTAT) & + ISS_CLKCTRL_MASK) == clk) + break; + } + + if (!timeout) + ret = -EBUSY; + + return ret; +} + +int omap4iss_subclk_enable(struct iss_device *iss, + enum iss_subclk_resource res) +{ + iss->subclk_resources |= res; + + return __iss_subclk_update(iss); +} + +int omap4iss_subclk_disable(struct iss_device *iss, + enum iss_subclk_resource res) +{ + iss->subclk_resources &= ~res; + + return __iss_subclk_update(iss); +} + +#define ISS_ISP5_CLKCTRL_MASK (ISP5_CTRL_BL_CLK_ENABLE |\ + ISP5_CTRL_ISIF_CLK_ENABLE |\ + ISP5_CTRL_H3A_CLK_ENABLE |\ + ISP5_CTRL_RSZ_CLK_ENABLE |\ + ISP5_CTRL_IPIPE_CLK_ENABLE |\ + ISP5_CTRL_IPIPEIF_CLK_ENABLE) + +static void __iss_isp_subclk_update(struct iss_device *iss) +{ + u32 clk = 0; + + if (iss->isp_subclk_resources & OMAP4_ISS_ISP_SUBCLK_ISIF) + clk |= ISP5_CTRL_ISIF_CLK_ENABLE; + + if (iss->isp_subclk_resources & OMAP4_ISS_ISP_SUBCLK_H3A) + clk |= ISP5_CTRL_H3A_CLK_ENABLE; + + if (iss->isp_subclk_resources & OMAP4_ISS_ISP_SUBCLK_RSZ) + clk |= ISP5_CTRL_RSZ_CLK_ENABLE; + + if (iss->isp_subclk_resources & OMAP4_ISS_ISP_SUBCLK_IPIPE) + clk |= ISP5_CTRL_IPIPE_CLK_ENABLE; + + if (iss->isp_subclk_resources & OMAP4_ISS_ISP_SUBCLK_IPIPEIF) + clk |= ISP5_CTRL_IPIPEIF_CLK_ENABLE; + + if (clk) + clk |= ISP5_CTRL_BL_CLK_ENABLE; + + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_CTRL, + ISS_ISP5_CLKCTRL_MASK, clk); +} + +void omap4iss_isp_subclk_enable(struct iss_device *iss, + enum iss_isp_subclk_resource res) +{ + iss->isp_subclk_resources |= res; + + __iss_isp_subclk_update(iss); +} + +void omap4iss_isp_subclk_disable(struct iss_device *iss, + enum iss_isp_subclk_resource res) +{ + iss->isp_subclk_resources &= ~res; + + __iss_isp_subclk_update(iss); +} + +/* + * iss_enable_clocks - Enable ISS clocks + * @iss: OMAP4 ISS device + * + * Return 0 if successful, or clk_enable return value if any of tthem fails. + */ +static int iss_enable_clocks(struct iss_device *iss) +{ + int ret; + + ret = clk_enable(iss->iss_fck); + if (ret) { + dev_err(iss->dev, "clk_enable iss_fck failed\n"); + return ret; + } + + ret = clk_enable(iss->iss_ctrlclk); + if (ret) { + dev_err(iss->dev, "clk_enable iss_ctrlclk failed\n"); + clk_disable(iss->iss_fck); + return ret; + } + + return 0; +} + +/* + * iss_disable_clocks - Disable ISS clocks + * @iss: OMAP4 ISS device + */ +static void iss_disable_clocks(struct iss_device *iss) +{ + clk_disable(iss->iss_ctrlclk); + clk_disable(iss->iss_fck); +} + +static void iss_put_clocks(struct iss_device *iss) +{ + if (iss->iss_fck) { + clk_put(iss->iss_fck); + iss->iss_fck = NULL; + } + + if (iss->iss_ctrlclk) { + clk_put(iss->iss_ctrlclk); + iss->iss_ctrlclk = NULL; + } +} + +static int iss_get_clocks(struct iss_device *iss) +{ + iss->iss_fck = clk_get(iss->dev, "iss_fck"); + if (IS_ERR(iss->iss_fck)) { + dev_err(iss->dev, "Unable to get iss_fck clock info\n"); + iss_put_clocks(iss); + return PTR_ERR(iss->iss_fck); + } + + iss->iss_ctrlclk = clk_get(iss->dev, "iss_ctrlclk"); + if (IS_ERR(iss->iss_ctrlclk)) { + dev_err(iss->dev, "Unable to get iss_ctrlclk clock info\n"); + iss_put_clocks(iss); + return PTR_ERR(iss->iss_fck); + } + + return 0; +} + +/* + * omap4iss_get - Acquire the ISS resource. + * + * Initializes the clocks for the first acquire. + * + * Increment the reference count on the ISS. If the first reference is taken, + * enable clocks and power-up all submodules. + * + * Return a pointer to the ISS device structure, or NULL if an error occurred. + */ +struct iss_device *omap4iss_get(struct iss_device *iss) +{ + struct iss_device *__iss = iss; + + if (iss == NULL) + return NULL; + + mutex_lock(&iss->iss_mutex); + if (iss->ref_count > 0) + goto out; + + if (iss_enable_clocks(iss) < 0) { + __iss = NULL; + goto out; + } + + iss_enable_interrupts(iss); + +out: + if (__iss != NULL) + iss->ref_count++; + mutex_unlock(&iss->iss_mutex); + + return __iss; +} + +/* + * omap4iss_put - Release the ISS + * + * Decrement the reference count on the ISS. If the last reference is released, + * power-down all submodules, disable clocks and free temporary buffers. + */ +void omap4iss_put(struct iss_device *iss) +{ + if (iss == NULL) + return; + + mutex_lock(&iss->iss_mutex); + BUG_ON(iss->ref_count == 0); + if (--iss->ref_count == 0) { + iss_disable_interrupts(iss); + /* Reset the ISS if an entity has failed to stop. This is the + * only way to recover from such conditions, although it would + * be worth investigating whether resetting the ISP only can't + * fix the problem in some cases. + */ + if (iss->crashed) + iss_reset(iss); + iss_disable_clocks(iss); + } + mutex_unlock(&iss->iss_mutex); +} + +static int iss_map_mem_resource(struct platform_device *pdev, + struct iss_device *iss, + enum iss_mem_resources res) +{ + struct resource *mem; + + /* request the mem region for the camera registers */ + + mem = platform_get_resource(pdev, IORESOURCE_MEM, res); + if (!mem) { + dev_err(iss->dev, "no mem resource?\n"); + return -ENODEV; + } + + if (!request_mem_region(mem->start, resource_size(mem), pdev->name)) { + dev_err(iss->dev, + "cannot reserve camera register I/O region\n"); + return -ENODEV; + } + iss->res[res] = mem; + + /* map the region */ + iss->regs[res] = ioremap_nocache(mem->start, resource_size(mem)); + if (!iss->regs[res]) { + dev_err(iss->dev, "cannot map camera register I/O region\n"); + return -ENODEV; + } + + return 0; +} + +static void iss_unregister_entities(struct iss_device *iss) +{ + omap4iss_resizer_unregister_entities(&iss->resizer); + omap4iss_ipipe_unregister_entities(&iss->ipipe); + omap4iss_ipipeif_unregister_entities(&iss->ipipeif); + omap4iss_csi2_unregister_entities(&iss->csi2a); + omap4iss_csi2_unregister_entities(&iss->csi2b); + + v4l2_device_unregister(&iss->v4l2_dev); + media_device_unregister(&iss->media_dev); +} + +/* + * iss_register_subdev_group - Register a group of subdevices + * @iss: OMAP4 ISS device + * @board_info: I2C subdevs board information array + * + * Register all I2C subdevices in the board_info array. The array must be + * terminated by a NULL entry, and the first entry must be the sensor. + * + * Return a pointer to the sensor media entity if it has been successfully + * registered, or NULL otherwise. + */ +static struct v4l2_subdev * +iss_register_subdev_group(struct iss_device *iss, + struct iss_subdev_i2c_board_info *board_info) +{ + struct v4l2_subdev *sensor = NULL; + unsigned int first; + + if (board_info->board_info == NULL) + return NULL; + + for (first = 1; board_info->board_info; ++board_info, first = 0) { + struct v4l2_subdev *subdev; + struct i2c_adapter *adapter; + + adapter = i2c_get_adapter(board_info->i2c_adapter_id); + if (adapter == NULL) { + dev_err(iss->dev, + "%s: Unable to get I2C adapter %d for device %s\n", + __func__, board_info->i2c_adapter_id, + board_info->board_info->type); + continue; + } + + subdev = v4l2_i2c_new_subdev_board(&iss->v4l2_dev, adapter, + board_info->board_info, NULL); + if (subdev == NULL) { + dev_err(iss->dev, "%s: Unable to register subdev %s\n", + __func__, board_info->board_info->type); + continue; + } + + if (first) + sensor = subdev; + } + + return sensor; +} + +static int iss_register_entities(struct iss_device *iss) +{ + struct iss_platform_data *pdata = iss->pdata; + struct iss_v4l2_subdevs_group *subdevs; + int ret; + + iss->media_dev.dev = iss->dev; + strlcpy(iss->media_dev.model, "TI OMAP4 ISS", + sizeof(iss->media_dev.model)); + iss->media_dev.hw_revision = iss->revision; + iss->media_dev.link_notify = iss_pipeline_link_notify; + ret = media_device_register(&iss->media_dev); + if (ret < 0) { + dev_err(iss->dev, "%s: Media device registration failed (%d)\n", + __func__, ret); + return ret; + } + + iss->v4l2_dev.mdev = &iss->media_dev; + ret = v4l2_device_register(iss->dev, &iss->v4l2_dev); + if (ret < 0) { + dev_err(iss->dev, "%s: V4L2 device registration failed (%d)\n", + __func__, ret); + goto done; + } + + /* Register internal entities */ + ret = omap4iss_csi2_register_entities(&iss->csi2a, &iss->v4l2_dev); + if (ret < 0) + goto done; + + ret = omap4iss_csi2_register_entities(&iss->csi2b, &iss->v4l2_dev); + if (ret < 0) + goto done; + + ret = omap4iss_ipipeif_register_entities(&iss->ipipeif, &iss->v4l2_dev); + if (ret < 0) + goto done; + + ret = omap4iss_ipipe_register_entities(&iss->ipipe, &iss->v4l2_dev); + if (ret < 0) + goto done; + + ret = omap4iss_resizer_register_entities(&iss->resizer, &iss->v4l2_dev); + if (ret < 0) + goto done; + + /* Register external entities */ + for (subdevs = pdata->subdevs; subdevs && subdevs->subdevs; ++subdevs) { + struct v4l2_subdev *sensor; + struct media_entity *input; + unsigned int flags; + unsigned int pad; + + sensor = iss_register_subdev_group(iss, subdevs->subdevs); + if (sensor == NULL) + continue; + + sensor->host_priv = subdevs; + + /* Connect the sensor to the correct interface module. + * CSI2a receiver through CSIPHY1, or + * CSI2b receiver through CSIPHY2 + */ + switch (subdevs->interface) { + case ISS_INTERFACE_CSI2A_PHY1: + input = &iss->csi2a.subdev.entity; + pad = CSI2_PAD_SINK; + flags = MEDIA_LNK_FL_IMMUTABLE + | MEDIA_LNK_FL_ENABLED; + break; + + case ISS_INTERFACE_CSI2B_PHY2: + input = &iss->csi2b.subdev.entity; + pad = CSI2_PAD_SINK; + flags = MEDIA_LNK_FL_IMMUTABLE + | MEDIA_LNK_FL_ENABLED; + break; + + default: + dev_err(iss->dev, "%s: invalid interface type %u\n", + __func__, subdevs->interface); + ret = -EINVAL; + goto done; + } + + ret = media_entity_create_link(&sensor->entity, 0, input, pad, + flags); + if (ret < 0) + goto done; + } + + ret = v4l2_device_register_subdev_nodes(&iss->v4l2_dev); + +done: + if (ret < 0) + iss_unregister_entities(iss); + + return ret; +} + +static void iss_cleanup_modules(struct iss_device *iss) +{ + omap4iss_csi2_cleanup(iss); + omap4iss_ipipeif_cleanup(iss); + omap4iss_ipipe_cleanup(iss); + omap4iss_resizer_cleanup(iss); +} + +static int iss_initialize_modules(struct iss_device *iss) +{ + int ret; + + ret = omap4iss_csiphy_init(iss); + if (ret < 0) { + dev_err(iss->dev, "CSI PHY initialization failed\n"); + goto error_csiphy; + } + + ret = omap4iss_csi2_init(iss); + if (ret < 0) { + dev_err(iss->dev, "CSI2 initialization failed\n"); + goto error_csi2; + } + + ret = omap4iss_ipipeif_init(iss); + if (ret < 0) { + dev_err(iss->dev, "ISP IPIPEIF initialization failed\n"); + goto error_ipipeif; + } + + ret = omap4iss_ipipe_init(iss); + if (ret < 0) { + dev_err(iss->dev, "ISP IPIPE initialization failed\n"); + goto error_ipipe; + } + + ret = omap4iss_resizer_init(iss); + if (ret < 0) { + dev_err(iss->dev, "ISP RESIZER initialization failed\n"); + goto error_resizer; + } + + /* Connect the submodules. */ + ret = media_entity_create_link( + &iss->csi2a.subdev.entity, CSI2_PAD_SOURCE, + &iss->ipipeif.subdev.entity, IPIPEIF_PAD_SINK, 0); + if (ret < 0) + goto error_link; + + ret = media_entity_create_link( + &iss->csi2b.subdev.entity, CSI2_PAD_SOURCE, + &iss->ipipeif.subdev.entity, IPIPEIF_PAD_SINK, 0); + if (ret < 0) + goto error_link; + + ret = media_entity_create_link( + &iss->ipipeif.subdev.entity, IPIPEIF_PAD_SOURCE_VP, + &iss->resizer.subdev.entity, RESIZER_PAD_SINK, 0); + if (ret < 0) + goto error_link; + + ret = media_entity_create_link( + &iss->ipipeif.subdev.entity, IPIPEIF_PAD_SOURCE_VP, + &iss->ipipe.subdev.entity, IPIPE_PAD_SINK, 0); + if (ret < 0) + goto error_link; + + ret = media_entity_create_link( + &iss->ipipe.subdev.entity, IPIPE_PAD_SOURCE_VP, + &iss->resizer.subdev.entity, RESIZER_PAD_SINK, 0); + if (ret < 0) + goto error_link; + + return 0; + +error_link: + omap4iss_resizer_cleanup(iss); +error_resizer: + omap4iss_ipipe_cleanup(iss); +error_ipipe: + omap4iss_ipipeif_cleanup(iss); +error_ipipeif: + omap4iss_csi2_cleanup(iss); +error_csi2: +error_csiphy: + return ret; +} + +static int iss_probe(struct platform_device *pdev) +{ + struct iss_platform_data *pdata = pdev->dev.platform_data; + struct iss_device *iss; + unsigned int i; + int ret; + + if (pdata == NULL) + return -EINVAL; + + iss = kzalloc(sizeof(*iss), GFP_KERNEL); + if (!iss) { + dev_err(&pdev->dev, "Could not allocate memory\n"); + return -ENOMEM; + } + + mutex_init(&iss->iss_mutex); + + iss->dev = &pdev->dev; + iss->pdata = pdata; + + iss->raw_dmamask = DMA_BIT_MASK(32); + iss->dev->dma_mask = &iss->raw_dmamask; + iss->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + platform_set_drvdata(pdev, iss); + + /* Clocks */ + ret = iss_map_mem_resource(pdev, iss, OMAP4_ISS_MEM_TOP); + if (ret < 0) + goto error; + + ret = iss_get_clocks(iss); + if (ret < 0) + goto error; + + if (omap4iss_get(iss) == NULL) + goto error; + + ret = iss_reset(iss); + if (ret < 0) + goto error_iss; + + iss->revision = iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_HL_REVISION); + dev_info(iss->dev, "Revision %08x found\n", iss->revision); + + for (i = 1; i < OMAP4_ISS_MEM_LAST; i++) { + ret = iss_map_mem_resource(pdev, iss, i); + if (ret) + goto error_iss; + } + + /* Configure BTE BW_LIMITER field to max recommended value (1 GB) */ + iss_reg_update(iss, OMAP4_ISS_MEM_BTE, BTE_CTRL, + BTE_CTRL_BW_LIMITER_MASK, + 18 << BTE_CTRL_BW_LIMITER_SHIFT); + + /* Perform ISP reset */ + ret = omap4iss_subclk_enable(iss, OMAP4_ISS_SUBCLK_ISP); + if (ret < 0) + goto error_iss; + + ret = iss_isp_reset(iss); + if (ret < 0) + goto error_iss; + + dev_info(iss->dev, "ISP Revision %08x found\n", + iss_reg_read(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_REVISION)); + + /* Interrupt */ + iss->irq_num = platform_get_irq(pdev, 0); + if (iss->irq_num <= 0) { + dev_err(iss->dev, "No IRQ resource\n"); + ret = -ENODEV; + goto error_iss; + } + + if (request_irq(iss->irq_num, iss_isr, IRQF_SHARED, "OMAP4 ISS", iss)) { + dev_err(iss->dev, "Unable to request IRQ\n"); + ret = -EINVAL; + goto error_iss; + } + + /* Entities */ + ret = iss_initialize_modules(iss); + if (ret < 0) + goto error_irq; + + ret = iss_register_entities(iss); + if (ret < 0) + goto error_modules; + + omap4iss_put(iss); + + return 0; + +error_modules: + iss_cleanup_modules(iss); +error_irq: + free_irq(iss->irq_num, iss); +error_iss: + omap4iss_put(iss); +error: + iss_put_clocks(iss); + + for (i = 0; i < OMAP4_ISS_MEM_LAST; i++) { + if (iss->regs[i]) { + iounmap(iss->regs[i]); + iss->regs[i] = NULL; + } + + if (iss->res[i]) { + release_mem_region(iss->res[i]->start, + resource_size(iss->res[i])); + iss->res[i] = NULL; + } + } + platform_set_drvdata(pdev, NULL); + + mutex_destroy(&iss->iss_mutex); + kfree(iss); + + return ret; +} + +static int iss_remove(struct platform_device *pdev) +{ + struct iss_device *iss = platform_get_drvdata(pdev); + unsigned int i; + + iss_unregister_entities(iss); + iss_cleanup_modules(iss); + + free_irq(iss->irq_num, iss); + iss_put_clocks(iss); + + for (i = 0; i < OMAP4_ISS_MEM_LAST; i++) { + if (iss->regs[i]) { + iounmap(iss->regs[i]); + iss->regs[i] = NULL; + } + + if (iss->res[i]) { + release_mem_region(iss->res[i]->start, + resource_size(iss->res[i])); + iss->res[i] = NULL; + } + } + + kfree(iss); + + return 0; +} + +static struct platform_device_id omap4iss_id_table[] = { + { "omap4iss", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(platform, omap4iss_id_table); + +static struct platform_driver iss_driver = { + .probe = iss_probe, + .remove = iss_remove, + .id_table = omap4iss_id_table, + .driver = { + .owner = THIS_MODULE, + .name = "omap4iss", + }, +}; + +module_platform_driver(iss_driver); + +MODULE_DESCRIPTION("TI OMAP4 ISS driver"); +MODULE_AUTHOR("Sergio Aguirre <sergio.a.aguirre@gmail.com>"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(ISS_VIDEO_DRIVER_VERSION); diff --git a/drivers/staging/media/omap4iss/iss.h b/drivers/staging/media/omap4iss/iss.h new file mode 100644 index 000000000000..346db9233171 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss.h @@ -0,0 +1,236 @@ +/* + * TI OMAP4 ISS V4L2 Driver + * + * Copyright (C) 2012 Texas Instruments. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.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 _OMAP4_ISS_H_ +#define _OMAP4_ISS_H_ + +#include <media/v4l2-device.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/wait.h> + +#include <media/omap4iss.h> + +#include "iss_regs.h" +#include "iss_csiphy.h" +#include "iss_csi2.h" +#include "iss_ipipeif.h" +#include "iss_ipipe.h" +#include "iss_resizer.h" + +#define to_iss_device(ptr_module) \ + container_of(ptr_module, struct iss_device, ptr_module) +#define to_device(ptr_module) \ + (to_iss_device(ptr_module)->dev) + +enum iss_mem_resources { + OMAP4_ISS_MEM_TOP, + OMAP4_ISS_MEM_CSI2_A_REGS1, + OMAP4_ISS_MEM_CAMERARX_CORE1, + OMAP4_ISS_MEM_CSI2_B_REGS1, + OMAP4_ISS_MEM_CAMERARX_CORE2, + OMAP4_ISS_MEM_BTE, + OMAP4_ISS_MEM_ISP_SYS1, + OMAP4_ISS_MEM_ISP_RESIZER, + OMAP4_ISS_MEM_ISP_IPIPE, + OMAP4_ISS_MEM_ISP_ISIF, + OMAP4_ISS_MEM_ISP_IPIPEIF, + OMAP4_ISS_MEM_LAST, +}; + +enum iss_subclk_resource { + OMAP4_ISS_SUBCLK_SIMCOP = (1 << 0), + OMAP4_ISS_SUBCLK_ISP = (1 << 1), + OMAP4_ISS_SUBCLK_CSI2_A = (1 << 2), + OMAP4_ISS_SUBCLK_CSI2_B = (1 << 3), + OMAP4_ISS_SUBCLK_CCP2 = (1 << 4), +}; + +enum iss_isp_subclk_resource { + OMAP4_ISS_ISP_SUBCLK_BL = (1 << 0), + OMAP4_ISS_ISP_SUBCLK_ISIF = (1 << 1), + OMAP4_ISS_ISP_SUBCLK_H3A = (1 << 2), + OMAP4_ISS_ISP_SUBCLK_RSZ = (1 << 3), + OMAP4_ISS_ISP_SUBCLK_IPIPE = (1 << 4), + OMAP4_ISS_ISP_SUBCLK_IPIPEIF = (1 << 5), +}; + +/* + * struct iss_reg - Structure for ISS register values. + * @reg: 32-bit Register address. + * @val: 32-bit Register value. + */ +struct iss_reg { + enum iss_mem_resources mmio_range; + u32 reg; + u32 val; +}; + +/* + * struct iss_device - ISS device structure. + * @crashed: Bitmask of crashed entities (indexed by entity ID) + */ +struct iss_device { + struct v4l2_device v4l2_dev; + struct media_device media_dev; + struct device *dev; + u32 revision; + + /* platform HW resources */ + struct iss_platform_data *pdata; + unsigned int irq_num; + + struct resource *res[OMAP4_ISS_MEM_LAST]; + void __iomem *regs[OMAP4_ISS_MEM_LAST]; + + u64 raw_dmamask; + + struct mutex iss_mutex; /* For handling ref_count field */ + bool crashed; + int has_context; + int ref_count; + + struct clk *iss_fck; + struct clk *iss_ctrlclk; + + /* ISS modules */ + struct iss_csi2_device csi2a; + struct iss_csi2_device csi2b; + struct iss_csiphy csiphy1; + struct iss_csiphy csiphy2; + struct iss_ipipeif_device ipipeif; + struct iss_ipipe_device ipipe; + struct iss_resizer_device resizer; + + unsigned int subclk_resources; + unsigned int isp_subclk_resources; +}; + +#define v4l2_dev_to_iss_device(dev) \ + container_of(dev, struct iss_device, v4l2_dev) + +int omap4iss_get_external_info(struct iss_pipeline *pipe, + struct media_link *link); + +int omap4iss_module_sync_idle(struct media_entity *me, wait_queue_head_t *wait, + atomic_t *stopping); + +int omap4iss_module_sync_is_stopping(wait_queue_head_t *wait, + atomic_t *stopping); + +int omap4iss_pipeline_set_stream(struct iss_pipeline *pipe, + enum iss_pipeline_stream_state state); +void omap4iss_pipeline_cancel_stream(struct iss_pipeline *pipe); + +void omap4iss_configure_bridge(struct iss_device *iss, + enum ipipeif_input_entity input); + +struct iss_device *omap4iss_get(struct iss_device *iss); +void omap4iss_put(struct iss_device *iss); +int omap4iss_subclk_enable(struct iss_device *iss, + enum iss_subclk_resource res); +int omap4iss_subclk_disable(struct iss_device *iss, + enum iss_subclk_resource res); +void omap4iss_isp_subclk_enable(struct iss_device *iss, + enum iss_isp_subclk_resource res); +void omap4iss_isp_subclk_disable(struct iss_device *iss, + enum iss_isp_subclk_resource res); + +int omap4iss_pipeline_pm_use(struct media_entity *entity, int use); + +int omap4iss_register_entities(struct platform_device *pdev, + struct v4l2_device *v4l2_dev); +void omap4iss_unregister_entities(struct platform_device *pdev); + +/* + * iss_reg_read - Read the value of an OMAP4 ISS register + * @iss: the ISS device + * @res: memory resource in which the register is located + * @offset: register offset in the memory resource + * + * Return the register value. + */ +static inline +u32 iss_reg_read(struct iss_device *iss, enum iss_mem_resources res, + u32 offset) +{ + return readl(iss->regs[res] + offset); +} + +/* + * iss_reg_write - Write a value to an OMAP4 ISS register + * @iss: the ISS device + * @res: memory resource in which the register is located + * @offset: register offset in the memory resource + * @value: value to be written + */ +static inline +void iss_reg_write(struct iss_device *iss, enum iss_mem_resources res, + u32 offset, u32 value) +{ + writel(value, iss->regs[res] + offset); +} + +/* + * iss_reg_clr - Clear bits in an OMAP4 ISS register + * @iss: the ISS device + * @res: memory resource in which the register is located + * @offset: register offset in the memory resource + * @clr: bit mask to be cleared + */ +static inline +void iss_reg_clr(struct iss_device *iss, enum iss_mem_resources res, + u32 offset, u32 clr) +{ + u32 v = iss_reg_read(iss, res, offset); + + iss_reg_write(iss, res, offset, v & ~clr); +} + +/* + * iss_reg_set - Set bits in an OMAP4 ISS register + * @iss: the ISS device + * @res: memory resource in which the register is located + * @offset: register offset in the memory resource + * @set: bit mask to be set + */ +static inline +void iss_reg_set(struct iss_device *iss, enum iss_mem_resources res, + u32 offset, u32 set) +{ + u32 v = iss_reg_read(iss, res, offset); + + iss_reg_write(iss, res, offset, v | set); +} + +/* + * iss_reg_update - Clear and set bits in an OMAP4 ISS register + * @iss: the ISS device + * @res: memory resource in which the register is located + * @offset: register offset in the memory resource + * @clr: bit mask to be cleared + * @set: bit mask to be set + * + * Clear the clr mask first and then set the set mask. + */ +static inline +void iss_reg_update(struct iss_device *iss, enum iss_mem_resources res, + u32 offset, u32 clr, u32 set) +{ + u32 v = iss_reg_read(iss, res, offset); + + iss_reg_write(iss, res, offset, (v & ~clr) | set); +} + +#endif /* _OMAP4_ISS_H_ */ diff --git a/drivers/staging/media/omap4iss/iss_csi2.c b/drivers/staging/media/omap4iss/iss_csi2.c new file mode 100644 index 000000000000..61fc350eb251 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_csi2.c @@ -0,0 +1,1343 @@ +/* + * TI OMAP4 ISS V4L2 Driver - CSI PHY module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.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/delay.h> +#include <media/v4l2-common.h> +#include <linux/v4l2-mediabus.h> +#include <linux/mm.h> + +#include "iss.h" +#include "iss_regs.h" +#include "iss_csi2.h" + +/* + * csi2_if_enable - Enable CSI2 Receiver interface. + * @enable: enable flag + * + */ +static void csi2_if_enable(struct iss_csi2_device *csi2, u8 enable) +{ + struct iss_csi2_ctrl_cfg *currctrl = &csi2->ctrl; + + iss_reg_update(csi2->iss, csi2->regs1, CSI2_CTRL, CSI2_CTRL_IF_EN, + enable ? CSI2_CTRL_IF_EN : 0); + + currctrl->if_enable = enable; +} + +/* + * csi2_recv_config - CSI2 receiver module configuration. + * @currctrl: iss_csi2_ctrl_cfg structure + * + */ +static void csi2_recv_config(struct iss_csi2_device *csi2, + struct iss_csi2_ctrl_cfg *currctrl) +{ + u32 reg = 0; + + if (currctrl->frame_mode) + reg |= CSI2_CTRL_FRAME; + else + reg &= ~CSI2_CTRL_FRAME; + + if (currctrl->vp_clk_enable) + reg |= CSI2_CTRL_VP_CLK_EN; + else + reg &= ~CSI2_CTRL_VP_CLK_EN; + + if (currctrl->vp_only_enable) + reg |= CSI2_CTRL_VP_ONLY_EN; + else + reg &= ~CSI2_CTRL_VP_ONLY_EN; + + reg &= ~CSI2_CTRL_VP_OUT_CTRL_MASK; + reg |= currctrl->vp_out_ctrl << CSI2_CTRL_VP_OUT_CTRL_SHIFT; + + if (currctrl->ecc_enable) + reg |= CSI2_CTRL_ECC_EN; + else + reg &= ~CSI2_CTRL_ECC_EN; + + /* + * Set MFlag assertion boundaries to: + * Low: 4/8 of FIFO size + * High: 6/8 of FIFO size + */ + reg &= ~(CSI2_CTRL_MFLAG_LEVH_MASK | CSI2_CTRL_MFLAG_LEVL_MASK); + reg |= (2 << CSI2_CTRL_MFLAG_LEVH_SHIFT) | + (4 << CSI2_CTRL_MFLAG_LEVL_SHIFT); + + /* Generation of 16x64-bit bursts (Recommended) */ + reg |= CSI2_CTRL_BURST_SIZE_EXPAND; + + /* Do Non-Posted writes (Recommended) */ + reg |= CSI2_CTRL_NON_POSTED_WRITE; + + /* + * Enforce Little endian for all formats, including: + * YUV4:2:2 8-bit and YUV4:2:0 Legacy + */ + reg |= CSI2_CTRL_ENDIANNESS; + + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTRL, reg); +} + +static const unsigned int csi2_input_fmts[] = { + V4L2_MBUS_FMT_SGRBG10_1X10, + V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8, + V4L2_MBUS_FMT_SRGGB10_1X10, + V4L2_MBUS_FMT_SRGGB10_DPCM8_1X8, + V4L2_MBUS_FMT_SBGGR10_1X10, + V4L2_MBUS_FMT_SBGGR10_DPCM8_1X8, + V4L2_MBUS_FMT_SGBRG10_1X10, + V4L2_MBUS_FMT_SGBRG10_DPCM8_1X8, + V4L2_MBUS_FMT_SBGGR8_1X8, + V4L2_MBUS_FMT_SGBRG8_1X8, + V4L2_MBUS_FMT_SGRBG8_1X8, + V4L2_MBUS_FMT_SRGGB8_1X8, + V4L2_MBUS_FMT_UYVY8_1X16, + V4L2_MBUS_FMT_YUYV8_1X16, +}; + +/* To set the format on the CSI2 requires a mapping function that takes + * the following inputs: + * - 3 different formats (at this time) + * - 2 destinations (mem, vp+mem) (vp only handled separately) + * - 2 decompression options (on, off) + * Output should be CSI2 frame format code + * Array indices as follows: [format][dest][decompr] + * Not all combinations are valid. 0 means invalid. + */ +static const u16 __csi2_fmt_map[][2][2] = { + /* RAW10 formats */ + { + /* Output to memory */ + { + /* No DPCM decompression */ + CSI2_PIX_FMT_RAW10_EXP16, + /* DPCM decompression */ + 0, + }, + /* Output to both */ + { + /* No DPCM decompression */ + CSI2_PIX_FMT_RAW10_EXP16_VP, + /* DPCM decompression */ + 0, + }, + }, + /* RAW10 DPCM8 formats */ + { + /* Output to memory */ + { + /* No DPCM decompression */ + CSI2_USERDEF_8BIT_DATA1, + /* DPCM decompression */ + CSI2_USERDEF_8BIT_DATA1_DPCM10, + }, + /* Output to both */ + { + /* No DPCM decompression */ + CSI2_PIX_FMT_RAW8_VP, + /* DPCM decompression */ + CSI2_USERDEF_8BIT_DATA1_DPCM10_VP, + }, + }, + /* RAW8 formats */ + { + /* Output to memory */ + { + /* No DPCM decompression */ + CSI2_PIX_FMT_RAW8, + /* DPCM decompression */ + 0, + }, + /* Output to both */ + { + /* No DPCM decompression */ + CSI2_PIX_FMT_RAW8_VP, + /* DPCM decompression */ + 0, + }, + }, + /* YUV422 formats */ + { + /* Output to memory */ + { + /* No DPCM decompression */ + CSI2_PIX_FMT_YUV422_8BIT, + /* DPCM decompression */ + 0, + }, + /* Output to both */ + { + /* No DPCM decompression */ + CSI2_PIX_FMT_YUV422_8BIT_VP16, + /* DPCM decompression */ + 0, + }, + }, +}; + +/* + * csi2_ctx_map_format - Map CSI2 sink media bus format to CSI2 format ID + * @csi2: ISS CSI2 device + * + * Returns CSI2 physical format id + */ +static u16 csi2_ctx_map_format(struct iss_csi2_device *csi2) +{ + const struct v4l2_mbus_framefmt *fmt = &csi2->formats[CSI2_PAD_SINK]; + int fmtidx, destidx; + + switch (fmt->code) { + case V4L2_MBUS_FMT_SGRBG10_1X10: + case V4L2_MBUS_FMT_SRGGB10_1X10: + case V4L2_MBUS_FMT_SBGGR10_1X10: + case V4L2_MBUS_FMT_SGBRG10_1X10: + fmtidx = 0; + break; + case V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8: + case V4L2_MBUS_FMT_SRGGB10_DPCM8_1X8: + case V4L2_MBUS_FMT_SBGGR10_DPCM8_1X8: + case V4L2_MBUS_FMT_SGBRG10_DPCM8_1X8: + fmtidx = 1; + break; + case V4L2_MBUS_FMT_SBGGR8_1X8: + case V4L2_MBUS_FMT_SGBRG8_1X8: + case V4L2_MBUS_FMT_SGRBG8_1X8: + case V4L2_MBUS_FMT_SRGGB8_1X8: + fmtidx = 2; + break; + case V4L2_MBUS_FMT_UYVY8_1X16: + case V4L2_MBUS_FMT_YUYV8_1X16: + fmtidx = 3; + break; + default: + WARN(1, KERN_ERR "CSI2: pixel format %08x unsupported!\n", + fmt->code); + return 0; + } + + if (!(csi2->output & CSI2_OUTPUT_IPIPEIF) && + !(csi2->output & CSI2_OUTPUT_MEMORY)) { + /* Neither output enabled is a valid combination */ + return CSI2_PIX_FMT_OTHERS; + } + + /* If we need to skip frames at the beginning of the stream disable the + * video port to avoid sending the skipped frames to the IPIPEIF. + */ + destidx = csi2->frame_skip ? 0 : !!(csi2->output & CSI2_OUTPUT_IPIPEIF); + + return __csi2_fmt_map[fmtidx][destidx][csi2->dpcm_decompress]; +} + +/* + * csi2_set_outaddr - Set memory address to save output image + * @csi2: Pointer to ISS CSI2a device. + * @addr: 32-bit memory address aligned on 32 byte boundary. + * + * Sets the memory address where the output will be saved. + * + * Returns 0 if successful, or -EINVAL if the address is not in the 32 byte + * boundary. + */ +static void csi2_set_outaddr(struct iss_csi2_device *csi2, u32 addr) +{ + struct iss_csi2_ctx_cfg *ctx = &csi2->contexts[0]; + + ctx->ping_addr = addr; + ctx->pong_addr = addr; + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_PING_ADDR(ctx->ctxnum), + ctx->ping_addr); + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_PONG_ADDR(ctx->ctxnum), + ctx->pong_addr); +} + +/* + * is_usr_def_mapping - Checks whether USER_DEF_MAPPING should + * be enabled by CSI2. + * @format_id: mapped format id + * + */ +static inline int is_usr_def_mapping(u32 format_id) +{ + return (format_id & 0xf0) == 0x40 ? 1 : 0; +} + +/* + * csi2_ctx_enable - Enable specified CSI2 context + * @ctxnum: Context number, valid between 0 and 7 values. + * @enable: enable + * + */ +static void csi2_ctx_enable(struct iss_csi2_device *csi2, u8 ctxnum, u8 enable) +{ + struct iss_csi2_ctx_cfg *ctx = &csi2->contexts[ctxnum]; + u32 reg; + + reg = iss_reg_read(csi2->iss, csi2->regs1, CSI2_CTX_CTRL1(ctxnum)); + + if (enable) { + unsigned int skip = 0; + + if (csi2->frame_skip) + skip = csi2->frame_skip; + else if (csi2->output & CSI2_OUTPUT_MEMORY) + skip = 1; + + reg &= ~CSI2_CTX_CTRL1_COUNT_MASK; + reg |= CSI2_CTX_CTRL1_COUNT_UNLOCK + | (skip << CSI2_CTX_CTRL1_COUNT_SHIFT) + | CSI2_CTX_CTRL1_CTX_EN; + } else { + reg &= ~CSI2_CTX_CTRL1_CTX_EN; + } + + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_CTRL1(ctxnum), reg); + ctx->enabled = enable; +} + +/* + * csi2_ctx_config - CSI2 context configuration. + * @ctx: context configuration + * + */ +static void csi2_ctx_config(struct iss_csi2_device *csi2, + struct iss_csi2_ctx_cfg *ctx) +{ + u32 reg; + + /* Set up CSI2_CTx_CTRL1 */ + if (ctx->eof_enabled) + reg = CSI2_CTX_CTRL1_EOF_EN; + + if (ctx->eol_enabled) + reg |= CSI2_CTX_CTRL1_EOL_EN; + + if (ctx->checksum_enabled) + reg |= CSI2_CTX_CTRL1_CS_EN; + + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_CTRL1(ctx->ctxnum), reg); + + /* Set up CSI2_CTx_CTRL2 */ + reg = ctx->virtual_id << CSI2_CTX_CTRL2_VIRTUAL_ID_SHIFT; + reg |= ctx->format_id << CSI2_CTX_CTRL2_FORMAT_SHIFT; + + if (ctx->dpcm_decompress && ctx->dpcm_predictor) + reg |= CSI2_CTX_CTRL2_DPCM_PRED; + + if (is_usr_def_mapping(ctx->format_id)) + reg |= 2 << CSI2_CTX_CTRL2_USER_DEF_MAP_SHIFT; + + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_CTRL2(ctx->ctxnum), reg); + + /* Set up CSI2_CTx_CTRL3 */ + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_CTRL3(ctx->ctxnum), + ctx->alpha << CSI2_CTX_CTRL3_ALPHA_SHIFT); + + /* Set up CSI2_CTx_DAT_OFST */ + iss_reg_update(csi2->iss, csi2->regs1, CSI2_CTX_DAT_OFST(ctx->ctxnum), + CSI2_CTX_DAT_OFST_MASK, ctx->data_offset); + + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_PING_ADDR(ctx->ctxnum), + ctx->ping_addr); + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_PONG_ADDR(ctx->ctxnum), + ctx->pong_addr); +} + +/* + * csi2_timing_config - CSI2 timing configuration. + * @timing: csi2_timing_cfg structure + */ +static void csi2_timing_config(struct iss_csi2_device *csi2, + struct iss_csi2_timing_cfg *timing) +{ + u32 reg; + + reg = iss_reg_read(csi2->iss, csi2->regs1, CSI2_TIMING); + + if (timing->force_rx_mode) + reg |= CSI2_TIMING_FORCE_RX_MODE_IO1; + else + reg &= ~CSI2_TIMING_FORCE_RX_MODE_IO1; + + if (timing->stop_state_16x) + reg |= CSI2_TIMING_STOP_STATE_X16_IO1; + else + reg &= ~CSI2_TIMING_STOP_STATE_X16_IO1; + + if (timing->stop_state_4x) + reg |= CSI2_TIMING_STOP_STATE_X4_IO1; + else + reg &= ~CSI2_TIMING_STOP_STATE_X4_IO1; + + reg &= ~CSI2_TIMING_STOP_STATE_COUNTER_IO1_MASK; + reg |= timing->stop_state_counter << + CSI2_TIMING_STOP_STATE_COUNTER_IO1_SHIFT; + + iss_reg_write(csi2->iss, csi2->regs1, CSI2_TIMING, reg); +} + +/* + * csi2_irq_ctx_set - Enables CSI2 Context IRQs. + * @enable: Enable/disable CSI2 Context interrupts + */ +static void csi2_irq_ctx_set(struct iss_csi2_device *csi2, int enable) +{ + u32 reg = CSI2_CTX_IRQ_FE; + int i; + + if (csi2->use_fs_irq) + reg |= CSI2_CTX_IRQ_FS; + + for (i = 0; i < 8; i++) { + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_IRQSTATUS(i), + reg); + if (enable) + iss_reg_set(csi2->iss, csi2->regs1, + CSI2_CTX_IRQENABLE(i), reg); + else + iss_reg_clr(csi2->iss, csi2->regs1, + CSI2_CTX_IRQENABLE(i), reg); + } +} + +/* + * csi2_irq_complexio1_set - Enables CSI2 ComplexIO IRQs. + * @enable: Enable/disable CSI2 ComplexIO #1 interrupts + */ +static void csi2_irq_complexio1_set(struct iss_csi2_device *csi2, int enable) +{ + u32 reg; + reg = CSI2_COMPLEXIO_IRQ_STATEALLULPMEXIT | + CSI2_COMPLEXIO_IRQ_STATEALLULPMENTER | + CSI2_COMPLEXIO_IRQ_STATEULPM5 | + CSI2_COMPLEXIO_IRQ_ERRCONTROL5 | + CSI2_COMPLEXIO_IRQ_ERRESC5 | + CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS5 | + CSI2_COMPLEXIO_IRQ_ERRSOTHS5 | + CSI2_COMPLEXIO_IRQ_STATEULPM4 | + CSI2_COMPLEXIO_IRQ_ERRCONTROL4 | + CSI2_COMPLEXIO_IRQ_ERRESC4 | + CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS4 | + CSI2_COMPLEXIO_IRQ_ERRSOTHS4 | + CSI2_COMPLEXIO_IRQ_STATEULPM3 | + CSI2_COMPLEXIO_IRQ_ERRCONTROL3 | + CSI2_COMPLEXIO_IRQ_ERRESC3 | + CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS3 | + CSI2_COMPLEXIO_IRQ_ERRSOTHS3 | + CSI2_COMPLEXIO_IRQ_STATEULPM2 | + CSI2_COMPLEXIO_IRQ_ERRCONTROL2 | + CSI2_COMPLEXIO_IRQ_ERRESC2 | + CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS2 | + CSI2_COMPLEXIO_IRQ_ERRSOTHS2 | + CSI2_COMPLEXIO_IRQ_STATEULPM1 | + CSI2_COMPLEXIO_IRQ_ERRCONTROL1 | + CSI2_COMPLEXIO_IRQ_ERRESC1 | + CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS1 | + CSI2_COMPLEXIO_IRQ_ERRSOTHS1; + iss_reg_write(csi2->iss, csi2->regs1, CSI2_COMPLEXIO_IRQSTATUS, reg); + if (enable) + iss_reg_set(csi2->iss, csi2->regs1, CSI2_COMPLEXIO_IRQENABLE, + reg); + else + iss_reg_write(csi2->iss, csi2->regs1, CSI2_COMPLEXIO_IRQENABLE, + 0); +} + +/* + * csi2_irq_status_set - Enables CSI2 Status IRQs. + * @enable: Enable/disable CSI2 Status interrupts + */ +static void csi2_irq_status_set(struct iss_csi2_device *csi2, int enable) +{ + u32 reg; + reg = CSI2_IRQ_OCP_ERR | + CSI2_IRQ_SHORT_PACKET | + CSI2_IRQ_ECC_CORRECTION | + CSI2_IRQ_ECC_NO_CORRECTION | + CSI2_IRQ_COMPLEXIO_ERR | + CSI2_IRQ_FIFO_OVF | + CSI2_IRQ_CONTEXT0; + iss_reg_write(csi2->iss, csi2->regs1, CSI2_IRQSTATUS, reg); + if (enable) + iss_reg_set(csi2->iss, csi2->regs1, CSI2_IRQENABLE, reg); + else + iss_reg_write(csi2->iss, csi2->regs1, CSI2_IRQENABLE, 0); +} + +/* + * omap4iss_csi2_reset - Resets the CSI2 module. + * + * Must be called with the phy lock held. + * + * Returns 0 if successful, or -EBUSY if power command didn't respond. + */ +int omap4iss_csi2_reset(struct iss_csi2_device *csi2) +{ + u8 soft_reset_retries = 0; + u32 reg; + int i; + + if (!csi2->available) + return -ENODEV; + + if (csi2->phy->phy_in_use) + return -EBUSY; + + iss_reg_set(csi2->iss, csi2->regs1, CSI2_SYSCONFIG, + CSI2_SYSCONFIG_SOFT_RESET); + + do { + reg = iss_reg_read(csi2->iss, csi2->regs1, CSI2_SYSSTATUS) + & CSI2_SYSSTATUS_RESET_DONE; + if (reg == CSI2_SYSSTATUS_RESET_DONE) + break; + soft_reset_retries++; + if (soft_reset_retries < 5) + usleep_range(100, 100); + } while (soft_reset_retries < 5); + + if (soft_reset_retries == 5) { + dev_err(csi2->iss->dev, + "CSI2: Soft reset try count exceeded!\n"); + return -EBUSY; + } + + iss_reg_set(csi2->iss, csi2->regs1, CSI2_COMPLEXIO_CFG, + CSI2_COMPLEXIO_CFG_RESET_CTRL); + + i = 100; + do { + reg = iss_reg_read(csi2->iss, csi2->phy->phy_regs, REGISTER1) + & REGISTER1_RESET_DONE_CTRLCLK; + if (reg == REGISTER1_RESET_DONE_CTRLCLK) + break; + usleep_range(100, 100); + } while (--i > 0); + + if (i == 0) { + dev_err(csi2->iss->dev, + "CSI2: Reset for CSI2_96M_FCLK domain Failed!\n"); + return -EBUSY; + } + + iss_reg_update(csi2->iss, csi2->regs1, CSI2_SYSCONFIG, + CSI2_SYSCONFIG_MSTANDBY_MODE_MASK | + CSI2_SYSCONFIG_AUTO_IDLE, + CSI2_SYSCONFIG_MSTANDBY_MODE_NO); + + return 0; +} + +static int csi2_configure(struct iss_csi2_device *csi2) +{ + const struct iss_v4l2_subdevs_group *pdata; + struct iss_csi2_timing_cfg *timing = &csi2->timing[0]; + struct v4l2_subdev *sensor; + struct media_pad *pad; + + /* + * CSI2 fields that can be updated while the context has + * been enabled or the interface has been enabled are not + * updated dynamically currently. So we do not allow to + * reconfigure if either has been enabled + */ + if (csi2->contexts[0].enabled || csi2->ctrl.if_enable) + return -EBUSY; + + pad = media_entity_remote_pad(&csi2->pads[CSI2_PAD_SINK]); + sensor = media_entity_to_v4l2_subdev(pad->entity); + pdata = sensor->host_priv; + + csi2->frame_skip = 0; + v4l2_subdev_call(sensor, sensor, g_skip_frames, &csi2->frame_skip); + + csi2->ctrl.vp_out_ctrl = pdata->bus.csi2.vpclk_div; + csi2->ctrl.frame_mode = ISS_CSI2_FRAME_IMMEDIATE; + csi2->ctrl.ecc_enable = pdata->bus.csi2.crc; + + timing->force_rx_mode = 1; + timing->stop_state_16x = 1; + timing->stop_state_4x = 1; + timing->stop_state_counter = 0x1ff; + + /* + * The CSI2 receiver can't do any format conversion except DPCM + * decompression, so every set_format call configures both pads + * and enables DPCM decompression as a special case: + */ + if (csi2->formats[CSI2_PAD_SINK].code != + csi2->formats[CSI2_PAD_SOURCE].code) + csi2->dpcm_decompress = true; + else + csi2->dpcm_decompress = false; + + csi2->contexts[0].format_id = csi2_ctx_map_format(csi2); + + if (csi2->video_out.bpl_padding == 0) + csi2->contexts[0].data_offset = 0; + else + csi2->contexts[0].data_offset = csi2->video_out.bpl_value; + + /* + * Enable end of frame and end of line signals generation for + * context 0. These signals are generated from CSI2 receiver to + * qualify the last pixel of a frame and the last pixel of a line. + * Without enabling the signals CSI2 receiver writes data to memory + * beyond buffer size and/or data line offset is not handled correctly. + */ + csi2->contexts[0].eof_enabled = 1; + csi2->contexts[0].eol_enabled = 1; + + csi2_irq_complexio1_set(csi2, 1); + csi2_irq_ctx_set(csi2, 1); + csi2_irq_status_set(csi2, 1); + + /* Set configuration (timings, format and links) */ + csi2_timing_config(csi2, timing); + csi2_recv_config(csi2, &csi2->ctrl); + csi2_ctx_config(csi2, &csi2->contexts[0]); + + return 0; +} + +/* + * csi2_print_status - Prints CSI2 debug information. + */ +#define CSI2_PRINT_REGISTER(iss, regs, name)\ + dev_dbg(iss->dev, "###CSI2 " #name "=0x%08x\n", \ + iss_reg_read(iss, regs, CSI2_##name)) + +static void csi2_print_status(struct iss_csi2_device *csi2) +{ + struct iss_device *iss = csi2->iss; + + if (!csi2->available) + return; + + dev_dbg(iss->dev, "-------------CSI2 Register dump-------------\n"); + + CSI2_PRINT_REGISTER(iss, csi2->regs1, SYSCONFIG); + CSI2_PRINT_REGISTER(iss, csi2->regs1, SYSSTATUS); + CSI2_PRINT_REGISTER(iss, csi2->regs1, IRQENABLE); + CSI2_PRINT_REGISTER(iss, csi2->regs1, IRQSTATUS); + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTRL); + CSI2_PRINT_REGISTER(iss, csi2->regs1, DBG_H); + CSI2_PRINT_REGISTER(iss, csi2->regs1, COMPLEXIO_CFG); + CSI2_PRINT_REGISTER(iss, csi2->regs1, COMPLEXIO_IRQSTATUS); + CSI2_PRINT_REGISTER(iss, csi2->regs1, SHORT_PACKET); + CSI2_PRINT_REGISTER(iss, csi2->regs1, COMPLEXIO_IRQENABLE); + CSI2_PRINT_REGISTER(iss, csi2->regs1, DBG_P); + CSI2_PRINT_REGISTER(iss, csi2->regs1, TIMING); + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_CTRL1(0)); + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_CTRL2(0)); + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_DAT_OFST(0)); + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_PING_ADDR(0)); + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_PONG_ADDR(0)); + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_IRQENABLE(0)); + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_IRQSTATUS(0)); + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_CTRL3(0)); + + dev_dbg(iss->dev, "--------------------------------------------\n"); +} + +/* ----------------------------------------------------------------------------- + * Interrupt handling + */ + +/* + * csi2_isr_buffer - Does buffer handling at end-of-frame + * when writing to memory. + */ +static void csi2_isr_buffer(struct iss_csi2_device *csi2) +{ + struct iss_buffer *buffer; + + csi2_ctx_enable(csi2, 0, 0); + + buffer = omap4iss_video_buffer_next(&csi2->video_out); + + /* + * Let video queue operation restart engine if there is an underrun + * condition. + */ + if (buffer == NULL) + return; + + csi2_set_outaddr(csi2, buffer->iss_addr); + csi2_ctx_enable(csi2, 0, 1); +} + +static void csi2_isr_ctx(struct iss_csi2_device *csi2, + struct iss_csi2_ctx_cfg *ctx) +{ + unsigned int n = ctx->ctxnum; + u32 status; + + status = iss_reg_read(csi2->iss, csi2->regs1, CSI2_CTX_IRQSTATUS(n)); + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_IRQSTATUS(n), status); + + /* Propagate frame number */ + if (status & CSI2_CTX_IRQ_FS) { + struct iss_pipeline *pipe = + to_iss_pipeline(&csi2->subdev.entity); + if (pipe->do_propagation) + atomic_inc(&pipe->frame_number); + } + + if (!(status & CSI2_CTX_IRQ_FE)) + return; + + /* Skip interrupts until we reach the frame skip count. The CSI2 will be + * automatically disabled, as the frame skip count has been programmed + * in the CSI2_CTx_CTRL1::COUNT field, so reenable it. + * + * It would have been nice to rely on the FRAME_NUMBER interrupt instead + * but it turned out that the interrupt is only generated when the CSI2 + * writes to memory (the CSI2_CTx_CTRL1::COUNT field is decreased + * correctly and reaches 0 when data is forwarded to the video port only + * but no interrupt arrives). Maybe a CSI2 hardware bug. + */ + if (csi2->frame_skip) { + csi2->frame_skip--; + if (csi2->frame_skip == 0) { + ctx->format_id = csi2_ctx_map_format(csi2); + csi2_ctx_config(csi2, ctx); + csi2_ctx_enable(csi2, n, 1); + } + return; + } + + if (csi2->output & CSI2_OUTPUT_MEMORY) + csi2_isr_buffer(csi2); +} + +/* + * omap4iss_csi2_isr - CSI2 interrupt handling. + */ +void omap4iss_csi2_isr(struct iss_csi2_device *csi2) +{ + struct iss_pipeline *pipe = to_iss_pipeline(&csi2->subdev.entity); + u32 csi2_irqstatus, cpxio1_irqstatus; + struct iss_device *iss = csi2->iss; + + if (!csi2->available) + return; + + csi2_irqstatus = iss_reg_read(csi2->iss, csi2->regs1, CSI2_IRQSTATUS); + iss_reg_write(csi2->iss, csi2->regs1, CSI2_IRQSTATUS, csi2_irqstatus); + + /* Failure Cases */ + if (csi2_irqstatus & CSI2_IRQ_COMPLEXIO_ERR) { + cpxio1_irqstatus = iss_reg_read(csi2->iss, csi2->regs1, + CSI2_COMPLEXIO_IRQSTATUS); + iss_reg_write(csi2->iss, csi2->regs1, CSI2_COMPLEXIO_IRQSTATUS, + cpxio1_irqstatus); + dev_dbg(iss->dev, "CSI2: ComplexIO Error IRQ %x\n", + cpxio1_irqstatus); + pipe->error = true; + } + + if (csi2_irqstatus & (CSI2_IRQ_OCP_ERR | + CSI2_IRQ_SHORT_PACKET | + CSI2_IRQ_ECC_NO_CORRECTION | + CSI2_IRQ_COMPLEXIO_ERR | + CSI2_IRQ_FIFO_OVF)) { + dev_dbg(iss->dev, + "CSI2 Err: OCP:%d SHORT:%d ECC:%d CPXIO:%d OVF:%d\n", + csi2_irqstatus & CSI2_IRQ_OCP_ERR ? 1 : 0, + csi2_irqstatus & CSI2_IRQ_SHORT_PACKET ? 1 : 0, + csi2_irqstatus & CSI2_IRQ_ECC_NO_CORRECTION ? 1 : 0, + csi2_irqstatus & CSI2_IRQ_COMPLEXIO_ERR ? 1 : 0, + csi2_irqstatus & CSI2_IRQ_FIFO_OVF ? 1 : 0); + pipe->error = true; + } + + if (omap4iss_module_sync_is_stopping(&csi2->wait, &csi2->stopping)) + return; + + /* Successful cases */ + if (csi2_irqstatus & CSI2_IRQ_CONTEXT0) + csi2_isr_ctx(csi2, &csi2->contexts[0]); + + if (csi2_irqstatus & CSI2_IRQ_ECC_CORRECTION) + dev_dbg(iss->dev, "CSI2: ECC correction done\n"); +} + +/* ----------------------------------------------------------------------------- + * ISS video operations + */ + +/* + * csi2_queue - Queues the first buffer when using memory output + * @video: The video node + * @buffer: buffer to queue + */ +static int csi2_queue(struct iss_video *video, struct iss_buffer *buffer) +{ + struct iss_csi2_device *csi2 = container_of(video, + struct iss_csi2_device, video_out); + + csi2_set_outaddr(csi2, buffer->iss_addr); + + /* + * If streaming was enabled before there was a buffer queued + * or underrun happened in the ISR, the hardware was not enabled + * and DMA queue flag ISS_VIDEO_DMAQUEUE_UNDERRUN is still set. + * Enable it now. + */ + if (csi2->video_out.dmaqueue_flags & ISS_VIDEO_DMAQUEUE_UNDERRUN) { + /* Enable / disable context 0 and IRQs */ + csi2_if_enable(csi2, 1); + csi2_ctx_enable(csi2, 0, 1); + iss_video_dmaqueue_flags_clr(&csi2->video_out); + } + + return 0; +} + +static const struct iss_video_operations csi2_issvideo_ops = { + .queue = csi2_queue, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static struct v4l2_mbus_framefmt * +__csi2_get_format(struct iss_csi2_device *csi2, struct v4l2_subdev_fh *fh, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(fh, pad); + else + return &csi2->formats[pad]; +} + +static void +csi2_try_format(struct iss_csi2_device *csi2, struct v4l2_subdev_fh *fh, + unsigned int pad, struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + enum v4l2_mbus_pixelcode pixelcode; + struct v4l2_mbus_framefmt *format; + const struct iss_format_info *info; + unsigned int i; + + switch (pad) { + case CSI2_PAD_SINK: + /* Clamp the width and height to valid range (1-8191). */ + for (i = 0; i < ARRAY_SIZE(csi2_input_fmts); i++) { + if (fmt->code == csi2_input_fmts[i]) + break; + } + + /* If not found, use SGRBG10 as default */ + if (i >= ARRAY_SIZE(csi2_input_fmts)) + fmt->code = V4L2_MBUS_FMT_SGRBG10_1X10; + + fmt->width = clamp_t(u32, fmt->width, 1, 8191); + fmt->height = clamp_t(u32, fmt->height, 1, 8191); + break; + + case CSI2_PAD_SOURCE: + /* Source format same as sink format, except for DPCM + * compression. + */ + pixelcode = fmt->code; + format = __csi2_get_format(csi2, fh, CSI2_PAD_SINK, which); + memcpy(fmt, format, sizeof(*fmt)); + + /* + * Only Allow DPCM decompression, and check that the + * pattern is preserved + */ + info = omap4iss_video_format_info(fmt->code); + if (info->uncompressed == pixelcode) + fmt->code = pixelcode; + break; + } + + /* RGB, non-interlaced */ + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->field = V4L2_FIELD_NONE; +} + +/* + * csi2_enum_mbus_code - Handle pixel format enumeration + * @sd : pointer to v4l2 subdev structure + * @fh : V4L2 subdev file handle + * @code : pointer to v4l2_subdev_mbus_code_enum structure + * return -EINVAL or zero on success + */ +static int csi2_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + const struct iss_format_info *info; + + if (code->pad == CSI2_PAD_SINK) { + if (code->index >= ARRAY_SIZE(csi2_input_fmts)) + return -EINVAL; + + code->code = csi2_input_fmts[code->index]; + } else { + format = __csi2_get_format(csi2, fh, CSI2_PAD_SINK, + V4L2_SUBDEV_FORMAT_TRY); + switch (code->index) { + case 0: + /* Passthrough sink pad code */ + code->code = format->code; + break; + case 1: + /* Uncompressed code */ + info = omap4iss_video_format_info(format->code); + if (info->uncompressed == format->code) + return -EINVAL; + + code->code = info->uncompressed; + break; + default: + return -EINVAL; + } + } + + return 0; +} + +static int csi2_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index != 0) + return -EINVAL; + + format.code = fse->code; + format.width = 1; + format.height = 1; + csi2_try_format(csi2, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY); + fse->min_width = format.width; + fse->min_height = format.height; + + if (format.code != fse->code) + return -EINVAL; + + format.code = fse->code; + format.width = -1; + format.height = -1; + csi2_try_format(csi2, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * csi2_get_format - Handle get format by pads subdev method + * @sd : pointer to v4l2 subdev structure + * @fh : V4L2 subdev file handle + * @fmt: pointer to v4l2 subdev format structure + * return -EINVAL or zero on success + */ +static int csi2_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __csi2_get_format(csi2, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + fmt->format = *format; + return 0; +} + +/* + * csi2_set_format - Handle set format by pads subdev method + * @sd : pointer to v4l2 subdev structure + * @fh : V4L2 subdev file handle + * @fmt: pointer to v4l2 subdev format structure + * return -EINVAL or zero on success + */ +static int csi2_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __csi2_get_format(csi2, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + csi2_try_format(csi2, fh, fmt->pad, &fmt->format, fmt->which); + *format = fmt->format; + + /* Propagate the format from sink to source */ + if (fmt->pad == CSI2_PAD_SINK) { + format = __csi2_get_format(csi2, fh, CSI2_PAD_SOURCE, + fmt->which); + *format = fmt->format; + csi2_try_format(csi2, fh, CSI2_PAD_SOURCE, format, fmt->which); + } + + return 0; +} + +static int csi2_link_validate(struct v4l2_subdev *sd, struct media_link *link, + struct v4l2_subdev_format *source_fmt, + struct v4l2_subdev_format *sink_fmt) +{ + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); + struct iss_pipeline *pipe = to_iss_pipeline(&csi2->subdev.entity); + int rval; + + pipe->external = media_entity_to_v4l2_subdev(link->source->entity); + rval = omap4iss_get_external_info(pipe, link); + if (rval < 0) + return rval; + + return v4l2_subdev_link_validate_default(sd, link, source_fmt, + sink_fmt); +} + +/* + * csi2_init_formats - Initialize formats on all pads + * @sd: ISS CSI2 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. + */ +static int csi2_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format; + + memset(&format, 0, sizeof(format)); + format.pad = CSI2_PAD_SINK; + format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + format.format.code = V4L2_MBUS_FMT_SGRBG10_1X10; + format.format.width = 4096; + format.format.height = 4096; + csi2_set_format(sd, fh, &format); + + return 0; +} + +/* + * csi2_set_stream - Enable/Disable streaming on the CSI2 module + * @sd: ISS CSI2 V4L2 subdevice + * @enable: ISS pipeline stream state + * + * Return 0 on success or a negative error code otherwise. + */ +static int csi2_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); + struct iss_device *iss = csi2->iss; + struct iss_pipeline *pipe = to_iss_pipeline(&csi2->subdev.entity); + struct iss_video *video_out = &csi2->video_out; + int ret = 0; + + if (csi2->state == ISS_PIPELINE_STREAM_STOPPED) { + if (enable == ISS_PIPELINE_STREAM_STOPPED) + return 0; + + omap4iss_subclk_enable(iss, csi2->subclk); + } + + switch (enable) { + case ISS_PIPELINE_STREAM_CONTINUOUS: { + ret = omap4iss_csiphy_config(iss, sd); + if (ret < 0) + return ret; + + if (omap4iss_csiphy_acquire(csi2->phy) < 0) + return -ENODEV; + csi2->use_fs_irq = pipe->do_propagation; + csi2_configure(csi2); + csi2_print_status(csi2); + + /* + * When outputting to memory with no buffer available, let the + * buffer queue handler start the hardware. A DMA queue flag + * ISS_VIDEO_DMAQUEUE_QUEUED will be set as soon as there is + * a buffer available. + */ + if (csi2->output & CSI2_OUTPUT_MEMORY && + !(video_out->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_QUEUED)) + break; + /* Enable context 0 and IRQs */ + atomic_set(&csi2->stopping, 0); + csi2_ctx_enable(csi2, 0, 1); + csi2_if_enable(csi2, 1); + iss_video_dmaqueue_flags_clr(video_out); + break; + } + case ISS_PIPELINE_STREAM_STOPPED: + if (csi2->state == ISS_PIPELINE_STREAM_STOPPED) + return 0; + if (omap4iss_module_sync_idle(&sd->entity, &csi2->wait, + &csi2->stopping)) + ret = -ETIMEDOUT; + csi2_ctx_enable(csi2, 0, 0); + csi2_if_enable(csi2, 0); + csi2_irq_ctx_set(csi2, 0); + omap4iss_csiphy_release(csi2->phy); + omap4iss_subclk_disable(iss, csi2->subclk); + iss_video_dmaqueue_flags_clr(video_out); + break; + } + + csi2->state = enable; + return ret; +} + +/* subdev video operations */ +static const struct v4l2_subdev_video_ops csi2_video_ops = { + .s_stream = csi2_set_stream, +}; + +/* subdev pad operations */ +static const struct v4l2_subdev_pad_ops csi2_pad_ops = { + .enum_mbus_code = csi2_enum_mbus_code, + .enum_frame_size = csi2_enum_frame_size, + .get_fmt = csi2_get_format, + .set_fmt = csi2_set_format, + .link_validate = csi2_link_validate, +}; + +/* subdev operations */ +static const struct v4l2_subdev_ops csi2_ops = { + .video = &csi2_video_ops, + .pad = &csi2_pad_ops, +}; + +/* subdev internal operations */ +static const struct v4l2_subdev_internal_ops csi2_internal_ops = { + .open = csi2_init_formats, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +/* + * csi2_link_setup - Setup CSI2 connections. + * @entity : Pointer to media entity structure + * @local : Pointer to local pad array + * @remote : Pointer to remote pad array + * @flags : Link flags + * return -EINVAL or zero on success + */ +static int csi2_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); + struct iss_csi2_ctrl_cfg *ctrl = &csi2->ctrl; + + /* + * The ISS core doesn't support pipelines with multiple video outputs. + * Revisit this when it will be implemented, and return -EBUSY for now. + */ + + switch (local->index | media_entity_type(remote->entity)) { + case CSI2_PAD_SOURCE | MEDIA_ENT_T_DEVNODE: + if (flags & MEDIA_LNK_FL_ENABLED) { + if (csi2->output & ~CSI2_OUTPUT_MEMORY) + return -EBUSY; + csi2->output |= CSI2_OUTPUT_MEMORY; + } else { + csi2->output &= ~CSI2_OUTPUT_MEMORY; + } + break; + + case CSI2_PAD_SOURCE | MEDIA_ENT_T_V4L2_SUBDEV: + if (flags & MEDIA_LNK_FL_ENABLED) { + if (csi2->output & ~CSI2_OUTPUT_IPIPEIF) + return -EBUSY; + csi2->output |= CSI2_OUTPUT_IPIPEIF; + } else { + csi2->output &= ~CSI2_OUTPUT_IPIPEIF; + } + break; + + default: + /* Link from camera to CSI2 is fixed... */ + return -EINVAL; + } + + ctrl->vp_only_enable = csi2->output & CSI2_OUTPUT_MEMORY ? false : true; + ctrl->vp_clk_enable = !!(csi2->output & CSI2_OUTPUT_IPIPEIF); + + return 0; +} + +/* media operations */ +static const struct media_entity_operations csi2_media_ops = { + .link_setup = csi2_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +void omap4iss_csi2_unregister_entities(struct iss_csi2_device *csi2) +{ + v4l2_device_unregister_subdev(&csi2->subdev); + omap4iss_video_unregister(&csi2->video_out); +} + +int omap4iss_csi2_register_entities(struct iss_csi2_device *csi2, + struct v4l2_device *vdev) +{ + int ret; + + /* Register the subdev and video nodes. */ + ret = v4l2_device_register_subdev(vdev, &csi2->subdev); + if (ret < 0) + goto error; + + ret = omap4iss_video_register(&csi2->video_out, vdev); + if (ret < 0) + goto error; + + return 0; + +error: + omap4iss_csi2_unregister_entities(csi2); + return ret; +} + +/* ----------------------------------------------------------------------------- + * ISS CSI2 initialisation and cleanup + */ + +/* + * csi2_init_entities - Initialize subdev and media entity. + * @csi2: Pointer to csi2 structure. + * return -ENOMEM or zero on success + */ +static int csi2_init_entities(struct iss_csi2_device *csi2, const char *subname) +{ + struct v4l2_subdev *sd = &csi2->subdev; + struct media_pad *pads = csi2->pads; + struct media_entity *me = &sd->entity; + int ret; + char name[V4L2_SUBDEV_NAME_SIZE]; + + v4l2_subdev_init(sd, &csi2_ops); + sd->internal_ops = &csi2_internal_ops; + sprintf(name, "CSI2%s", subname); + snprintf(sd->name, sizeof(sd->name), "OMAP4 ISS %s", name); + + sd->grp_id = 1 << 16; /* group ID for iss subdevs */ + v4l2_set_subdevdata(sd, csi2); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + pads[CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + pads[CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + + me->ops = &csi2_media_ops; + ret = media_entity_init(me, CSI2_PADS_NUM, pads, 0); + if (ret < 0) + return ret; + + csi2_init_formats(sd, NULL); + + /* Video device node */ + csi2->video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + csi2->video_out.ops = &csi2_issvideo_ops; + csi2->video_out.bpl_alignment = 32; + csi2->video_out.bpl_zero_padding = 1; + csi2->video_out.bpl_max = 0x1ffe0; + csi2->video_out.iss = csi2->iss; + csi2->video_out.capture_mem = PAGE_ALIGN(4096 * 4096) * 3; + + ret = omap4iss_video_init(&csi2->video_out, name); + if (ret < 0) + goto error_video; + + /* Connect the CSI2 subdev to the video node. */ + ret = media_entity_create_link(&csi2->subdev.entity, CSI2_PAD_SOURCE, + &csi2->video_out.video.entity, 0, 0); + if (ret < 0) + goto error_link; + + return 0; + +error_link: + omap4iss_video_cleanup(&csi2->video_out); +error_video: + media_entity_cleanup(&csi2->subdev.entity); + return ret; +} + +/* + * omap4iss_csi2_init - Routine for module driver init + */ +int omap4iss_csi2_init(struct iss_device *iss) +{ + struct iss_csi2_device *csi2a = &iss->csi2a; + struct iss_csi2_device *csi2b = &iss->csi2b; + int ret; + + csi2a->iss = iss; + csi2a->available = 1; + csi2a->regs1 = OMAP4_ISS_MEM_CSI2_A_REGS1; + csi2a->phy = &iss->csiphy1; + csi2a->subclk = OMAP4_ISS_SUBCLK_CSI2_A; + csi2a->state = ISS_PIPELINE_STREAM_STOPPED; + init_waitqueue_head(&csi2a->wait); + + ret = csi2_init_entities(csi2a, "a"); + if (ret < 0) + return ret; + + csi2b->iss = iss; + csi2b->available = 1; + csi2b->regs1 = OMAP4_ISS_MEM_CSI2_B_REGS1; + csi2b->phy = &iss->csiphy2; + csi2b->subclk = OMAP4_ISS_SUBCLK_CSI2_B; + csi2b->state = ISS_PIPELINE_STREAM_STOPPED; + init_waitqueue_head(&csi2b->wait); + + ret = csi2_init_entities(csi2b, "b"); + if (ret < 0) + return ret; + + return 0; +} + +/* + * omap4iss_csi2_cleanup - Routine for module driver cleanup + */ +void omap4iss_csi2_cleanup(struct iss_device *iss) +{ + struct iss_csi2_device *csi2a = &iss->csi2a; + struct iss_csi2_device *csi2b = &iss->csi2b; + + omap4iss_video_cleanup(&csi2a->video_out); + media_entity_cleanup(&csi2a->subdev.entity); + + omap4iss_video_cleanup(&csi2b->video_out); + media_entity_cleanup(&csi2b->subdev.entity); +} diff --git a/drivers/staging/media/omap4iss/iss_csi2.h b/drivers/staging/media/omap4iss/iss_csi2.h new file mode 100644 index 000000000000..971aa7b08013 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_csi2.h @@ -0,0 +1,158 @@ +/* + * TI OMAP4 ISS V4L2 Driver - CSI2 module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.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 OMAP4_ISS_CSI2_H +#define OMAP4_ISS_CSI2_H + +#include <linux/types.h> +#include <linux/videodev2.h> + +#include "iss_video.h" + +struct iss_csiphy; + +/* This is not an exhaustive list */ +enum iss_csi2_pix_formats { + CSI2_PIX_FMT_OTHERS = 0, + CSI2_PIX_FMT_YUV422_8BIT = 0x1e, + CSI2_PIX_FMT_YUV422_8BIT_VP = 0x9e, + CSI2_PIX_FMT_YUV422_8BIT_VP16 = 0xde, + CSI2_PIX_FMT_RAW10_EXP16 = 0xab, + CSI2_PIX_FMT_RAW10_EXP16_VP = 0x12f, + CSI2_PIX_FMT_RAW8 = 0x2a, + CSI2_PIX_FMT_RAW8_DPCM10_EXP16 = 0x2aa, + CSI2_PIX_FMT_RAW8_DPCM10_VP = 0x32a, + CSI2_PIX_FMT_RAW8_VP = 0x12a, + CSI2_USERDEF_8BIT_DATA1_DPCM10_VP = 0x340, + CSI2_USERDEF_8BIT_DATA1_DPCM10 = 0x2c0, + CSI2_USERDEF_8BIT_DATA1 = 0x40, +}; + +enum iss_csi2_irqevents { + OCP_ERR_IRQ = 0x4000, + SHORT_PACKET_IRQ = 0x2000, + ECC_CORRECTION_IRQ = 0x1000, + ECC_NO_CORRECTION_IRQ = 0x800, + COMPLEXIO2_ERR_IRQ = 0x400, + COMPLEXIO1_ERR_IRQ = 0x200, + FIFO_OVF_IRQ = 0x100, + CONTEXT7 = 0x80, + CONTEXT6 = 0x40, + CONTEXT5 = 0x20, + CONTEXT4 = 0x10, + CONTEXT3 = 0x8, + CONTEXT2 = 0x4, + CONTEXT1 = 0x2, + CONTEXT0 = 0x1, +}; + +enum iss_csi2_ctx_irqevents { + CTX_ECC_CORRECTION = 0x100, + CTX_LINE_NUMBER = 0x80, + CTX_FRAME_NUMBER = 0x40, + CTX_CS = 0x20, + CTX_LE = 0x8, + CTX_LS = 0x4, + CTX_FE = 0x2, + CTX_FS = 0x1, +}; + +enum iss_csi2_frame_mode { + ISS_CSI2_FRAME_IMMEDIATE, + ISS_CSI2_FRAME_AFTERFEC, +}; + +#define ISS_CSI2_MAX_CTX_NUM 7 + +struct iss_csi2_ctx_cfg { + u8 ctxnum; /* context number 0 - 7 */ + u8 dpcm_decompress; + + /* Fields in CSI2_CTx_CTRL2 - locked by CSI2_CTx_CTRL1.CTX_EN */ + u8 virtual_id; + u16 format_id; /* as in CSI2_CTx_CTRL2[9:0] */ + u8 dpcm_predictor; /* 1: simple, 0: advanced */ + + /* Fields in CSI2_CTx_CTRL1/3 - Shadowed */ + u16 alpha; + u16 data_offset; + u32 ping_addr; + u32 pong_addr; + u8 eof_enabled; + u8 eol_enabled; + u8 checksum_enabled; + u8 enabled; +}; + +struct iss_csi2_timing_cfg { + u8 ionum; /* IO1 or IO2 as in CSI2_TIMING */ + unsigned force_rx_mode:1; + unsigned stop_state_16x:1; + unsigned stop_state_4x:1; + u16 stop_state_counter; +}; + +struct iss_csi2_ctrl_cfg { + bool vp_clk_enable; + bool vp_only_enable; + u8 vp_out_ctrl; + enum iss_csi2_frame_mode frame_mode; + bool ecc_enable; + bool if_enable; +}; + +#define CSI2_PAD_SINK 0 +#define CSI2_PAD_SOURCE 1 +#define CSI2_PADS_NUM 2 + +#define CSI2_OUTPUT_IPIPEIF (1 << 0) +#define CSI2_OUTPUT_MEMORY (1 << 1) + +struct iss_csi2_device { + struct v4l2_subdev subdev; + struct media_pad pads[CSI2_PADS_NUM]; + struct v4l2_mbus_framefmt formats[CSI2_PADS_NUM]; + + struct iss_video video_out; + struct iss_device *iss; + + u8 available; /* Is the IP present on the silicon? */ + + /* memory resources, as defined in enum iss_mem_resources */ + unsigned int regs1; + unsigned int regs2; + /* ISP subclock, as defined in enum iss_isp_subclk_resource */ + unsigned int subclk; + + u32 output; /* output to IPIPEIF, memory or both? */ + bool dpcm_decompress; + unsigned int frame_skip; + bool use_fs_irq; + + struct iss_csiphy *phy; + struct iss_csi2_ctx_cfg contexts[ISS_CSI2_MAX_CTX_NUM + 1]; + struct iss_csi2_timing_cfg timing[2]; + struct iss_csi2_ctrl_cfg ctrl; + enum iss_pipeline_stream_state state; + wait_queue_head_t wait; + atomic_t stopping; +}; + +void omap4iss_csi2_isr(struct iss_csi2_device *csi2); +int omap4iss_csi2_reset(struct iss_csi2_device *csi2); +int omap4iss_csi2_init(struct iss_device *iss); +void omap4iss_csi2_cleanup(struct iss_device *iss); +void omap4iss_csi2_unregister_entities(struct iss_csi2_device *csi2); +int omap4iss_csi2_register_entities(struct iss_csi2_device *csi2, + struct v4l2_device *vdev); +#endif /* OMAP4_ISS_CSI2_H */ diff --git a/drivers/staging/media/omap4iss/iss_csiphy.c b/drivers/staging/media/omap4iss/iss_csiphy.c new file mode 100644 index 000000000000..7c3d55d811ef --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_csiphy.c @@ -0,0 +1,279 @@ +/* + * TI OMAP4 ISS V4L2 Driver - CSI PHY module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.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/delay.h> +#include <linux/device.h> + +#include "../../../../arch/arm/mach-omap2/control.h" + +#include "iss.h" +#include "iss_regs.h" +#include "iss_csiphy.h" + +/* + * csiphy_lanes_config - Configuration of CSIPHY lanes. + * + * Updates HW configuration. + * Called with phy->mutex taken. + */ +static void csiphy_lanes_config(struct iss_csiphy *phy) +{ + unsigned int i; + u32 reg; + + reg = iss_reg_read(phy->iss, phy->cfg_regs, CSI2_COMPLEXIO_CFG); + + for (i = 0; i < phy->max_data_lanes; i++) { + reg &= ~(CSI2_COMPLEXIO_CFG_DATA_POL(i + 1) | + CSI2_COMPLEXIO_CFG_DATA_POSITION_MASK(i + 1)); + reg |= (phy->lanes.data[i].pol ? + CSI2_COMPLEXIO_CFG_DATA_POL(i + 1) : 0); + reg |= (phy->lanes.data[i].pos << + CSI2_COMPLEXIO_CFG_DATA_POSITION_SHIFT(i + 1)); + } + + reg &= ~(CSI2_COMPLEXIO_CFG_CLOCK_POL | + CSI2_COMPLEXIO_CFG_CLOCK_POSITION_MASK); + reg |= phy->lanes.clk.pol ? CSI2_COMPLEXIO_CFG_CLOCK_POL : 0; + reg |= phy->lanes.clk.pos << CSI2_COMPLEXIO_CFG_CLOCK_POSITION_SHIFT; + + iss_reg_write(phy->iss, phy->cfg_regs, CSI2_COMPLEXIO_CFG, reg); +} + +/* + * csiphy_set_power + * @power: Power state to be set. + * + * Returns 0 if successful, or -EBUSY if the retry count is exceeded. + */ +static int csiphy_set_power(struct iss_csiphy *phy, u32 power) +{ + u32 reg; + u8 retry_count; + + iss_reg_update(phy->iss, phy->cfg_regs, CSI2_COMPLEXIO_CFG, + CSI2_COMPLEXIO_CFG_PWD_CMD_MASK, + power | CSI2_COMPLEXIO_CFG_PWR_AUTO); + + retry_count = 0; + do { + udelay(1); + reg = iss_reg_read(phy->iss, phy->cfg_regs, CSI2_COMPLEXIO_CFG) + & CSI2_COMPLEXIO_CFG_PWD_STATUS_MASK; + + if (reg != power >> 2) + retry_count++; + + } while ((reg != power >> 2) && (retry_count < 250)); + + if (retry_count == 250) { + dev_err(phy->iss->dev, "CSI2 CIO set power failed!\n"); + return -EBUSY; + } + + return 0; +} + +/* + * csiphy_dphy_config - Configure CSI2 D-PHY parameters. + * + * Called with phy->mutex taken. + */ +static void csiphy_dphy_config(struct iss_csiphy *phy) +{ + u32 reg; + + /* Set up REGISTER0 */ + reg = phy->dphy.ths_term << REGISTER0_THS_TERM_SHIFT; + reg |= phy->dphy.ths_settle << REGISTER0_THS_SETTLE_SHIFT; + + iss_reg_write(phy->iss, phy->phy_regs, REGISTER0, reg); + + /* Set up REGISTER1 */ + reg = phy->dphy.tclk_term << REGISTER1_TCLK_TERM_SHIFT; + reg |= phy->dphy.tclk_miss << REGISTER1_CTRLCLK_DIV_FACTOR_SHIFT; + reg |= phy->dphy.tclk_settle << REGISTER1_TCLK_SETTLE_SHIFT; + reg |= 0xb8 << REGISTER1_DPHY_HS_SYNC_PATTERN_SHIFT; + + iss_reg_write(phy->iss, phy->phy_regs, REGISTER1, reg); +} + +/* + * TCLK values are OK at their reset values + */ +#define TCLK_TERM 0 +#define TCLK_MISS 1 +#define TCLK_SETTLE 14 + +int omap4iss_csiphy_config(struct iss_device *iss, + struct v4l2_subdev *csi2_subdev) +{ + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(csi2_subdev); + struct iss_pipeline *pipe = to_iss_pipeline(&csi2_subdev->entity); + struct iss_v4l2_subdevs_group *subdevs = pipe->external->host_priv; + struct iss_csiphy_dphy_cfg csi2phy; + int csi2_ddrclk_khz; + struct iss_csiphy_lanes_cfg *lanes; + unsigned int used_lanes = 0; + u32 cam_rx_ctrl; + unsigned int i; + + lanes = &subdevs->bus.csi2.lanecfg; + + /* + * SCM.CONTROL_CAMERA_RX + * - bit [31] : CSIPHY2 lane 2 enable (4460+ only) + * - bit [30:29] : CSIPHY2 per-lane enable (1 to 0) + * - bit [28:24] : CSIPHY1 per-lane enable (4 to 0) + * - bit [21] : CSIPHY2 CTRLCLK enable + * - bit [20:19] : CSIPHY2 config: 00 d-phy, 01/10 ccp2 + * - bit [18] : CSIPHY1 CTRLCLK enable + * - bit [17:16] : CSIPHY1 config: 00 d-phy, 01/10 ccp2 + */ + cam_rx_ctrl = omap4_ctrl_pad_readl( + OMAP4_CTRL_MODULE_PAD_CORE_CONTROL_CAMERA_RX); + + + if (subdevs->interface == ISS_INTERFACE_CSI2A_PHY1) { + cam_rx_ctrl &= ~(OMAP4_CAMERARX_CSI21_LANEENABLE_MASK | + OMAP4_CAMERARX_CSI21_CAMMODE_MASK); + /* NOTE: Leave CSIPHY1 config to 0x0: D-PHY mode */ + /* Enable all lanes for now */ + cam_rx_ctrl |= + 0x1f << OMAP4_CAMERARX_CSI21_LANEENABLE_SHIFT; + /* Enable CTRLCLK */ + cam_rx_ctrl |= OMAP4_CAMERARX_CSI21_CTRLCLKEN_MASK; + } + + if (subdevs->interface == ISS_INTERFACE_CSI2B_PHY2) { + cam_rx_ctrl &= ~(OMAP4_CAMERARX_CSI22_LANEENABLE_MASK | + OMAP4_CAMERARX_CSI22_CAMMODE_MASK); + /* NOTE: Leave CSIPHY2 config to 0x0: D-PHY mode */ + /* Enable all lanes for now */ + cam_rx_ctrl |= + 0x3 << OMAP4_CAMERARX_CSI22_LANEENABLE_SHIFT; + /* Enable CTRLCLK */ + cam_rx_ctrl |= OMAP4_CAMERARX_CSI22_CTRLCLKEN_MASK; + } + + omap4_ctrl_pad_writel(cam_rx_ctrl, + OMAP4_CTRL_MODULE_PAD_CORE_CONTROL_CAMERA_RX); + + /* Reset used lane count */ + csi2->phy->used_data_lanes = 0; + + /* Clock and data lanes verification */ + for (i = 0; i < csi2->phy->max_data_lanes; i++) { + if (lanes->data[i].pos == 0) + continue; + + if (lanes->data[i].pol > 1 || + lanes->data[i].pos > (csi2->phy->max_data_lanes + 1)) + return -EINVAL; + + if (used_lanes & (1 << lanes->data[i].pos)) + return -EINVAL; + + used_lanes |= 1 << lanes->data[i].pos; + csi2->phy->used_data_lanes++; + } + + if (lanes->clk.pol > 1 || + lanes->clk.pos > (csi2->phy->max_data_lanes + 1)) + return -EINVAL; + + if (lanes->clk.pos == 0 || used_lanes & (1 << lanes->clk.pos)) + return -EINVAL; + + csi2_ddrclk_khz = pipe->external_rate / 1000 + / (2 * csi2->phy->used_data_lanes) + * pipe->external_bpp; + + /* + * THS_TERM: Programmed value = ceil(12.5 ns/DDRClk period) - 1. + * THS_SETTLE: Programmed value = ceil(90 ns/DDRClk period) + 3. + */ + csi2phy.ths_term = DIV_ROUND_UP(25 * csi2_ddrclk_khz, 2000000) - 1; + csi2phy.ths_settle = DIV_ROUND_UP(90 * csi2_ddrclk_khz, 1000000) + 3; + csi2phy.tclk_term = TCLK_TERM; + csi2phy.tclk_miss = TCLK_MISS; + csi2phy.tclk_settle = TCLK_SETTLE; + + mutex_lock(&csi2->phy->mutex); + csi2->phy->dphy = csi2phy; + csi2->phy->lanes = *lanes; + mutex_unlock(&csi2->phy->mutex); + + return 0; +} + +int omap4iss_csiphy_acquire(struct iss_csiphy *phy) +{ + int rval; + + mutex_lock(&phy->mutex); + + rval = omap4iss_csi2_reset(phy->csi2); + if (rval) + goto done; + + csiphy_dphy_config(phy); + csiphy_lanes_config(phy); + + rval = csiphy_set_power(phy, CSI2_COMPLEXIO_CFG_PWD_CMD_ON); + if (rval) + goto done; + + phy->phy_in_use = 1; + +done: + mutex_unlock(&phy->mutex); + return rval; +} + +void omap4iss_csiphy_release(struct iss_csiphy *phy) +{ + mutex_lock(&phy->mutex); + if (phy->phy_in_use) { + csiphy_set_power(phy, CSI2_COMPLEXIO_CFG_PWD_CMD_OFF); + phy->phy_in_use = 0; + } + mutex_unlock(&phy->mutex); +} + +/* + * omap4iss_csiphy_init - Initialize the CSI PHY frontends + */ +int omap4iss_csiphy_init(struct iss_device *iss) +{ + struct iss_csiphy *phy1 = &iss->csiphy1; + struct iss_csiphy *phy2 = &iss->csiphy2; + + phy1->iss = iss; + phy1->csi2 = &iss->csi2a; + phy1->max_data_lanes = ISS_CSIPHY1_NUM_DATA_LANES; + phy1->used_data_lanes = 0; + phy1->cfg_regs = OMAP4_ISS_MEM_CSI2_A_REGS1; + phy1->phy_regs = OMAP4_ISS_MEM_CAMERARX_CORE1; + mutex_init(&phy1->mutex); + + phy2->iss = iss; + phy2->csi2 = &iss->csi2b; + phy2->max_data_lanes = ISS_CSIPHY2_NUM_DATA_LANES; + phy2->used_data_lanes = 0; + phy2->cfg_regs = OMAP4_ISS_MEM_CSI2_B_REGS1; + phy2->phy_regs = OMAP4_ISS_MEM_CAMERARX_CORE2; + mutex_init(&phy2->mutex); + + return 0; +} diff --git a/drivers/staging/media/omap4iss/iss_csiphy.h b/drivers/staging/media/omap4iss/iss_csiphy.h new file mode 100644 index 000000000000..e9ca43955654 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_csiphy.h @@ -0,0 +1,51 @@ +/* + * TI OMAP4 ISS V4L2 Driver - CSI PHY module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.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 OMAP4_ISS_CSI_PHY_H +#define OMAP4_ISS_CSI_PHY_H + +#include <media/omap4iss.h> + +struct iss_csi2_device; + +struct iss_csiphy_dphy_cfg { + u8 ths_term; + u8 ths_settle; + u8 tclk_term; + unsigned tclk_miss:1; + u8 tclk_settle; +}; + +struct iss_csiphy { + struct iss_device *iss; + struct mutex mutex; /* serialize csiphy configuration */ + u8 phy_in_use; + struct iss_csi2_device *csi2; + + /* memory resources, as defined in enum iss_mem_resources */ + unsigned int cfg_regs; + unsigned int phy_regs; + + u8 max_data_lanes; /* number of CSI2 Data Lanes supported */ + u8 used_data_lanes; /* number of CSI2 Data Lanes used */ + struct iss_csiphy_lanes_cfg lanes; + struct iss_csiphy_dphy_cfg dphy; +}; + +int omap4iss_csiphy_config(struct iss_device *iss, + struct v4l2_subdev *csi2_subdev); +int omap4iss_csiphy_acquire(struct iss_csiphy *phy); +void omap4iss_csiphy_release(struct iss_csiphy *phy); +int omap4iss_csiphy_init(struct iss_device *iss); + +#endif /* OMAP4_ISS_CSI_PHY_H */ diff --git a/drivers/staging/media/omap4iss/iss_ipipe.c b/drivers/staging/media/omap4iss/iss_ipipe.c new file mode 100644 index 000000000000..6eaafc5e2eea --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_ipipe.c @@ -0,0 +1,570 @@ +/* + * TI OMAP4 ISS V4L2 Driver - ISP IPIPE module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.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/module.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/mm.h> +#include <linux/sched.h> + +#include "iss.h" +#include "iss_regs.h" +#include "iss_ipipe.h" + +static struct v4l2_mbus_framefmt * +__ipipe_get_format(struct iss_ipipe_device *ipipe, struct v4l2_subdev_fh *fh, + unsigned int pad, enum v4l2_subdev_format_whence which); + +static const unsigned int ipipe_fmts[] = { + V4L2_MBUS_FMT_SGRBG10_1X10, + V4L2_MBUS_FMT_SRGGB10_1X10, + V4L2_MBUS_FMT_SBGGR10_1X10, + V4L2_MBUS_FMT_SGBRG10_1X10, +}; + +/* + * ipipe_print_status - Print current IPIPE Module register values. + * @ipipe: Pointer to ISS ISP IPIPE device. + * + * Also prints other debug information stored in the IPIPE module. + */ +#define IPIPE_PRINT_REGISTER(iss, name)\ + dev_dbg(iss->dev, "###IPIPE " #name "=0x%08x\n", \ + iss_reg_read(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_##name)) + +static void ipipe_print_status(struct iss_ipipe_device *ipipe) +{ + struct iss_device *iss = to_iss_device(ipipe); + + dev_dbg(iss->dev, "-------------IPIPE Register dump-------------\n"); + + IPIPE_PRINT_REGISTER(iss, SRC_EN); + IPIPE_PRINT_REGISTER(iss, SRC_MODE); + IPIPE_PRINT_REGISTER(iss, SRC_FMT); + IPIPE_PRINT_REGISTER(iss, SRC_COL); + IPIPE_PRINT_REGISTER(iss, SRC_VPS); + IPIPE_PRINT_REGISTER(iss, SRC_VSZ); + IPIPE_PRINT_REGISTER(iss, SRC_HPS); + IPIPE_PRINT_REGISTER(iss, SRC_HSZ); + IPIPE_PRINT_REGISTER(iss, GCK_MMR); + IPIPE_PRINT_REGISTER(iss, YUV_PHS); + + dev_dbg(iss->dev, "-----------------------------------------------\n"); +} + +/* + * ipipe_enable - Enable/Disable IPIPE. + * @enable: enable flag + * + */ +static void ipipe_enable(struct iss_ipipe_device *ipipe, u8 enable) +{ + struct iss_device *iss = to_iss_device(ipipe); + + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_EN, + IPIPE_SRC_EN_EN, enable ? IPIPE_SRC_EN_EN : 0); +} + +/* ----------------------------------------------------------------------------- + * Format- and pipeline-related configuration helpers + */ + +static void ipipe_configure(struct iss_ipipe_device *ipipe) +{ + struct iss_device *iss = to_iss_device(ipipe); + struct v4l2_mbus_framefmt *format; + + /* IPIPE_PAD_SINK */ + format = &ipipe->formats[IPIPE_PAD_SINK]; + + /* NOTE: Currently just supporting pipeline IN: RGB, OUT: YUV422 */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_FMT, + IPIPE_SRC_FMT_RAW2YUV); + + /* Enable YUV444 -> YUV422 conversion */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_YUV_PHS, + IPIPE_YUV_PHS_LPF); + + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_VPS, 0); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_HPS, 0); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_VSZ, + (format->height - 2) & IPIPE_SRC_VSZ_MASK); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_HSZ, + (format->width - 1) & IPIPE_SRC_HSZ_MASK); + + /* Ignore ipipeif_wrt signal, and operate on-the-fly. */ + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_MODE, + IPIPE_SRC_MODE_WRT | IPIPE_SRC_MODE_OST); + + /* HACK: Values tuned for Ducati SW (OV) */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_COL, + IPIPE_SRC_COL_EE_B | IPIPE_SRC_COL_EO_GB | + IPIPE_SRC_COL_OE_GR | IPIPE_SRC_COL_OO_R); + + /* IPIPE_PAD_SOURCE_VP */ + format = &ipipe->formats[IPIPE_PAD_SOURCE_VP]; + /* Do nothing? */ +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +/* + * ipipe_set_stream - Enable/Disable streaming on the IPIPE module + * @sd: ISP IPIPE V4L2 subdevice + * @enable: Enable/disable stream + */ +static int ipipe_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct iss_ipipe_device *ipipe = v4l2_get_subdevdata(sd); + struct iss_device *iss = to_iss_device(ipipe); + int ret = 0; + + if (ipipe->state == ISS_PIPELINE_STREAM_STOPPED) { + if (enable == ISS_PIPELINE_STREAM_STOPPED) + return 0; + + omap4iss_isp_subclk_enable(iss, OMAP4_ISS_ISP_SUBCLK_IPIPE); + + /* Enable clk_arm_g0 */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_GCK_MMR, + IPIPE_GCK_MMR_REG); + + /* Enable clk_pix_g[3:0] */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_GCK_PIX, + IPIPE_GCK_PIX_G3 | IPIPE_GCK_PIX_G2 | + IPIPE_GCK_PIX_G1 | IPIPE_GCK_PIX_G0); + } + + switch (enable) { + case ISS_PIPELINE_STREAM_CONTINUOUS: + + ipipe_configure(ipipe); + ipipe_print_status(ipipe); + + atomic_set(&ipipe->stopping, 0); + ipipe_enable(ipipe, 1); + break; + + case ISS_PIPELINE_STREAM_STOPPED: + if (ipipe->state == ISS_PIPELINE_STREAM_STOPPED) + return 0; + if (omap4iss_module_sync_idle(&sd->entity, &ipipe->wait, + &ipipe->stopping)) + ret = -ETIMEDOUT; + + ipipe_enable(ipipe, 0); + omap4iss_isp_subclk_disable(iss, OMAP4_ISS_ISP_SUBCLK_IPIPE); + break; + } + + ipipe->state = enable; + return ret; +} + +static struct v4l2_mbus_framefmt * +__ipipe_get_format(struct iss_ipipe_device *ipipe, struct v4l2_subdev_fh *fh, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(fh, pad); + else + return &ipipe->formats[pad]; +} + +/* + * ipipe_try_format - Try video format on a pad + * @ipipe: ISS IPIPE device + * @fh : V4L2 subdev file handle + * @pad: Pad number + * @fmt: Format + */ +static void +ipipe_try_format(struct iss_ipipe_device *ipipe, 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 width = fmt->width; + unsigned int height = fmt->height; + unsigned int i; + + switch (pad) { + case IPIPE_PAD_SINK: + for (i = 0; i < ARRAY_SIZE(ipipe_fmts); i++) { + if (fmt->code == ipipe_fmts[i]) + break; + } + + /* If not found, use SGRBG10 as default */ + if (i >= ARRAY_SIZE(ipipe_fmts)) + fmt->code = V4L2_MBUS_FMT_SGRBG10_1X10; + + /* Clamp the input size. */ + fmt->width = clamp_t(u32, width, 1, 8192); + fmt->height = clamp_t(u32, height, 1, 8192); + fmt->colorspace = V4L2_COLORSPACE_SRGB; + break; + + case IPIPE_PAD_SOURCE_VP: + format = __ipipe_get_format(ipipe, fh, IPIPE_PAD_SINK, which); + memcpy(fmt, format, sizeof(*fmt)); + + fmt->code = V4L2_MBUS_FMT_UYVY8_1X16; + fmt->width = clamp_t(u32, width, 32, fmt->width); + fmt->height = clamp_t(u32, height, 32, fmt->height); + fmt->colorspace = V4L2_COLORSPACE_JPEG; + break; + } + + fmt->field = V4L2_FIELD_NONE; +} + +/* + * ipipe_enum_mbus_code - Handle pixel format enumeration + * @sd : pointer to v4l2 subdev structure + * @fh : V4L2 subdev file handle + * @code : pointer to v4l2_subdev_mbus_code_enum structure + * return -EINVAL or zero on success + */ +static int ipipe_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + switch (code->pad) { + case IPIPE_PAD_SINK: + if (code->index >= ARRAY_SIZE(ipipe_fmts)) + return -EINVAL; + + code->code = ipipe_fmts[code->index]; + break; + + case IPIPE_PAD_SOURCE_VP: + /* FIXME: Forced format conversion inside IPIPE ? */ + if (code->index != 0) + return -EINVAL; + + code->code = V4L2_MBUS_FMT_UYVY8_1X16; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int ipipe_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct iss_ipipe_device *ipipe = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index != 0) + return -EINVAL; + + format.code = fse->code; + format.width = 1; + format.height = 1; + ipipe_try_format(ipipe, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY); + fse->min_width = format.width; + fse->min_height = format.height; + + if (format.code != fse->code) + return -EINVAL; + + format.code = fse->code; + format.width = -1; + format.height = -1; + ipipe_try_format(ipipe, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * ipipe_get_format - Retrieve the video format on a pad + * @sd : ISP IPIPE V4L2 subdevice + * @fh : V4L2 subdev file handle + * @fmt: Format + * + * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond + * to the format type. + */ +static int ipipe_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct iss_ipipe_device *ipipe = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __ipipe_get_format(ipipe, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + fmt->format = *format; + return 0; +} + +/* + * ipipe_set_format - Set the video format on a pad + * @sd : ISP IPIPE V4L2 subdevice + * @fh : V4L2 subdev file handle + * @fmt: Format + * + * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond + * to the format type. + */ +static int ipipe_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct iss_ipipe_device *ipipe = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __ipipe_get_format(ipipe, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + ipipe_try_format(ipipe, fh, fmt->pad, &fmt->format, fmt->which); + *format = fmt->format; + + /* Propagate the format from sink to source */ + if (fmt->pad == IPIPE_PAD_SINK) { + format = __ipipe_get_format(ipipe, fh, IPIPE_PAD_SOURCE_VP, + fmt->which); + *format = fmt->format; + ipipe_try_format(ipipe, fh, IPIPE_PAD_SOURCE_VP, format, + fmt->which); + } + + return 0; +} + +static int ipipe_link_validate(struct v4l2_subdev *sd, struct media_link *link, + struct v4l2_subdev_format *source_fmt, + struct v4l2_subdev_format *sink_fmt) +{ + /* Check if the two ends match */ + if (source_fmt->format.width != sink_fmt->format.width || + source_fmt->format.height != sink_fmt->format.height) + return -EPIPE; + + if (source_fmt->format.code != sink_fmt->format.code) + return -EPIPE; + + return 0; +} + +/* + * ipipe_init_formats - Initialize formats on all pads + * @sd: ISP IPIPE 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. + */ +static int ipipe_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format; + + memset(&format, 0, sizeof(format)); + format.pad = IPIPE_PAD_SINK; + format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + format.format.code = V4L2_MBUS_FMT_SGRBG10_1X10; + format.format.width = 4096; + format.format.height = 4096; + ipipe_set_format(sd, fh, &format); + + return 0; +} + +/* V4L2 subdev video operations */ +static const struct v4l2_subdev_video_ops ipipe_v4l2_video_ops = { + .s_stream = ipipe_set_stream, +}; + +/* V4L2 subdev pad operations */ +static const struct v4l2_subdev_pad_ops ipipe_v4l2_pad_ops = { + .enum_mbus_code = ipipe_enum_mbus_code, + .enum_frame_size = ipipe_enum_frame_size, + .get_fmt = ipipe_get_format, + .set_fmt = ipipe_set_format, + .link_validate = ipipe_link_validate, +}; + +/* V4L2 subdev operations */ +static const struct v4l2_subdev_ops ipipe_v4l2_ops = { + .video = &ipipe_v4l2_video_ops, + .pad = &ipipe_v4l2_pad_ops, +}; + +/* V4L2 subdev internal operations */ +static const struct v4l2_subdev_internal_ops ipipe_v4l2_internal_ops = { + .open = ipipe_init_formats, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +/* + * ipipe_link_setup - Setup IPIPE connections + * @entity: IPIPE media entity + * @local: Pad at the local end of the link + * @remote: Pad at the remote end of the link + * @flags: Link flags + * + * return -EINVAL or zero on success + */ +static int ipipe_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct iss_ipipe_device *ipipe = v4l2_get_subdevdata(sd); + struct iss_device *iss = to_iss_device(ipipe); + + switch (local->index | media_entity_type(remote->entity)) { + case IPIPE_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV: + /* Read from IPIPEIF. */ + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + ipipe->input = IPIPE_INPUT_NONE; + break; + } + + if (ipipe->input != IPIPE_INPUT_NONE) + return -EBUSY; + + if (remote->entity == &iss->ipipeif.subdev.entity) + ipipe->input = IPIPE_INPUT_IPIPEIF; + + break; + + case IPIPE_PAD_SOURCE_VP | MEDIA_ENT_T_V4L2_SUBDEV: + /* Send to RESIZER */ + if (flags & MEDIA_LNK_FL_ENABLED) { + if (ipipe->output & ~IPIPE_OUTPUT_VP) + return -EBUSY; + ipipe->output |= IPIPE_OUTPUT_VP; + } else { + ipipe->output &= ~IPIPE_OUTPUT_VP; + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* media operations */ +static const struct media_entity_operations ipipe_media_ops = { + .link_setup = ipipe_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +/* + * ipipe_init_entities - Initialize V4L2 subdev and media entity + * @ipipe: ISS ISP IPIPE module + * + * Return 0 on success and a negative error code on failure. + */ +static int ipipe_init_entities(struct iss_ipipe_device *ipipe) +{ + struct v4l2_subdev *sd = &ipipe->subdev; + struct media_pad *pads = ipipe->pads; + struct media_entity *me = &sd->entity; + int ret; + + ipipe->input = IPIPE_INPUT_NONE; + + v4l2_subdev_init(sd, &ipipe_v4l2_ops); + sd->internal_ops = &ipipe_v4l2_internal_ops; + strlcpy(sd->name, "OMAP4 ISS ISP IPIPE", sizeof(sd->name)); + sd->grp_id = 1 << 16; /* group ID for iss subdevs */ + v4l2_set_subdevdata(sd, ipipe); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + pads[IPIPE_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[IPIPE_PAD_SOURCE_VP].flags = MEDIA_PAD_FL_SOURCE; + + me->ops = &ipipe_media_ops; + ret = media_entity_init(me, IPIPE_PADS_NUM, pads, 0); + if (ret < 0) + return ret; + + ipipe_init_formats(sd, NULL); + + return 0; +} + +void omap4iss_ipipe_unregister_entities(struct iss_ipipe_device *ipipe) +{ + media_entity_cleanup(&ipipe->subdev.entity); + + v4l2_device_unregister_subdev(&ipipe->subdev); +} + +int omap4iss_ipipe_register_entities(struct iss_ipipe_device *ipipe, + struct v4l2_device *vdev) +{ + int ret; + + /* Register the subdev and video node. */ + ret = v4l2_device_register_subdev(vdev, &ipipe->subdev); + if (ret < 0) + goto error; + + return 0; + +error: + omap4iss_ipipe_unregister_entities(ipipe); + return ret; +} + +/* ----------------------------------------------------------------------------- + * ISP IPIPE initialisation and cleanup + */ + +/* + * omap4iss_ipipe_init - IPIPE module initialization. + * @iss: Device pointer specific to the OMAP4 ISS. + * + * TODO: Get the initialisation values from platform data. + * + * Return 0 on success or a negative error code otherwise. + */ +int omap4iss_ipipe_init(struct iss_device *iss) +{ + struct iss_ipipe_device *ipipe = &iss->ipipe; + + ipipe->state = ISS_PIPELINE_STREAM_STOPPED; + init_waitqueue_head(&ipipe->wait); + + return ipipe_init_entities(ipipe); +} + +/* + * omap4iss_ipipe_cleanup - IPIPE module cleanup. + * @iss: Device pointer specific to the OMAP4 ISS. + */ +void omap4iss_ipipe_cleanup(struct iss_device *iss) +{ + /* FIXME: are you sure there's nothing to do? */ +} diff --git a/drivers/staging/media/omap4iss/iss_ipipe.h b/drivers/staging/media/omap4iss/iss_ipipe.h new file mode 100644 index 000000000000..c22d9041f2a5 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_ipipe.h @@ -0,0 +1,67 @@ +/* + * TI OMAP4 ISS V4L2 Driver - ISP IPIPE module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.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 OMAP4_ISS_IPIPE_H +#define OMAP4_ISS_IPIPE_H + +#include "iss_video.h" + +enum ipipe_input_entity { + IPIPE_INPUT_NONE, + IPIPE_INPUT_IPIPEIF, +}; + +#define IPIPE_OUTPUT_VP (1 << 0) + +/* Sink and source IPIPE pads */ +#define IPIPE_PAD_SINK 0 +#define IPIPE_PAD_SOURCE_VP 1 +#define IPIPE_PADS_NUM 2 + +/* + * struct iss_ipipe_device - Structure for the IPIPE module to store its own + * information + * @subdev: V4L2 subdevice + * @pads: Sink and source media entity pads + * @formats: Active video formats + * @input: Active input + * @output: Active outputs + * @error: A hardware error occurred during capture + * @state: Streaming state + * @wait: Wait queue used to stop the module + * @stopping: Stopping state + */ +struct iss_ipipe_device { + struct v4l2_subdev subdev; + struct media_pad pads[IPIPE_PADS_NUM]; + struct v4l2_mbus_framefmt formats[IPIPE_PADS_NUM]; + + enum ipipe_input_entity input; + unsigned int output; + unsigned int error; + + enum iss_pipeline_stream_state state; + wait_queue_head_t wait; + atomic_t stopping; +}; + +struct iss_device; + +int omap4iss_ipipe_register_entities(struct iss_ipipe_device *ipipe, + struct v4l2_device *vdev); +void omap4iss_ipipe_unregister_entities(struct iss_ipipe_device *ipipe); + +int omap4iss_ipipe_init(struct iss_device *iss); +void omap4iss_ipipe_cleanup(struct iss_device *iss); + +#endif /* OMAP4_ISS_IPIPE_H */ diff --git a/drivers/staging/media/omap4iss/iss_ipipeif.c b/drivers/staging/media/omap4iss/iss_ipipeif.c new file mode 100644 index 000000000000..7bc145762499 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_ipipeif.c @@ -0,0 +1,849 @@ +/* + * TI OMAP4 ISS V4L2 Driver - ISP IPIPEIF module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.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/module.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/mm.h> +#include <linux/sched.h> + +#include "iss.h" +#include "iss_regs.h" +#include "iss_ipipeif.h" + +static const unsigned int ipipeif_fmts[] = { + V4L2_MBUS_FMT_SGRBG10_1X10, + V4L2_MBUS_FMT_SRGGB10_1X10, + V4L2_MBUS_FMT_SBGGR10_1X10, + V4L2_MBUS_FMT_SGBRG10_1X10, + V4L2_MBUS_FMT_UYVY8_1X16, + V4L2_MBUS_FMT_YUYV8_1X16, +}; + +/* + * ipipeif_print_status - Print current IPIPEIF Module register values. + * @ipipeif: Pointer to ISS ISP IPIPEIF device. + * + * Also prints other debug information stored in the IPIPEIF module. + */ +#define IPIPEIF_PRINT_REGISTER(iss, name)\ + dev_dbg(iss->dev, "###IPIPEIF " #name "=0x%08x\n", \ + iss_reg_read(iss, OMAP4_ISS_MEM_ISP_IPIPEIF, IPIPEIF_##name)) + +#define ISIF_PRINT_REGISTER(iss, name)\ + dev_dbg(iss->dev, "###ISIF " #name "=0x%08x\n", \ + iss_reg_read(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_##name)) + +#define ISP5_PRINT_REGISTER(iss, name)\ + dev_dbg(iss->dev, "###ISP5 " #name "=0x%08x\n", \ + iss_reg_read(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_##name)) + +static void ipipeif_print_status(struct iss_ipipeif_device *ipipeif) +{ + struct iss_device *iss = to_iss_device(ipipeif); + + dev_dbg(iss->dev, "-------------IPIPEIF Register dump-------------\n"); + + IPIPEIF_PRINT_REGISTER(iss, CFG1); + IPIPEIF_PRINT_REGISTER(iss, CFG2); + + ISIF_PRINT_REGISTER(iss, SYNCEN); + ISIF_PRINT_REGISTER(iss, CADU); + ISIF_PRINT_REGISTER(iss, CADL); + ISIF_PRINT_REGISTER(iss, MODESET); + ISIF_PRINT_REGISTER(iss, CCOLP); + ISIF_PRINT_REGISTER(iss, SPH); + ISIF_PRINT_REGISTER(iss, LNH); + ISIF_PRINT_REGISTER(iss, LNV); + ISIF_PRINT_REGISTER(iss, VDINT(0)); + ISIF_PRINT_REGISTER(iss, HSIZE); + + ISP5_PRINT_REGISTER(iss, SYSCONFIG); + ISP5_PRINT_REGISTER(iss, CTRL); + ISP5_PRINT_REGISTER(iss, IRQSTATUS(0)); + ISP5_PRINT_REGISTER(iss, IRQENABLE_SET(0)); + ISP5_PRINT_REGISTER(iss, IRQENABLE_CLR(0)); + + dev_dbg(iss->dev, "-----------------------------------------------\n"); +} + +static void ipipeif_write_enable(struct iss_ipipeif_device *ipipeif, u8 enable) +{ + struct iss_device *iss = to_iss_device(ipipeif); + + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_SYNCEN, + ISIF_SYNCEN_DWEN, enable ? ISIF_SYNCEN_DWEN : 0); +} + +/* + * ipipeif_enable - Enable/Disable IPIPEIF. + * @enable: enable flag + * + */ +static void ipipeif_enable(struct iss_ipipeif_device *ipipeif, u8 enable) +{ + struct iss_device *iss = to_iss_device(ipipeif); + + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_SYNCEN, + ISIF_SYNCEN_SYEN, enable ? ISIF_SYNCEN_SYEN : 0); +} + +/* ----------------------------------------------------------------------------- + * Format- and pipeline-related configuration helpers + */ + +/* + * ipipeif_set_outaddr - Set memory address to save output image + * @ipipeif: Pointer to ISP IPIPEIF device. + * @addr: 32-bit memory address aligned on 32 byte boundary. + * + * Sets the memory address where the output will be saved. + */ +static void ipipeif_set_outaddr(struct iss_ipipeif_device *ipipeif, u32 addr) +{ + struct iss_device *iss = to_iss_device(ipipeif); + + /* Save address splitted in Base Address H & L */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_CADU, + (addr >> (16 + 5)) & ISIF_CADU_MASK); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_CADL, + (addr >> 5) & ISIF_CADL_MASK); +} + +static void ipipeif_configure(struct iss_ipipeif_device *ipipeif) +{ + struct iss_device *iss = to_iss_device(ipipeif); + const struct iss_format_info *info; + struct v4l2_mbus_framefmt *format; + u32 isif_ccolp = 0; + + omap4iss_configure_bridge(iss, ipipeif->input); + + /* IPIPEIF_PAD_SINK */ + format = &ipipeif->formats[IPIPEIF_PAD_SINK]; + + /* IPIPEIF with YUV422 input from ISIF */ + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_IPIPEIF, IPIPEIF_CFG1, + IPIPEIF_CFG1_INPSRC1_MASK | IPIPEIF_CFG1_INPSRC2_MASK); + + /* Select ISIF/IPIPEIF input format */ + switch (format->code) { + case V4L2_MBUS_FMT_UYVY8_1X16: + case V4L2_MBUS_FMT_YUYV8_1X16: + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_MODESET, + ISIF_MODESET_CCDMD | ISIF_MODESET_INPMOD_MASK | + ISIF_MODESET_CCDW_MASK, + ISIF_MODESET_INPMOD_YCBCR16); + + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_IPIPEIF, IPIPEIF_CFG2, + IPIPEIF_CFG2_YUV8, IPIPEIF_CFG2_YUV16); + + break; + case V4L2_MBUS_FMT_SGRBG10_1X10: + isif_ccolp = ISIF_CCOLP_CP0_F0_GR | + ISIF_CCOLP_CP1_F0_R | + ISIF_CCOLP_CP2_F0_B | + ISIF_CCOLP_CP3_F0_GB; + goto cont_raw; + case V4L2_MBUS_FMT_SRGGB10_1X10: + isif_ccolp = ISIF_CCOLP_CP0_F0_R | + ISIF_CCOLP_CP1_F0_GR | + ISIF_CCOLP_CP2_F0_GB | + ISIF_CCOLP_CP3_F0_B; + goto cont_raw; + case V4L2_MBUS_FMT_SBGGR10_1X10: + isif_ccolp = ISIF_CCOLP_CP0_F0_B | + ISIF_CCOLP_CP1_F0_GB | + ISIF_CCOLP_CP2_F0_GR | + ISIF_CCOLP_CP3_F0_R; + goto cont_raw; + case V4L2_MBUS_FMT_SGBRG10_1X10: + isif_ccolp = ISIF_CCOLP_CP0_F0_GB | + ISIF_CCOLP_CP1_F0_B | + ISIF_CCOLP_CP2_F0_R | + ISIF_CCOLP_CP3_F0_GR; +cont_raw: + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_IPIPEIF, IPIPEIF_CFG2, + IPIPEIF_CFG2_YUV16); + + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_MODESET, + ISIF_MODESET_CCDMD | ISIF_MODESET_INPMOD_MASK | + ISIF_MODESET_CCDW_MASK, ISIF_MODESET_INPMOD_RAW | + ISIF_MODESET_CCDW_2BIT); + + info = omap4iss_video_format_info(format->code); + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_CGAMMAWD, + ISIF_CGAMMAWD_GWDI_MASK, + ISIF_CGAMMAWD_GWDI(info->bpp)); + + /* Set RAW Bayer pattern */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_CCOLP, + isif_ccolp); + break; + } + + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_SPH, 0 & ISIF_SPH_MASK); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_LNH, + (format->width - 1) & ISIF_LNH_MASK); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_LNV, + (format->height - 1) & ISIF_LNV_MASK); + + /* Generate ISIF0 on the last line of the image */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_VDINT(0), + format->height - 1); + + /* IPIPEIF_PAD_SOURCE_ISIF_SF */ + format = &ipipeif->formats[IPIPEIF_PAD_SOURCE_ISIF_SF]; + + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_HSIZE, + (ipipeif->video_out.bpl_value >> 5) & + ISIF_HSIZE_HSIZE_MASK); + + /* IPIPEIF_PAD_SOURCE_VP */ + /* Do nothing? */ +} + +/* ----------------------------------------------------------------------------- + * Interrupt handling + */ + +static void ipipeif_isr_buffer(struct iss_ipipeif_device *ipipeif) +{ + struct iss_buffer *buffer; + + /* The ISIF generates VD0 interrupts even when writes are disabled. + * deal with it anyway). Disabling the ISIF when no buffer is available + * is thus not be enough, we need to handle the situation explicitly. + */ + if (list_empty(&ipipeif->video_out.dmaqueue)) + return; + + ipipeif_write_enable(ipipeif, 0); + + buffer = omap4iss_video_buffer_next(&ipipeif->video_out); + if (buffer == NULL) + return; + + ipipeif_set_outaddr(ipipeif, buffer->iss_addr); + + ipipeif_write_enable(ipipeif, 1); +} + +/* + * ipipeif_isif0_isr - Handle ISIF0 event + * @ipipeif: Pointer to ISP IPIPEIF device. + * + * Executes LSC deferred enablement before next frame starts. + */ +static void ipipeif_isif0_isr(struct iss_ipipeif_device *ipipeif) +{ + struct iss_pipeline *pipe = + to_iss_pipeline(&ipipeif->subdev.entity); + if (pipe->do_propagation) + atomic_inc(&pipe->frame_number); + + if (ipipeif->output & IPIPEIF_OUTPUT_MEMORY) + ipipeif_isr_buffer(ipipeif); +} + +/* + * omap4iss_ipipeif_isr - Configure ipipeif during interframe time. + * @ipipeif: Pointer to ISP IPIPEIF device. + * @events: IPIPEIF events + */ +void omap4iss_ipipeif_isr(struct iss_ipipeif_device *ipipeif, u32 events) +{ + if (omap4iss_module_sync_is_stopping(&ipipeif->wait, + &ipipeif->stopping)) + return; + + if (events & ISP5_IRQ_ISIF_INT(0)) + ipipeif_isif0_isr(ipipeif); +} + +/* ----------------------------------------------------------------------------- + * ISP video operations + */ + +static int ipipeif_video_queue(struct iss_video *video, + struct iss_buffer *buffer) +{ + struct iss_ipipeif_device *ipipeif = container_of(video, + struct iss_ipipeif_device, video_out); + + if (!(ipipeif->output & IPIPEIF_OUTPUT_MEMORY)) + return -ENODEV; + + ipipeif_set_outaddr(ipipeif, buffer->iss_addr); + + /* + * If streaming was enabled before there was a buffer queued + * or underrun happened in the ISR, the hardware was not enabled + * and DMA queue flag ISS_VIDEO_DMAQUEUE_UNDERRUN is still set. + * Enable it now. + */ + if (video->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_UNDERRUN) { + if (ipipeif->output & IPIPEIF_OUTPUT_MEMORY) + ipipeif_write_enable(ipipeif, 1); + ipipeif_enable(ipipeif, 1); + iss_video_dmaqueue_flags_clr(video); + } + + return 0; +} + +static const struct iss_video_operations ipipeif_video_ops = { + .queue = ipipeif_video_queue, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +#define IPIPEIF_DRV_SUBCLK_MASK (OMAP4_ISS_ISP_SUBCLK_IPIPEIF |\ + OMAP4_ISS_ISP_SUBCLK_ISIF) +/* + * ipipeif_set_stream - Enable/Disable streaming on the IPIPEIF module + * @sd: ISP IPIPEIF V4L2 subdevice + * @enable: Enable/disable stream + */ +static int ipipeif_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct iss_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct iss_device *iss = to_iss_device(ipipeif); + struct iss_video *video_out = &ipipeif->video_out; + int ret = 0; + + if (ipipeif->state == ISS_PIPELINE_STREAM_STOPPED) { + if (enable == ISS_PIPELINE_STREAM_STOPPED) + return 0; + + omap4iss_isp_subclk_enable(iss, IPIPEIF_DRV_SUBCLK_MASK); + } + + switch (enable) { + case ISS_PIPELINE_STREAM_CONTINUOUS: + + ipipeif_configure(ipipeif); + ipipeif_print_status(ipipeif); + + /* + * When outputting to memory with no buffer available, let the + * buffer queue handler start the hardware. A DMA queue flag + * ISS_VIDEO_DMAQUEUE_QUEUED will be set as soon as there is + * a buffer available. + */ + if (ipipeif->output & IPIPEIF_OUTPUT_MEMORY && + !(video_out->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_QUEUED)) + break; + + atomic_set(&ipipeif->stopping, 0); + if (ipipeif->output & IPIPEIF_OUTPUT_MEMORY) + ipipeif_write_enable(ipipeif, 1); + ipipeif_enable(ipipeif, 1); + iss_video_dmaqueue_flags_clr(video_out); + break; + + case ISS_PIPELINE_STREAM_STOPPED: + if (ipipeif->state == ISS_PIPELINE_STREAM_STOPPED) + return 0; + if (omap4iss_module_sync_idle(&sd->entity, &ipipeif->wait, + &ipipeif->stopping)) + ret = -ETIMEDOUT; + + if (ipipeif->output & IPIPEIF_OUTPUT_MEMORY) + ipipeif_write_enable(ipipeif, 0); + ipipeif_enable(ipipeif, 0); + omap4iss_isp_subclk_disable(iss, IPIPEIF_DRV_SUBCLK_MASK); + iss_video_dmaqueue_flags_clr(video_out); + break; + } + + ipipeif->state = enable; + return ret; +} + +static struct v4l2_mbus_framefmt * +__ipipeif_get_format(struct iss_ipipeif_device *ipipeif, + struct v4l2_subdev_fh *fh, unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(fh, pad); + else + return &ipipeif->formats[pad]; +} + +/* + * ipipeif_try_format - Try video format on a pad + * @ipipeif: ISS IPIPEIF device + * @fh : V4L2 subdev file handle + * @pad: Pad number + * @fmt: Format + */ +static void +ipipeif_try_format(struct iss_ipipeif_device *ipipeif, + 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 width = fmt->width; + unsigned int height = fmt->height; + unsigned int i; + + switch (pad) { + case IPIPEIF_PAD_SINK: + /* TODO: If the IPIPEIF output formatter pad is connected + * directly to the resizer, only YUV formats can be used. + */ + for (i = 0; i < ARRAY_SIZE(ipipeif_fmts); i++) { + if (fmt->code == ipipeif_fmts[i]) + break; + } + + /* If not found, use SGRBG10 as default */ + if (i >= ARRAY_SIZE(ipipeif_fmts)) + fmt->code = V4L2_MBUS_FMT_SGRBG10_1X10; + + /* Clamp the input size. */ + fmt->width = clamp_t(u32, width, 1, 8192); + fmt->height = clamp_t(u32, height, 1, 8192); + break; + + case IPIPEIF_PAD_SOURCE_ISIF_SF: + format = __ipipeif_get_format(ipipeif, fh, IPIPEIF_PAD_SINK, + which); + memcpy(fmt, format, sizeof(*fmt)); + + /* The data formatter truncates the number of horizontal output + * pixels to a multiple of 16. To avoid clipping data, allow + * callers to request an output size bigger than the input size + * up to the nearest multiple of 16. + */ + fmt->width = clamp_t(u32, width, 32, (fmt->width + 15) & ~15); + fmt->width &= ~15; + fmt->height = clamp_t(u32, height, 32, fmt->height); + break; + + case IPIPEIF_PAD_SOURCE_VP: + format = __ipipeif_get_format(ipipeif, fh, IPIPEIF_PAD_SINK, + which); + memcpy(fmt, format, sizeof(*fmt)); + + fmt->width = clamp_t(u32, width, 32, fmt->width); + fmt->height = clamp_t(u32, height, 32, fmt->height); + break; + } + + /* Data is written to memory unpacked, each 10-bit or 12-bit pixel is + * stored on 2 bytes. + */ + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->field = V4L2_FIELD_NONE; +} + +/* + * ipipeif_enum_mbus_code - Handle pixel format enumeration + * @sd : pointer to v4l2 subdev structure + * @fh : V4L2 subdev file handle + * @code : pointer to v4l2_subdev_mbus_code_enum structure + * return -EINVAL or zero on success + */ +static int ipipeif_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct iss_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + switch (code->pad) { + case IPIPEIF_PAD_SINK: + if (code->index >= ARRAY_SIZE(ipipeif_fmts)) + return -EINVAL; + + code->code = ipipeif_fmts[code->index]; + break; + + case IPIPEIF_PAD_SOURCE_ISIF_SF: + case IPIPEIF_PAD_SOURCE_VP: + /* No format conversion inside IPIPEIF */ + if (code->index != 0) + return -EINVAL; + + format = __ipipeif_get_format(ipipeif, fh, IPIPEIF_PAD_SINK, + V4L2_SUBDEV_FORMAT_TRY); + + code->code = format->code; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int ipipeif_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct iss_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index != 0) + return -EINVAL; + + format.code = fse->code; + format.width = 1; + format.height = 1; + ipipeif_try_format(ipipeif, fh, fse->pad, &format, + V4L2_SUBDEV_FORMAT_TRY); + fse->min_width = format.width; + fse->min_height = format.height; + + if (format.code != fse->code) + return -EINVAL; + + format.code = fse->code; + format.width = -1; + format.height = -1; + ipipeif_try_format(ipipeif, fh, fse->pad, &format, + V4L2_SUBDEV_FORMAT_TRY); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * ipipeif_get_format - Retrieve the video format on a pad + * @sd : ISP IPIPEIF V4L2 subdevice + * @fh : V4L2 subdev file handle + * @fmt: Format + * + * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond + * to the format type. + */ +static int ipipeif_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct iss_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __ipipeif_get_format(ipipeif, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + fmt->format = *format; + return 0; +} + +/* + * ipipeif_set_format - Set the video format on a pad + * @sd : ISP IPIPEIF V4L2 subdevice + * @fh : V4L2 subdev file handle + * @fmt: Format + * + * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond + * to the format type. + */ +static int ipipeif_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct iss_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __ipipeif_get_format(ipipeif, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + ipipeif_try_format(ipipeif, fh, fmt->pad, &fmt->format, fmt->which); + *format = fmt->format; + + /* Propagate the format from sink to source */ + if (fmt->pad == IPIPEIF_PAD_SINK) { + format = __ipipeif_get_format(ipipeif, fh, + IPIPEIF_PAD_SOURCE_ISIF_SF, + fmt->which); + *format = fmt->format; + ipipeif_try_format(ipipeif, fh, IPIPEIF_PAD_SOURCE_ISIF_SF, + format, fmt->which); + + format = __ipipeif_get_format(ipipeif, fh, + IPIPEIF_PAD_SOURCE_VP, + fmt->which); + *format = fmt->format; + ipipeif_try_format(ipipeif, fh, IPIPEIF_PAD_SOURCE_VP, format, + fmt->which); + } + + return 0; +} + +static int ipipeif_link_validate(struct v4l2_subdev *sd, + struct media_link *link, + struct v4l2_subdev_format *source_fmt, + struct v4l2_subdev_format *sink_fmt) +{ + /* Check if the two ends match */ + if (source_fmt->format.width != sink_fmt->format.width || + source_fmt->format.height != sink_fmt->format.height) + return -EPIPE; + + if (source_fmt->format.code != sink_fmt->format.code) + return -EPIPE; + + return 0; +} + +/* + * ipipeif_init_formats - Initialize formats on all pads + * @sd: ISP IPIPEIF 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. + */ +static int ipipeif_init_formats(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format; + + memset(&format, 0, sizeof(format)); + format.pad = IPIPEIF_PAD_SINK; + format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + format.format.code = V4L2_MBUS_FMT_SGRBG10_1X10; + format.format.width = 4096; + format.format.height = 4096; + ipipeif_set_format(sd, fh, &format); + + return 0; +} + +/* V4L2 subdev video operations */ +static const struct v4l2_subdev_video_ops ipipeif_v4l2_video_ops = { + .s_stream = ipipeif_set_stream, +}; + +/* V4L2 subdev pad operations */ +static const struct v4l2_subdev_pad_ops ipipeif_v4l2_pad_ops = { + .enum_mbus_code = ipipeif_enum_mbus_code, + .enum_frame_size = ipipeif_enum_frame_size, + .get_fmt = ipipeif_get_format, + .set_fmt = ipipeif_set_format, + .link_validate = ipipeif_link_validate, +}; + +/* V4L2 subdev operations */ +static const struct v4l2_subdev_ops ipipeif_v4l2_ops = { + .video = &ipipeif_v4l2_video_ops, + .pad = &ipipeif_v4l2_pad_ops, +}; + +/* V4L2 subdev internal operations */ +static const struct v4l2_subdev_internal_ops ipipeif_v4l2_internal_ops = { + .open = ipipeif_init_formats, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +/* + * ipipeif_link_setup - Setup IPIPEIF connections + * @entity: IPIPEIF media entity + * @local: Pad at the local end of the link + * @remote: Pad at the remote end of the link + * @flags: Link flags + * + * return -EINVAL or zero on success + */ +static int ipipeif_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct iss_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct iss_device *iss = to_iss_device(ipipeif); + + switch (local->index | media_entity_type(remote->entity)) { + case IPIPEIF_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV: + /* Read from the sensor CSI2a or CSI2b. */ + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + ipipeif->input = IPIPEIF_INPUT_NONE; + break; + } + + if (ipipeif->input != IPIPEIF_INPUT_NONE) + return -EBUSY; + + if (remote->entity == &iss->csi2a.subdev.entity) + ipipeif->input = IPIPEIF_INPUT_CSI2A; + else if (remote->entity == &iss->csi2b.subdev.entity) + ipipeif->input = IPIPEIF_INPUT_CSI2B; + + break; + + case IPIPEIF_PAD_SOURCE_ISIF_SF | MEDIA_ENT_T_DEVNODE: + /* Write to memory */ + if (flags & MEDIA_LNK_FL_ENABLED) { + if (ipipeif->output & ~IPIPEIF_OUTPUT_MEMORY) + return -EBUSY; + ipipeif->output |= IPIPEIF_OUTPUT_MEMORY; + } else { + ipipeif->output &= ~IPIPEIF_OUTPUT_MEMORY; + } + break; + + case IPIPEIF_PAD_SOURCE_VP | MEDIA_ENT_T_V4L2_SUBDEV: + /* Send to IPIPE/RESIZER */ + if (flags & MEDIA_LNK_FL_ENABLED) { + if (ipipeif->output & ~IPIPEIF_OUTPUT_VP) + return -EBUSY; + ipipeif->output |= IPIPEIF_OUTPUT_VP; + } else { + ipipeif->output &= ~IPIPEIF_OUTPUT_VP; + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* media operations */ +static const struct media_entity_operations ipipeif_media_ops = { + .link_setup = ipipeif_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +/* + * ipipeif_init_entities - Initialize V4L2 subdev and media entity + * @ipipeif: ISS ISP IPIPEIF module + * + * Return 0 on success and a negative error code on failure. + */ +static int ipipeif_init_entities(struct iss_ipipeif_device *ipipeif) +{ + struct v4l2_subdev *sd = &ipipeif->subdev; + struct media_pad *pads = ipipeif->pads; + struct media_entity *me = &sd->entity; + int ret; + + ipipeif->input = IPIPEIF_INPUT_NONE; + + v4l2_subdev_init(sd, &ipipeif_v4l2_ops); + sd->internal_ops = &ipipeif_v4l2_internal_ops; + strlcpy(sd->name, "OMAP4 ISS ISP IPIPEIF", sizeof(sd->name)); + sd->grp_id = 1 << 16; /* group ID for iss subdevs */ + v4l2_set_subdevdata(sd, ipipeif); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + pads[IPIPEIF_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[IPIPEIF_PAD_SOURCE_ISIF_SF].flags = MEDIA_PAD_FL_SOURCE; + pads[IPIPEIF_PAD_SOURCE_VP].flags = MEDIA_PAD_FL_SOURCE; + + me->ops = &ipipeif_media_ops; + ret = media_entity_init(me, IPIPEIF_PADS_NUM, pads, 0); + if (ret < 0) + return ret; + + ipipeif_init_formats(sd, NULL); + + ipipeif->video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + ipipeif->video_out.ops = &ipipeif_video_ops; + ipipeif->video_out.iss = to_iss_device(ipipeif); + ipipeif->video_out.capture_mem = PAGE_ALIGN(4096 * 4096) * 3; + ipipeif->video_out.bpl_alignment = 32; + ipipeif->video_out.bpl_zero_padding = 1; + ipipeif->video_out.bpl_max = 0x1ffe0; + + ret = omap4iss_video_init(&ipipeif->video_out, "ISP IPIPEIF"); + if (ret < 0) + return ret; + + /* Connect the IPIPEIF subdev to the video node. */ + ret = media_entity_create_link(&ipipeif->subdev.entity, + IPIPEIF_PAD_SOURCE_ISIF_SF, + &ipipeif->video_out.video.entity, 0, 0); + if (ret < 0) + return ret; + + return 0; +} + +void omap4iss_ipipeif_unregister_entities(struct iss_ipipeif_device *ipipeif) +{ + media_entity_cleanup(&ipipeif->subdev.entity); + + v4l2_device_unregister_subdev(&ipipeif->subdev); + omap4iss_video_unregister(&ipipeif->video_out); +} + +int omap4iss_ipipeif_register_entities(struct iss_ipipeif_device *ipipeif, + struct v4l2_device *vdev) +{ + int ret; + + /* Register the subdev and video node. */ + ret = v4l2_device_register_subdev(vdev, &ipipeif->subdev); + if (ret < 0) + goto error; + + ret = omap4iss_video_register(&ipipeif->video_out, vdev); + if (ret < 0) + goto error; + + return 0; + +error: + omap4iss_ipipeif_unregister_entities(ipipeif); + return ret; +} + +/* ----------------------------------------------------------------------------- + * ISP IPIPEIF initialisation and cleanup + */ + +/* + * omap4iss_ipipeif_init - IPIPEIF module initialization. + * @iss: Device pointer specific to the OMAP4 ISS. + * + * TODO: Get the initialisation values from platform data. + * + * Return 0 on success or a negative error code otherwise. + */ +int omap4iss_ipipeif_init(struct iss_device *iss) +{ + struct iss_ipipeif_device *ipipeif = &iss->ipipeif; + + ipipeif->state = ISS_PIPELINE_STREAM_STOPPED; + init_waitqueue_head(&ipipeif->wait); + + return ipipeif_init_entities(ipipeif); +} + +/* + * omap4iss_ipipeif_cleanup - IPIPEIF module cleanup. + * @iss: Device pointer specific to the OMAP4 ISS. + */ +void omap4iss_ipipeif_cleanup(struct iss_device *iss) +{ + /* FIXME: are you sure there's nothing to do? */ +} diff --git a/drivers/staging/media/omap4iss/iss_ipipeif.h b/drivers/staging/media/omap4iss/iss_ipipeif.h new file mode 100644 index 000000000000..cbdccb982eee --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_ipipeif.h @@ -0,0 +1,92 @@ +/* + * TI OMAP4 ISS V4L2 Driver - ISP IPIPEIF module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.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 OMAP4_ISS_IPIPEIF_H +#define OMAP4_ISS_IPIPEIF_H + +#include "iss_video.h" + +enum ipipeif_input_entity { + IPIPEIF_INPUT_NONE, + IPIPEIF_INPUT_CSI2A, + IPIPEIF_INPUT_CSI2B +}; + +#define IPIPEIF_OUTPUT_MEMORY (1 << 0) +#define IPIPEIF_OUTPUT_VP (1 << 1) + +/* Sink and source IPIPEIF pads */ +#define IPIPEIF_PAD_SINK 0 +#define IPIPEIF_PAD_SOURCE_ISIF_SF 1 +#define IPIPEIF_PAD_SOURCE_VP 2 +#define IPIPEIF_PADS_NUM 3 + +/* + * struct iss_ipipeif_device - Structure for the IPIPEIF module to store its own + * information + * @subdev: V4L2 subdevice + * @pads: Sink and source media entity pads + * @formats: Active video formats + * @input: Active input + * @output: Active outputs + * @video_out: Output video node + * @error: A hardware error occurred during capture + * @alaw: A-law compression enabled (1) or disabled (0) + * @lpf: Low pass filter enabled (1) or disabled (0) + * @obclamp: Optical-black clamp enabled (1) or disabled (0) + * @fpc_en: Faulty pixels correction enabled (1) or disabled (0) + * @blcomp: Black level compensation configuration + * @clamp: Optical-black or digital clamp configuration + * @fpc: Faulty pixels correction configuration + * @lsc: Lens shading compensation configuration + * @update: Bitmask of controls to update during the next interrupt + * @shadow_update: Controls update in progress by userspace + * @syncif: Interface synchronization configuration + * @vpcfg: Video port configuration + * @underrun: A buffer underrun occurred and a new buffer has been queued + * @state: Streaming state + * @lock: Serializes shadow_update with interrupt handler + * @wait: Wait queue used to stop the module + * @stopping: Stopping state + * @ioctl_lock: Serializes ioctl calls and LSC requests freeing + */ +struct iss_ipipeif_device { + struct v4l2_subdev subdev; + struct media_pad pads[IPIPEIF_PADS_NUM]; + struct v4l2_mbus_framefmt formats[IPIPEIF_PADS_NUM]; + + enum ipipeif_input_entity input; + unsigned int output; + struct iss_video video_out; + unsigned int error; + + enum iss_pipeline_stream_state state; + wait_queue_head_t wait; + atomic_t stopping; +}; + +struct iss_device; + +int omap4iss_ipipeif_init(struct iss_device *iss); +void omap4iss_ipipeif_cleanup(struct iss_device *iss); +int omap4iss_ipipeif_register_entities(struct iss_ipipeif_device *ipipeif, + struct v4l2_device *vdev); +void omap4iss_ipipeif_unregister_entities(struct iss_ipipeif_device *ipipeif); + +int omap4iss_ipipeif_busy(struct iss_ipipeif_device *ipipeif); +void omap4iss_ipipeif_isr(struct iss_ipipeif_device *ipipeif, u32 events); +void omap4iss_ipipeif_restore_context(struct iss_device *iss); +void omap4iss_ipipeif_max_rate(struct iss_ipipeif_device *ipipeif, + unsigned int *max_rate); + +#endif /* OMAP4_ISS_IPIPEIF_H */ diff --git a/drivers/staging/media/omap4iss/iss_regs.h b/drivers/staging/media/omap4iss/iss_regs.h new file mode 100644 index 000000000000..efd0291a86f7 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_regs.h @@ -0,0 +1,901 @@ +/* + * TI OMAP4 ISS V4L2 Driver - Register defines + * + * Copyright (C) 2012 Texas Instruments. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.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 _OMAP4_ISS_REGS_H_ +#define _OMAP4_ISS_REGS_H_ + +/* ISS */ +#define ISS_HL_REVISION 0x0 + +#define ISS_HL_SYSCONFIG 0x10 +#define ISS_HL_SYSCONFIG_IDLEMODE_SHIFT 2 +#define ISS_HL_SYSCONFIG_IDLEMODE_FORCEIDLE 0x0 +#define ISS_HL_SYSCONFIG_IDLEMODE_NOIDLE 0x1 +#define ISS_HL_SYSCONFIG_IDLEMODE_SMARTIDLE 0x2 +#define ISS_HL_SYSCONFIG_SOFTRESET (1 << 0) + +#define ISS_HL_IRQSTATUS_RAW(i) (0x20 + (0x10 * (i))) +#define ISS_HL_IRQSTATUS(i) (0x24 + (0x10 * (i))) +#define ISS_HL_IRQENABLE_SET(i) (0x28 + (0x10 * (i))) +#define ISS_HL_IRQENABLE_CLR(i) (0x2c + (0x10 * (i))) + +#define ISS_HL_IRQ_HS_VS (1 << 17) +#define ISS_HL_IRQ_SIMCOP(i) (1 << (12 + (i))) +#define ISS_HL_IRQ_BTE (1 << 11) +#define ISS_HL_IRQ_CBUFF (1 << 10) +#define ISS_HL_IRQ_CCP2(i) (1 << ((i) > 3 ? 16 : 14 + (i))) +#define ISS_HL_IRQ_CSIB (1 << 5) +#define ISS_HL_IRQ_CSIA (1 << 4) +#define ISS_HL_IRQ_ISP(i) (1 << (i)) + +#define ISS_CTRL 0x80 +#define ISS_CTRL_CLK_DIV_MASK (3 << 4) +#define ISS_CTRL_INPUT_SEL_MASK (3 << 2) +#define ISS_CTRL_INPUT_SEL_CSI2A (0 << 2) +#define ISS_CTRL_INPUT_SEL_CSI2B (1 << 2) +#define ISS_CTRL_SYNC_DETECT_VS_RAISING (3 << 0) + +#define ISS_CLKCTRL 0x84 +#define ISS_CLKCTRL_VPORT2_CLK (1 << 30) +#define ISS_CLKCTRL_VPORT1_CLK (1 << 29) +#define ISS_CLKCTRL_VPORT0_CLK (1 << 28) +#define ISS_CLKCTRL_CCP2 (1 << 4) +#define ISS_CLKCTRL_CSI2_B (1 << 3) +#define ISS_CLKCTRL_CSI2_A (1 << 2) +#define ISS_CLKCTRL_ISP (1 << 1) +#define ISS_CLKCTRL_SIMCOP (1 << 0) + +#define ISS_CLKSTAT 0x88 +#define ISS_CLKSTAT_VPORT2_CLK (1 << 30) +#define ISS_CLKSTAT_VPORT1_CLK (1 << 29) +#define ISS_CLKSTAT_VPORT0_CLK (1 << 28) +#define ISS_CLKSTAT_CCP2 (1 << 4) +#define ISS_CLKSTAT_CSI2_B (1 << 3) +#define ISS_CLKSTAT_CSI2_A (1 << 2) +#define ISS_CLKSTAT_ISP (1 << 1) +#define ISS_CLKSTAT_SIMCOP (1 << 0) + +#define ISS_PM_STATUS 0x8c +#define ISS_PM_STATUS_CBUFF_PM_MASK (3 << 12) +#define ISS_PM_STATUS_BTE_PM_MASK (3 << 10) +#define ISS_PM_STATUS_SIMCOP_PM_MASK (3 << 8) +#define ISS_PM_STATUS_ISP_PM_MASK (3 << 6) +#define ISS_PM_STATUS_CCP2_PM_MASK (3 << 4) +#define ISS_PM_STATUS_CSI2_B_PM_MASK (3 << 2) +#define ISS_PM_STATUS_CSI2_A_PM_MASK (3 << 0) + +#define REGISTER0 0x0 +#define REGISTER0_HSCLOCKCONFIG (1 << 24) +#define REGISTER0_THS_TERM_MASK (0xff << 8) +#define REGISTER0_THS_TERM_SHIFT 8 +#define REGISTER0_THS_SETTLE_MASK (0xff << 0) +#define REGISTER0_THS_SETTLE_SHIFT 0 + +#define REGISTER1 0x4 +#define REGISTER1_RESET_DONE_CTRLCLK (1 << 29) +#define REGISTER1_CLOCK_MISS_DETECTOR_STATUS (1 << 25) +#define REGISTER1_TCLK_TERM_MASK (0x3f << 18) +#define REGISTER1_TCLK_TERM_SHIFT 18 +#define REGISTER1_DPHY_HS_SYNC_PATTERN_SHIFT 10 +#define REGISTER1_CTRLCLK_DIV_FACTOR_MASK (0x3 << 8) +#define REGISTER1_CTRLCLK_DIV_FACTOR_SHIFT 8 +#define REGISTER1_TCLK_SETTLE_MASK (0xff << 0) +#define REGISTER1_TCLK_SETTLE_SHIFT 0 + +#define REGISTER2 0x8 + +#define CSI2_SYSCONFIG 0x10 +#define CSI2_SYSCONFIG_MSTANDBY_MODE_MASK (3 << 12) +#define CSI2_SYSCONFIG_MSTANDBY_MODE_FORCE (0 << 12) +#define CSI2_SYSCONFIG_MSTANDBY_MODE_NO (1 << 12) +#define CSI2_SYSCONFIG_MSTANDBY_MODE_SMART (2 << 12) +#define CSI2_SYSCONFIG_SOFT_RESET (1 << 1) +#define CSI2_SYSCONFIG_AUTO_IDLE (1 << 0) + +#define CSI2_SYSSTATUS 0x14 +#define CSI2_SYSSTATUS_RESET_DONE (1 << 0) + +#define CSI2_IRQSTATUS 0x18 +#define CSI2_IRQENABLE 0x1c + +/* Shared bits across CSI2_IRQENABLE and IRQSTATUS */ + +#define CSI2_IRQ_OCP_ERR (1 << 14) +#define CSI2_IRQ_SHORT_PACKET (1 << 13) +#define CSI2_IRQ_ECC_CORRECTION (1 << 12) +#define CSI2_IRQ_ECC_NO_CORRECTION (1 << 11) +#define CSI2_IRQ_COMPLEXIO_ERR (1 << 9) +#define CSI2_IRQ_FIFO_OVF (1 << 8) +#define CSI2_IRQ_CONTEXT0 (1 << 0) + +#define CSI2_CTRL 0x40 +#define CSI2_CTRL_MFLAG_LEVH_MASK (7 << 20) +#define CSI2_CTRL_MFLAG_LEVH_SHIFT 20 +#define CSI2_CTRL_MFLAG_LEVL_MASK (7 << 17) +#define CSI2_CTRL_MFLAG_LEVL_SHIFT 17 +#define CSI2_CTRL_BURST_SIZE_EXPAND (1 << 16) +#define CSI2_CTRL_VP_CLK_EN (1 << 15) +#define CSI2_CTRL_NON_POSTED_WRITE (1 << 13) +#define CSI2_CTRL_VP_ONLY_EN (1 << 11) +#define CSI2_CTRL_VP_OUT_CTRL_MASK (3 << 8) +#define CSI2_CTRL_VP_OUT_CTRL_SHIFT 8 +#define CSI2_CTRL_DBG_EN (1 << 7) +#define CSI2_CTRL_BURST_SIZE_MASK (3 << 5) +#define CSI2_CTRL_ENDIANNESS (1 << 4) +#define CSI2_CTRL_FRAME (1 << 3) +#define CSI2_CTRL_ECC_EN (1 << 2) +#define CSI2_CTRL_IF_EN (1 << 0) + +#define CSI2_DBG_H 0x44 + +#define CSI2_COMPLEXIO_CFG 0x50 +#define CSI2_COMPLEXIO_CFG_RESET_CTRL (1 << 30) +#define CSI2_COMPLEXIO_CFG_RESET_DONE (1 << 29) +#define CSI2_COMPLEXIO_CFG_PWD_CMD_MASK (3 << 27) +#define CSI2_COMPLEXIO_CFG_PWD_CMD_OFF (0 << 27) +#define CSI2_COMPLEXIO_CFG_PWD_CMD_ON (1 << 27) +#define CSI2_COMPLEXIO_CFG_PWD_CMD_ULP (2 << 27) +#define CSI2_COMPLEXIO_CFG_PWD_STATUS_MASK (3 << 25) +#define CSI2_COMPLEXIO_CFG_PWD_STATUS_OFF (0 << 25) +#define CSI2_COMPLEXIO_CFG_PWD_STATUS_ON (1 << 25) +#define CSI2_COMPLEXIO_CFG_PWD_STATUS_ULP (2 << 25) +#define CSI2_COMPLEXIO_CFG_PWR_AUTO (1 << 24) +#define CSI2_COMPLEXIO_CFG_DATA_POL(i) (1 << (((i) * 4) + 3)) +#define CSI2_COMPLEXIO_CFG_DATA_POSITION_MASK(i) (7 << ((i) * 4)) +#define CSI2_COMPLEXIO_CFG_DATA_POSITION_SHIFT(i) ((i) * 4) +#define CSI2_COMPLEXIO_CFG_CLOCK_POL (1 << 3) +#define CSI2_COMPLEXIO_CFG_CLOCK_POSITION_MASK (7 << 0) +#define CSI2_COMPLEXIO_CFG_CLOCK_POSITION_SHIFT 0 + +#define CSI2_COMPLEXIO_IRQSTATUS 0x54 + +#define CSI2_SHORT_PACKET 0x5c + +#define CSI2_COMPLEXIO_IRQENABLE 0x60 + +/* Shared bits across CSI2_COMPLEXIO_IRQENABLE and IRQSTATUS */ +#define CSI2_COMPLEXIO_IRQ_STATEALLULPMEXIT (1 << 26) +#define CSI2_COMPLEXIO_IRQ_STATEALLULPMENTER (1 << 25) +#define CSI2_COMPLEXIO_IRQ_STATEULPM5 (1 << 24) +#define CSI2_COMPLEXIO_IRQ_STATEULPM4 (1 << 23) +#define CSI2_COMPLEXIO_IRQ_STATEULPM3 (1 << 22) +#define CSI2_COMPLEXIO_IRQ_STATEULPM2 (1 << 21) +#define CSI2_COMPLEXIO_IRQ_STATEULPM1 (1 << 20) +#define CSI2_COMPLEXIO_IRQ_ERRCONTROL5 (1 << 19) +#define CSI2_COMPLEXIO_IRQ_ERRCONTROL4 (1 << 18) +#define CSI2_COMPLEXIO_IRQ_ERRCONTROL3 (1 << 17) +#define CSI2_COMPLEXIO_IRQ_ERRCONTROL2 (1 << 16) +#define CSI2_COMPLEXIO_IRQ_ERRCONTROL1 (1 << 15) +#define CSI2_COMPLEXIO_IRQ_ERRESC5 (1 << 14) +#define CSI2_COMPLEXIO_IRQ_ERRESC4 (1 << 13) +#define CSI2_COMPLEXIO_IRQ_ERRESC3 (1 << 12) +#define CSI2_COMPLEXIO_IRQ_ERRESC2 (1 << 11) +#define CSI2_COMPLEXIO_IRQ_ERRESC1 (1 << 10) +#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS5 (1 << 9) +#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS4 (1 << 8) +#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS3 (1 << 7) +#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS2 (1 << 6) +#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS1 (1 << 5) +#define CSI2_COMPLEXIO_IRQ_ERRSOTHS5 (1 << 4) +#define CSI2_COMPLEXIO_IRQ_ERRSOTHS4 (1 << 3) +#define CSI2_COMPLEXIO_IRQ_ERRSOTHS3 (1 << 2) +#define CSI2_COMPLEXIO_IRQ_ERRSOTHS2 (1 << 1) +#define CSI2_COMPLEXIO_IRQ_ERRSOTHS1 (1 << 0) + +#define CSI2_DBG_P 0x68 + +#define CSI2_TIMING 0x6c +#define CSI2_TIMING_FORCE_RX_MODE_IO1 (1 << 15) +#define CSI2_TIMING_STOP_STATE_X16_IO1 (1 << 14) +#define CSI2_TIMING_STOP_STATE_X4_IO1 (1 << 13) +#define CSI2_TIMING_STOP_STATE_COUNTER_IO1_MASK (0x1fff << 0) +#define CSI2_TIMING_STOP_STATE_COUNTER_IO1_SHIFT 0 + +#define CSI2_CTX_CTRL1(i) (0x70 + (0x20 * i)) +#define CSI2_CTX_CTRL1_GENERIC (1 << 30) +#define CSI2_CTX_CTRL1_TRANSCODE (0xf << 24) +#define CSI2_CTX_CTRL1_FEC_NUMBER_MASK (0xff << 16) +#define CSI2_CTX_CTRL1_COUNT_MASK (0xff << 8) +#define CSI2_CTX_CTRL1_COUNT_SHIFT 8 +#define CSI2_CTX_CTRL1_EOF_EN (1 << 7) +#define CSI2_CTX_CTRL1_EOL_EN (1 << 6) +#define CSI2_CTX_CTRL1_CS_EN (1 << 5) +#define CSI2_CTX_CTRL1_COUNT_UNLOCK (1 << 4) +#define CSI2_CTX_CTRL1_PING_PONG (1 << 3) +#define CSI2_CTX_CTRL1_CTX_EN (1 << 0) + +#define CSI2_CTX_CTRL2(i) (0x74 + (0x20 * i)) +#define CSI2_CTX_CTRL2_USER_DEF_MAP_SHIFT 13 +#define CSI2_CTX_CTRL2_USER_DEF_MAP_MASK \ + (0x3 << CSI2_CTX_CTRL2_USER_DEF_MAP_SHIFT) +#define CSI2_CTX_CTRL2_VIRTUAL_ID_MASK (3 << 11) +#define CSI2_CTX_CTRL2_VIRTUAL_ID_SHIFT 11 +#define CSI2_CTX_CTRL2_DPCM_PRED (1 << 10) +#define CSI2_CTX_CTRL2_FORMAT_MASK (0x3ff << 0) +#define CSI2_CTX_CTRL2_FORMAT_SHIFT 0 + +#define CSI2_CTX_DAT_OFST(i) (0x78 + (0x20 * i)) +#define CSI2_CTX_DAT_OFST_MASK (0xfff << 5) + +#define CSI2_CTX_PING_ADDR(i) (0x7c + (0x20 * i)) +#define CSI2_CTX_PING_ADDR_MASK 0xffffffe0 + +#define CSI2_CTX_PONG_ADDR(i) (0x80 + (0x20 * i)) +#define CSI2_CTX_PONG_ADDR_MASK CSI2_CTX_PING_ADDR_MASK + +#define CSI2_CTX_IRQENABLE(i) (0x84 + (0x20 * i)) +#define CSI2_CTX_IRQSTATUS(i) (0x88 + (0x20 * i)) + +#define CSI2_CTX_CTRL3(i) (0x8c + (0x20 * i)) +#define CSI2_CTX_CTRL3_ALPHA_SHIFT 5 +#define CSI2_CTX_CTRL3_ALPHA_MASK \ + (0x3fff << CSI2_CTX_CTRL3_ALPHA_SHIFT) + +/* Shared bits across CSI2_CTX_IRQENABLE and IRQSTATUS */ +#define CSI2_CTX_IRQ_ECC_CORRECTION (1 << 8) +#define CSI2_CTX_IRQ_LINE_NUMBER (1 << 7) +#define CSI2_CTX_IRQ_FRAME_NUMBER (1 << 6) +#define CSI2_CTX_IRQ_CS (1 << 5) +#define CSI2_CTX_IRQ_LE (1 << 3) +#define CSI2_CTX_IRQ_LS (1 << 2) +#define CSI2_CTX_IRQ_FE (1 << 1) +#define CSI2_CTX_IRQ_FS (1 << 0) + +/* ISS BTE */ +#define BTE_CTRL (0x0030) +#define BTE_CTRL_BW_LIMITER_MASK (0x3ff << 22) +#define BTE_CTRL_BW_LIMITER_SHIFT 22 + +/* ISS ISP_SYS1 */ +#define ISP5_REVISION (0x0000) +#define ISP5_SYSCONFIG (0x0010) +#define ISP5_SYSCONFIG_STANDBYMODE_MASK (3 << 4) +#define ISP5_SYSCONFIG_STANDBYMODE_FORCE (0 << 4) +#define ISP5_SYSCONFIG_STANDBYMODE_NO (1 << 4) +#define ISP5_SYSCONFIG_STANDBYMODE_SMART (2 << 4) +#define ISP5_SYSCONFIG_SOFTRESET (1 << 1) + +#define ISP5_IRQSTATUS(i) (0x0028 + (0x10 * (i))) +#define ISP5_IRQENABLE_SET(i) (0x002c + (0x10 * (i))) +#define ISP5_IRQENABLE_CLR(i) (0x0030 + (0x10 * (i))) + +/* Bits shared for ISP5_IRQ* registers */ +#define ISP5_IRQ_OCP_ERR (1 << 31) +#define ISP5_IRQ_IPIPE_INT_DPC_RNEW1 (1 << 29) +#define ISP5_IRQ_IPIPE_INT_DPC_RNEW0 (1 << 28) +#define ISP5_IRQ_IPIPE_INT_DPC_INIT (1 << 27) +#define ISP5_IRQ_IPIPE_INT_EOF (1 << 25) +#define ISP5_IRQ_H3A_INT_EOF (1 << 24) +#define ISP5_IRQ_RSZ_INT_EOF1 (1 << 23) +#define ISP5_IRQ_RSZ_INT_EOF0 (1 << 22) +#define ISP5_IRQ_RSZ_FIFO_IN_BLK_ERR (1 << 19) +#define ISP5_IRQ_RSZ_FIFO_OVF (1 << 18) +#define ISP5_IRQ_RSZ_INT_CYC_RSZB (1 << 17) +#define ISP5_IRQ_RSZ_INT_CYC_RSZA (1 << 16) +#define ISP5_IRQ_RSZ_INT_DMA (1 << 15) +#define ISP5_IRQ_RSZ_INT_LAST_PIX (1 << 14) +#define ISP5_IRQ_RSZ_INT_REG (1 << 13) +#define ISP5_IRQ_H3A_INT (1 << 12) +#define ISP5_IRQ_AF_INT (1 << 11) +#define ISP5_IRQ_AEW_INT (1 << 10) +#define ISP5_IRQ_IPIPEIF_IRQ (1 << 9) +#define ISP5_IRQ_IPIPE_INT_HST (1 << 8) +#define ISP5_IRQ_IPIPE_INT_BSC (1 << 7) +#define ISP5_IRQ_IPIPE_INT_DMA (1 << 6) +#define ISP5_IRQ_IPIPE_INT_LAST_PIX (1 << 5) +#define ISP5_IRQ_IPIPE_INT_REG (1 << 4) +#define ISP5_IRQ_ISIF_INT(i) (1 << (i)) + +#define ISP5_CTRL (0x006c) +#define ISP5_CTRL_MSTANDBY (1 << 24) +#define ISP5_CTRL_VD_PULSE_EXT (1 << 23) +#define ISP5_CTRL_MSTANDBY_WAIT (1 << 20) +#define ISP5_CTRL_BL_CLK_ENABLE (1 << 15) +#define ISP5_CTRL_ISIF_CLK_ENABLE (1 << 14) +#define ISP5_CTRL_H3A_CLK_ENABLE (1 << 13) +#define ISP5_CTRL_RSZ_CLK_ENABLE (1 << 12) +#define ISP5_CTRL_IPIPE_CLK_ENABLE (1 << 11) +#define ISP5_CTRL_IPIPEIF_CLK_ENABLE (1 << 10) +#define ISP5_CTRL_SYNC_ENABLE (1 << 9) +#define ISP5_CTRL_PSYNC_CLK_SEL (1 << 8) + +/* ISS ISP ISIF register offsets */ +#define ISIF_SYNCEN (0x0000) +#define ISIF_SYNCEN_DWEN (1 << 1) +#define ISIF_SYNCEN_SYEN (1 << 0) + +#define ISIF_MODESET (0x0004) +#define ISIF_MODESET_INPMOD_MASK (3 << 12) +#define ISIF_MODESET_INPMOD_RAW (0 << 12) +#define ISIF_MODESET_INPMOD_YCBCR16 (1 << 12) +#define ISIF_MODESET_INPMOD_YCBCR8 (2 << 12) +#define ISIF_MODESET_CCDW_MASK (7 << 8) +#define ISIF_MODESET_CCDW_2BIT (2 << 8) +#define ISIF_MODESET_CCDMD (1 << 7) +#define ISIF_MODESET_SWEN (1 << 5) +#define ISIF_MODESET_HDPOL (1 << 3) +#define ISIF_MODESET_VDPOL (1 << 2) + +#define ISIF_SPH (0x0018) +#define ISIF_SPH_MASK (0x7fff) + +#define ISIF_LNH (0x001c) +#define ISIF_LNH_MASK (0x7fff) + +#define ISIF_LNV (0x0028) +#define ISIF_LNV_MASK (0x7fff) + +#define ISIF_HSIZE (0x0034) +#define ISIF_HSIZE_ADCR (1 << 12) +#define ISIF_HSIZE_HSIZE_MASK (0xfff) + +#define ISIF_CADU (0x003c) +#define ISIF_CADU_MASK (0x7ff) + +#define ISIF_CADL (0x0040) +#define ISIF_CADL_MASK (0xffff) + +#define ISIF_CCOLP (0x004c) +#define ISIF_CCOLP_CP0_F0_R (0 << 6) +#define ISIF_CCOLP_CP0_F0_GR (1 << 6) +#define ISIF_CCOLP_CP0_F0_B (3 << 6) +#define ISIF_CCOLP_CP0_F0_GB (2 << 6) +#define ISIF_CCOLP_CP1_F0_R (0 << 4) +#define ISIF_CCOLP_CP1_F0_GR (1 << 4) +#define ISIF_CCOLP_CP1_F0_B (3 << 4) +#define ISIF_CCOLP_CP1_F0_GB (2 << 4) +#define ISIF_CCOLP_CP2_F0_R (0 << 2) +#define ISIF_CCOLP_CP2_F0_GR (1 << 2) +#define ISIF_CCOLP_CP2_F0_B (3 << 2) +#define ISIF_CCOLP_CP2_F0_GB (2 << 2) +#define ISIF_CCOLP_CP3_F0_R (0 << 0) +#define ISIF_CCOLP_CP3_F0_GR (1 << 0) +#define ISIF_CCOLP_CP3_F0_B (3 << 0) +#define ISIF_CCOLP_CP3_F0_GB (2 << 0) + +#define ISIF_VDINT(i) (0x0070 + (i) * 4) +#define ISIF_VDINT_MASK (0x7fff) + +#define ISIF_CGAMMAWD (0x0080) +#define ISIF_CGAMMAWD_GWDI_MASK (0xf << 1) +#define ISIF_CGAMMAWD_GWDI(bpp) ((16 - (bpp)) << 1) + +#define ISIF_CCDCFG (0x0088) +#define ISIF_CCDCFG_Y8POS (1 << 11) + +/* ISS ISP IPIPEIF register offsets */ +#define IPIPEIF_ENABLE (0x0000) + +#define IPIPEIF_CFG1 (0x0004) +#define IPIPEIF_CFG1_INPSRC1_MASK (3 << 14) +#define IPIPEIF_CFG1_INPSRC1_VPORT_RAW (0 << 14) +#define IPIPEIF_CFG1_INPSRC1_SDRAM_RAW (1 << 14) +#define IPIPEIF_CFG1_INPSRC1_ISIF_DARKFM (2 << 14) +#define IPIPEIF_CFG1_INPSRC1_SDRAM_YUV (3 << 14) +#define IPIPEIF_CFG1_INPSRC2_MASK (3 << 2) +#define IPIPEIF_CFG1_INPSRC2_ISIF (0 << 2) +#define IPIPEIF_CFG1_INPSRC2_SDRAM_RAW (1 << 2) +#define IPIPEIF_CFG1_INPSRC2_ISIF_DARKFM (2 << 2) +#define IPIPEIF_CFG1_INPSRC2_SDRAM_YUV (3 << 2) + +#define IPIPEIF_CFG2 (0x0030) +#define IPIPEIF_CFG2_YUV8P (1 << 7) +#define IPIPEIF_CFG2_YUV8 (1 << 6) +#define IPIPEIF_CFG2_YUV16 (1 << 3) +#define IPIPEIF_CFG2_VDPOL (1 << 2) +#define IPIPEIF_CFG2_HDPOL (1 << 1) +#define IPIPEIF_CFG2_INTSW (1 << 0) + +#define IPIPEIF_CLKDIV (0x0040) + +/* ISS ISP IPIPE register offsets */ +#define IPIPE_SRC_EN (0x0000) +#define IPIPE_SRC_EN_EN (1 << 0) + +#define IPIPE_SRC_MODE (0x0004) +#define IPIPE_SRC_MODE_WRT (1 << 1) +#define IPIPE_SRC_MODE_OST (1 << 0) + +#define IPIPE_SRC_FMT (0x0008) +#define IPIPE_SRC_FMT_RAW2YUV (0 << 0) +#define IPIPE_SRC_FMT_RAW2RAW (1 << 0) +#define IPIPE_SRC_FMT_RAW2STATS (2 << 0) +#define IPIPE_SRC_FMT_YUV2YUV (3 << 0) + +#define IPIPE_SRC_COL (0x000c) +#define IPIPE_SRC_COL_OO_R (0 << 6) +#define IPIPE_SRC_COL_OO_GR (1 << 6) +#define IPIPE_SRC_COL_OO_B (3 << 6) +#define IPIPE_SRC_COL_OO_GB (2 << 6) +#define IPIPE_SRC_COL_OE_R (0 << 4) +#define IPIPE_SRC_COL_OE_GR (1 << 4) +#define IPIPE_SRC_COL_OE_B (3 << 4) +#define IPIPE_SRC_COL_OE_GB (2 << 4) +#define IPIPE_SRC_COL_EO_R (0 << 2) +#define IPIPE_SRC_COL_EO_GR (1 << 2) +#define IPIPE_SRC_COL_EO_B (3 << 2) +#define IPIPE_SRC_COL_EO_GB (2 << 2) +#define IPIPE_SRC_COL_EE_R (0 << 0) +#define IPIPE_SRC_COL_EE_GR (1 << 0) +#define IPIPE_SRC_COL_EE_B (3 << 0) +#define IPIPE_SRC_COL_EE_GB (2 << 0) + +#define IPIPE_SRC_VPS (0x0010) +#define IPIPE_SRC_VPS_MASK (0xffff) + +#define IPIPE_SRC_VSZ (0x0014) +#define IPIPE_SRC_VSZ_MASK (0x1fff) + +#define IPIPE_SRC_HPS (0x0018) +#define IPIPE_SRC_HPS_MASK (0xffff) + +#define IPIPE_SRC_HSZ (0x001c) +#define IPIPE_SRC_HSZ_MASK (0x1ffe) + +#define IPIPE_SEL_SBU (0x0020) + +#define IPIPE_SRC_STA (0x0024) + +#define IPIPE_GCK_MMR (0x0028) +#define IPIPE_GCK_MMR_REG (1 << 0) + +#define IPIPE_GCK_PIX (0x002c) +#define IPIPE_GCK_PIX_G3 (1 << 3) +#define IPIPE_GCK_PIX_G2 (1 << 2) +#define IPIPE_GCK_PIX_G1 (1 << 1) +#define IPIPE_GCK_PIX_G0 (1 << 0) + +#define IPIPE_DPC_LUT_EN (0x0034) +#define IPIPE_DPC_LUT_SEL (0x0038) +#define IPIPE_DPC_LUT_ADR (0x003c) +#define IPIPE_DPC_LUT_SIZ (0x0040) + +#define IPIPE_DPC_OTF_EN (0x0044) +#define IPIPE_DPC_OTF_TYP (0x0048) +#define IPIPE_DPC_OTF_2_D_THR_R (0x004c) +#define IPIPE_DPC_OTF_2_D_THR_GR (0x0050) +#define IPIPE_DPC_OTF_2_D_THR_GB (0x0054) +#define IPIPE_DPC_OTF_2_D_THR_B (0x0058) +#define IPIPE_DPC_OTF_2_C_THR_R (0x005c) +#define IPIPE_DPC_OTF_2_C_THR_GR (0x0060) +#define IPIPE_DPC_OTF_2_C_THR_GB (0x0064) +#define IPIPE_DPC_OTF_2_C_THR_B (0x0068) +#define IPIPE_DPC_OTF_3_SHF (0x006c) +#define IPIPE_DPC_OTF_3_D_THR (0x0070) +#define IPIPE_DPC_OTF_3_D_SPL (0x0074) +#define IPIPE_DPC_OTF_3_D_MIN (0x0078) +#define IPIPE_DPC_OTF_3_D_MAX (0x007c) +#define IPIPE_DPC_OTF_3_C_THR (0x0080) +#define IPIPE_DPC_OTF_3_C_SLP (0x0084) +#define IPIPE_DPC_OTF_3_C_MIN (0x0088) +#define IPIPE_DPC_OTF_3_C_MAX (0x008c) + +#define IPIPE_LSC_VOFT (0x0090) +#define IPIPE_LSC_VA2 (0x0094) +#define IPIPE_LSC_VA1 (0x0098) +#define IPIPE_LSC_VS (0x009c) +#define IPIPE_LSC_HOFT (0x00a0) +#define IPIPE_LSC_HA2 (0x00a4) +#define IPIPE_LSC_HA1 (0x00a8) +#define IPIPE_LSC_HS (0x00ac) +#define IPIPE_LSC_GAN_R (0x00b0) +#define IPIPE_LSC_GAN_GR (0x00b4) +#define IPIPE_LSC_GAN_GB (0x00b8) +#define IPIPE_LSC_GAN_B (0x00bc) +#define IPIPE_LSC_OFT_R (0x00c0) +#define IPIPE_LSC_OFT_GR (0x00c4) +#define IPIPE_LSC_OFT_GB (0x00c8) +#define IPIPE_LSC_OFT_B (0x00cc) +#define IPIPE_LSC_SHF (0x00d0) +#define IPIPE_LSC_MAX (0x00d4) + +#define IPIPE_D2F_1ST_EN (0x00d8) +#define IPIPE_D2F_1ST_TYP (0x00dc) +#define IPIPE_D2F_1ST_THR_00 (0x00e0) +#define IPIPE_D2F_1ST_THR_01 (0x00e4) +#define IPIPE_D2F_1ST_THR_02 (0x00e8) +#define IPIPE_D2F_1ST_THR_03 (0x00ec) +#define IPIPE_D2F_1ST_THR_04 (0x00f0) +#define IPIPE_D2F_1ST_THR_05 (0x00f4) +#define IPIPE_D2F_1ST_THR_06 (0x00f8) +#define IPIPE_D2F_1ST_THR_07 (0x00fc) +#define IPIPE_D2F_1ST_STR_00 (0x0100) +#define IPIPE_D2F_1ST_STR_01 (0x0104) +#define IPIPE_D2F_1ST_STR_02 (0x0108) +#define IPIPE_D2F_1ST_STR_03 (0x010c) +#define IPIPE_D2F_1ST_STR_04 (0x0110) +#define IPIPE_D2F_1ST_STR_05 (0x0114) +#define IPIPE_D2F_1ST_STR_06 (0x0118) +#define IPIPE_D2F_1ST_STR_07 (0x011c) +#define IPIPE_D2F_1ST_SPR_00 (0x0120) +#define IPIPE_D2F_1ST_SPR_01 (0x0124) +#define IPIPE_D2F_1ST_SPR_02 (0x0128) +#define IPIPE_D2F_1ST_SPR_03 (0x012c) +#define IPIPE_D2F_1ST_SPR_04 (0x0130) +#define IPIPE_D2F_1ST_SPR_05 (0x0134) +#define IPIPE_D2F_1ST_SPR_06 (0x0138) +#define IPIPE_D2F_1ST_SPR_07 (0x013c) +#define IPIPE_D2F_1ST_EDG_MIN (0x0140) +#define IPIPE_D2F_1ST_EDG_MAX (0x0144) +#define IPIPE_D2F_2ND_EN (0x0148) +#define IPIPE_D2F_2ND_TYP (0x014c) +#define IPIPE_D2F_2ND_THR00 (0x0150) +#define IPIPE_D2F_2ND_THR01 (0x0154) +#define IPIPE_D2F_2ND_THR02 (0x0158) +#define IPIPE_D2F_2ND_THR03 (0x015c) +#define IPIPE_D2F_2ND_THR04 (0x0160) +#define IPIPE_D2F_2ND_THR05 (0x0164) +#define IPIPE_D2F_2ND_THR06 (0x0168) +#define IPIPE_D2F_2ND_THR07 (0x016c) +#define IPIPE_D2F_2ND_STR_00 (0x0170) +#define IPIPE_D2F_2ND_STR_01 (0x0174) +#define IPIPE_D2F_2ND_STR_02 (0x0178) +#define IPIPE_D2F_2ND_STR_03 (0x017c) +#define IPIPE_D2F_2ND_STR_04 (0x0180) +#define IPIPE_D2F_2ND_STR_05 (0x0184) +#define IPIPE_D2F_2ND_STR_06 (0x0188) +#define IPIPE_D2F_2ND_STR_07 (0x018c) +#define IPIPE_D2F_2ND_SPR_00 (0x0190) +#define IPIPE_D2F_2ND_SPR_01 (0x0194) +#define IPIPE_D2F_2ND_SPR_02 (0x0198) +#define IPIPE_D2F_2ND_SPR_03 (0x019c) +#define IPIPE_D2F_2ND_SPR_04 (0x01a0) +#define IPIPE_D2F_2ND_SPR_05 (0x01a4) +#define IPIPE_D2F_2ND_SPR_06 (0x01a8) +#define IPIPE_D2F_2ND_SPR_07 (0x01ac) +#define IPIPE_D2F_2ND_EDG_MIN (0x01b0) +#define IPIPE_D2F_2ND_EDG_MAX (0x01b4) + +#define IPIPE_GIC_EN (0x01b8) +#define IPIPE_GIC_TYP (0x01bc) +#define IPIPE_GIC_GAN (0x01c0) +#define IPIPE_GIC_NFGAIN (0x01c4) +#define IPIPE_GIC_THR (0x01c8) +#define IPIPE_GIC_SLP (0x01cc) + +#define IPIPE_WB2_OFT_R (0x01d0) +#define IPIPE_WB2_OFT_GR (0x01d4) +#define IPIPE_WB2_OFT_GB (0x01d8) +#define IPIPE_WB2_OFT_B (0x01dc) + +#define IPIPE_WB2_WGN_R (0x01e0) +#define IPIPE_WB2_WGN_GR (0x01e4) +#define IPIPE_WB2_WGN_GB (0x01e8) +#define IPIPE_WB2_WGN_B (0x01ec) + +#define IPIPE_CFA_MODE (0x01f0) +#define IPIPE_CFA_2DIR_HPF_THR (0x01f4) +#define IPIPE_CFA_2DIR_HPF_SLP (0x01f8) +#define IPIPE_CFA_2DIR_MIX_THR (0x01fc) +#define IPIPE_CFA_2DIR_MIX_SLP (0x0200) +#define IPIPE_CFA_2DIR_DIR_TRH (0x0204) +#define IPIPE_CFA_2DIR_DIR_SLP (0x0208) +#define IPIPE_CFA_2DIR_NDWT (0x020c) +#define IPIPE_CFA_MONO_HUE_FRA (0x0210) +#define IPIPE_CFA_MONO_EDG_THR (0x0214) +#define IPIPE_CFA_MONO_THR_MIN (0x0218) + +#define IPIPE_CFA_MONO_THR_SLP (0x021c) +#define IPIPE_CFA_MONO_SLP_MIN (0x0220) +#define IPIPE_CFA_MONO_SLP_SLP (0x0224) +#define IPIPE_CFA_MONO_LPWT (0x0228) + +#define IPIPE_RGB1_MUL_RR (0x022c) +#define IPIPE_RGB1_MUL_GR (0x0230) +#define IPIPE_RGB1_MUL_BR (0x0234) +#define IPIPE_RGB1_MUL_RG (0x0238) +#define IPIPE_RGB1_MUL_GG (0x023c) +#define IPIPE_RGB1_MUL_BG (0x0240) +#define IPIPE_RGB1_MUL_RB (0x0244) +#define IPIPE_RGB1_MUL_GB (0x0248) +#define IPIPE_RGB1_MUL_BB (0x024c) +#define IPIPE_RGB1_OFT_OR (0x0250) +#define IPIPE_RGB1_OFT_OG (0x0254) +#define IPIPE_RGB1_OFT_OB (0x0258) +#define IPIPE_GMM_CFG (0x025c) +#define IPIPE_RGB2_MUL_RR (0x0260) +#define IPIPE_RGB2_MUL_GR (0x0264) +#define IPIPE_RGB2_MUL_BR (0x0268) +#define IPIPE_RGB2_MUL_RG (0x026c) +#define IPIPE_RGB2_MUL_GG (0x0270) +#define IPIPE_RGB2_MUL_BG (0x0274) +#define IPIPE_RGB2_MUL_RB (0x0278) +#define IPIPE_RGB2_MUL_GB (0x027c) +#define IPIPE_RGB2_MUL_BB (0x0280) +#define IPIPE_RGB2_OFT_OR (0x0284) +#define IPIPE_RGB2_OFT_OG (0x0288) +#define IPIPE_RGB2_OFT_OB (0x028c) + +#define IPIPE_YUV_ADJ (0x0294) +#define IPIPE_YUV_MUL_RY (0x0298) +#define IPIPE_YUV_MUL_GY (0x029c) +#define IPIPE_YUV_MUL_BY (0x02a0) +#define IPIPE_YUV_MUL_RCB (0x02a4) +#define IPIPE_YUV_MUL_GCB (0x02a8) +#define IPIPE_YUV_MUL_BCB (0x02ac) +#define IPIPE_YUV_MUL_RCR (0x02b0) +#define IPIPE_YUV_MUL_GCR (0x02b4) +#define IPIPE_YUV_MUL_BCR (0x02b8) +#define IPIPE_YUV_OFT_Y (0x02bc) +#define IPIPE_YUV_OFT_CB (0x02c0) +#define IPIPE_YUV_OFT_CR (0x02c4) + +#define IPIPE_YUV_PHS (0x02c8) +#define IPIPE_YUV_PHS_LPF (1 << 1) +#define IPIPE_YUV_PHS_POS (1 << 0) + +#define IPIPE_YEE_EN (0x02d4) +#define IPIPE_YEE_TYP (0x02d8) +#define IPIPE_YEE_SHF (0x02dc) +#define IPIPE_YEE_MUL_00 (0x02e0) +#define IPIPE_YEE_MUL_01 (0x02e4) +#define IPIPE_YEE_MUL_02 (0x02e8) +#define IPIPE_YEE_MUL_10 (0x02ec) +#define IPIPE_YEE_MUL_11 (0x02f0) +#define IPIPE_YEE_MUL_12 (0x02f4) +#define IPIPE_YEE_MUL_20 (0x02f8) +#define IPIPE_YEE_MUL_21 (0x02fc) +#define IPIPE_YEE_MUL_22 (0x0300) +#define IPIPE_YEE_THR (0x0304) +#define IPIPE_YEE_E_GAN (0x0308) +#define IPIPE_YEE_E_THR_1 (0x030c) +#define IPIPE_YEE_E_THR_2 (0x0310) +#define IPIPE_YEE_G_GAN (0x0314) +#define IPIPE_YEE_G_OFT (0x0318) + +#define IPIPE_CAR_EN (0x031c) +#define IPIPE_CAR_TYP (0x0320) +#define IPIPE_CAR_SW (0x0324) +#define IPIPE_CAR_HPF_TYP (0x0328) +#define IPIPE_CAR_HPF_SHF (0x032c) +#define IPIPE_CAR_HPF_THR (0x0330) +#define IPIPE_CAR_GN1_GAN (0x0334) +#define IPIPE_CAR_GN1_SHF (0x0338) +#define IPIPE_CAR_GN1_MIN (0x033c) +#define IPIPE_CAR_GN2_GAN (0x0340) +#define IPIPE_CAR_GN2_SHF (0x0344) +#define IPIPE_CAR_GN2_MIN (0x0348) +#define IPIPE_CGS_EN (0x034c) +#define IPIPE_CGS_GN1_L_THR (0x0350) +#define IPIPE_CGS_GN1_L_GAIN (0x0354) +#define IPIPE_CGS_GN1_L_SHF (0x0358) +#define IPIPE_CGS_GN1_L_MIN (0x035c) +#define IPIPE_CGS_GN1_H_THR (0x0360) +#define IPIPE_CGS_GN1_H_GAIN (0x0364) +#define IPIPE_CGS_GN1_H_SHF (0x0368) +#define IPIPE_CGS_GN1_H_MIN (0x036c) +#define IPIPE_CGS_GN2_L_THR (0x0370) +#define IPIPE_CGS_GN2_L_GAIN (0x0374) +#define IPIPE_CGS_GN2_L_SHF (0x0378) +#define IPIPE_CGS_GN2_L_MIN (0x037c) + +#define IPIPE_BOX_EN (0x0380) +#define IPIPE_BOX_MODE (0x0384) +#define IPIPE_BOX_TYP (0x0388) +#define IPIPE_BOX_SHF (0x038c) +#define IPIPE_BOX_SDR_SAD_H (0x0390) +#define IPIPE_BOX_SDR_SAD_L (0x0394) + +#define IPIPE_HST_EN (0x039c) +#define IPIPE_HST_MODE (0x03a0) +#define IPIPE_HST_SEL (0x03a4) +#define IPIPE_HST_PARA (0x03a8) +#define IPIPE_HST_0_VPS (0x03ac) +#define IPIPE_HST_0_VSZ (0x03b0) +#define IPIPE_HST_0_HPS (0x03b4) +#define IPIPE_HST_0_HSZ (0x03b8) +#define IPIPE_HST_1_VPS (0x03bc) +#define IPIPE_HST_1_VSZ (0x03c0) +#define IPIPE_HST_1_HPS (0x03c4) +#define IPIPE_HST_1_HSZ (0x03c8) +#define IPIPE_HST_2_VPS (0x03cc) +#define IPIPE_HST_2_VSZ (0x03d0) +#define IPIPE_HST_2_HPS (0x03d4) +#define IPIPE_HST_2_HSZ (0x03d8) +#define IPIPE_HST_3_VPS (0x03dc) +#define IPIPE_HST_3_VSZ (0x03e0) +#define IPIPE_HST_3_HPS (0x03e4) +#define IPIPE_HST_3_HSZ (0x03e8) +#define IPIPE_HST_TBL (0x03ec) +#define IPIPE_HST_MUL_R (0x03f0) +#define IPIPE_HST_MUL_GR (0x03f4) +#define IPIPE_HST_MUL_GB (0x03f8) +#define IPIPE_HST_MUL_B (0x03fc) + +#define IPIPE_BSC_EN (0x0400) +#define IPIPE_BSC_MODE (0x0404) +#define IPIPE_BSC_TYP (0x0408) +#define IPIPE_BSC_ROW_VCT (0x040c) +#define IPIPE_BSC_ROW_SHF (0x0410) +#define IPIPE_BSC_ROW_VPO (0x0414) +#define IPIPE_BSC_ROW_VNU (0x0418) +#define IPIPE_BSC_ROW_VSKIP (0x041c) +#define IPIPE_BSC_ROW_HPO (0x0420) +#define IPIPE_BSC_ROW_HNU (0x0424) +#define IPIPE_BSC_ROW_HSKIP (0x0428) +#define IPIPE_BSC_COL_VCT (0x042c) +#define IPIPE_BSC_COL_SHF (0x0430) +#define IPIPE_BSC_COL_VPO (0x0434) +#define IPIPE_BSC_COL_VNU (0x0438) +#define IPIPE_BSC_COL_VSKIP (0x043c) +#define IPIPE_BSC_COL_HPO (0x0440) +#define IPIPE_BSC_COL_HNU (0x0444) +#define IPIPE_BSC_COL_HSKIP (0x0448) + +#define IPIPE_BSC_EN (0x0400) + +/* ISS ISP Resizer register offsets */ +#define RSZ_REVISION (0x0000) +#define RSZ_SYSCONFIG (0x0004) +#define RSZ_SYSCONFIG_RSZB_CLK_EN (1 << 9) +#define RSZ_SYSCONFIG_RSZA_CLK_EN (1 << 8) + +#define RSZ_IN_FIFO_CTRL (0x000c) +#define RSZ_IN_FIFO_CTRL_THRLD_LOW_MASK (0x1ff << 16) +#define RSZ_IN_FIFO_CTRL_THRLD_LOW_SHIFT 16 +#define RSZ_IN_FIFO_CTRL_THRLD_HIGH_MASK (0x1ff << 0) +#define RSZ_IN_FIFO_CTRL_THRLD_HIGH_SHIFT 0 + +#define RSZ_FRACDIV (0x0008) +#define RSZ_FRACDIV_MASK (0xffff) + +#define RSZ_SRC_EN (0x0020) +#define RSZ_SRC_EN_SRC_EN (1 << 0) + +#define RSZ_SRC_MODE (0x0024) +#define RSZ_SRC_MODE_OST (1 << 0) +#define RSZ_SRC_MODE_WRT (1 << 1) + +#define RSZ_SRC_FMT0 (0x0028) +#define RSZ_SRC_FMT0_BYPASS (1 << 1) +#define RSZ_SRC_FMT0_SEL (1 << 0) + +#define RSZ_SRC_FMT1 (0x002c) +#define RSZ_SRC_FMT1_IN420 (1 << 1) + +#define RSZ_SRC_VPS (0x0030) +#define RSZ_SRC_VSZ (0x0034) +#define RSZ_SRC_HPS (0x0038) +#define RSZ_SRC_HSZ (0x003c) +#define RSZ_DMA_RZA (0x0040) +#define RSZ_DMA_RZB (0x0044) +#define RSZ_DMA_STA (0x0048) +#define RSZ_GCK_MMR (0x004c) +#define RSZ_GCK_MMR_MMR (1 << 0) + +#define RSZ_GCK_SDR (0x0054) +#define RSZ_GCK_SDR_CORE (1 << 0) + +#define RSZ_IRQ_RZA (0x0058) +#define RSZ_IRQ_RZA_MASK (0x1fff) + +#define RSZ_IRQ_RZB (0x005c) +#define RSZ_IRQ_RZB_MASK (0x1fff) + +#define RSZ_YUV_Y_MIN (0x0060) +#define RSZ_YUV_Y_MAX (0x0064) +#define RSZ_YUV_C_MIN (0x0068) +#define RSZ_YUV_C_MAX (0x006c) + +#define RSZ_SEQ (0x0074) +#define RSZ_SEQ_HRVB (1 << 2) +#define RSZ_SEQ_HRVA (1 << 0) + +#define RZA_EN (0x0078) +#define RZA_MODE (0x007c) +#define RZA_MODE_ONE_SHOT (1 << 0) + +#define RZA_420 (0x0080) +#define RZA_I_VPS (0x0084) +#define RZA_I_HPS (0x0088) +#define RZA_O_VSZ (0x008c) +#define RZA_O_HSZ (0x0090) +#define RZA_V_PHS_Y (0x0094) +#define RZA_V_PHS_C (0x0098) +#define RZA_V_DIF (0x009c) +#define RZA_V_TYP (0x00a0) +#define RZA_V_LPF (0x00a4) +#define RZA_H_PHS (0x00a8) +#define RZA_H_DIF (0x00b0) +#define RZA_H_TYP (0x00b4) +#define RZA_H_LPF (0x00b8) +#define RZA_DWN_EN (0x00bc) +#define RZA_SDR_Y_BAD_H (0x00d0) +#define RZA_SDR_Y_BAD_L (0x00d4) +#define RZA_SDR_Y_SAD_H (0x00d8) +#define RZA_SDR_Y_SAD_L (0x00dc) +#define RZA_SDR_Y_OFT (0x00e0) +#define RZA_SDR_Y_PTR_S (0x00e4) +#define RZA_SDR_Y_PTR_E (0x00e8) +#define RZA_SDR_C_BAD_H (0x00ec) +#define RZA_SDR_C_BAD_L (0x00f0) +#define RZA_SDR_C_SAD_H (0x00f4) +#define RZA_SDR_C_SAD_L (0x00f8) +#define RZA_SDR_C_OFT (0x00fc) +#define RZA_SDR_C_PTR_S (0x0100) +#define RZA_SDR_C_PTR_E (0x0104) + +#define RZB_EN (0x0108) +#define RZB_MODE (0x010c) +#define RZB_420 (0x0110) +#define RZB_I_VPS (0x0114) +#define RZB_I_HPS (0x0118) +#define RZB_O_VSZ (0x011c) +#define RZB_O_HSZ (0x0120) + +#define RZB_V_DIF (0x012c) +#define RZB_V_TYP (0x0130) +#define RZB_V_LPF (0x0134) + +#define RZB_H_DIF (0x0140) +#define RZB_H_TYP (0x0144) +#define RZB_H_LPF (0x0148) + +#define RZB_SDR_Y_BAD_H (0x0160) +#define RZB_SDR_Y_BAD_L (0x0164) +#define RZB_SDR_Y_SAD_H (0x0168) +#define RZB_SDR_Y_SAD_L (0x016c) +#define RZB_SDR_Y_OFT (0x0170) +#define RZB_SDR_Y_PTR_S (0x0174) +#define RZB_SDR_Y_PTR_E (0x0178) +#define RZB_SDR_C_BAD_H (0x017c) +#define RZB_SDR_C_BAD_L (0x0180) +#define RZB_SDR_C_SAD_H (0x0184) +#define RZB_SDR_C_SAD_L (0x0188) + +#define RZB_SDR_C_PTR_S (0x0190) +#define RZB_SDR_C_PTR_E (0x0194) + +/* Shared Bitmasks between RZA & RZB */ +#define RSZ_EN_EN (1 << 0) + +#define RSZ_420_CEN (1 << 1) +#define RSZ_420_YEN (1 << 0) + +#define RSZ_I_VPS_MASK (0x1fff) + +#define RSZ_I_HPS_MASK (0x1fff) + +#define RSZ_O_VSZ_MASK (0x1fff) + +#define RSZ_O_HSZ_MASK (0x1ffe) + +#define RSZ_V_PHS_Y_MASK (0x3fff) + +#define RSZ_V_PHS_C_MASK (0x3fff) + +#define RSZ_V_DIF_MASK (0x3fff) + +#define RSZ_V_TYP_C (1 << 1) +#define RSZ_V_TYP_Y (1 << 0) + +#define RSZ_V_LPF_C_MASK (0x3f << 6) +#define RSZ_V_LPF_C_SHIFT 6 +#define RSZ_V_LPF_Y_MASK (0x3f << 0) +#define RSZ_V_LPF_Y_SHIFT 0 + +#define RSZ_H_PHS_MASK (0x3fff) + +#define RSZ_H_DIF_MASK (0x3fff) + +#define RSZ_H_TYP_C (1 << 1) +#define RSZ_H_TYP_Y (1 << 0) + +#define RSZ_H_LPF_C_MASK (0x3f << 6) +#define RSZ_H_LPF_C_SHIFT 6 +#define RSZ_H_LPF_Y_MASK (0x3f << 0) +#define RSZ_H_LPF_Y_SHIFT 0 + +#define RSZ_DWN_EN_DWN_EN (1 << 0) + +#endif /* _OMAP4_ISS_REGS_H_ */ diff --git a/drivers/staging/media/omap4iss/iss_resizer.c b/drivers/staging/media/omap4iss/iss_resizer.c new file mode 100644 index 000000000000..ae831b8985c9 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_resizer.c @@ -0,0 +1,893 @@ +/* + * TI OMAP4 ISS V4L2 Driver - ISP RESIZER module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.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/module.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/mm.h> +#include <linux/sched.h> + +#include "iss.h" +#include "iss_regs.h" +#include "iss_resizer.h" + +static const unsigned int resizer_fmts[] = { + V4L2_MBUS_FMT_UYVY8_1X16, + V4L2_MBUS_FMT_YUYV8_1X16, +}; + +/* + * resizer_print_status - Print current RESIZER Module register values. + * @resizer: Pointer to ISS ISP RESIZER device. + * + * Also prints other debug information stored in the RESIZER module. + */ +#define RSZ_PRINT_REGISTER(iss, name)\ + dev_dbg(iss->dev, "###RSZ " #name "=0x%08x\n", \ + iss_reg_read(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_##name)) + +#define RZA_PRINT_REGISTER(iss, name)\ + dev_dbg(iss->dev, "###RZA " #name "=0x%08x\n", \ + iss_reg_read(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_##name)) + +static void resizer_print_status(struct iss_resizer_device *resizer) +{ + struct iss_device *iss = to_iss_device(resizer); + + dev_dbg(iss->dev, "-------------RESIZER Register dump-------------\n"); + + RSZ_PRINT_REGISTER(iss, SYSCONFIG); + RSZ_PRINT_REGISTER(iss, IN_FIFO_CTRL); + RSZ_PRINT_REGISTER(iss, FRACDIV); + RSZ_PRINT_REGISTER(iss, SRC_EN); + RSZ_PRINT_REGISTER(iss, SRC_MODE); + RSZ_PRINT_REGISTER(iss, SRC_FMT0); + RSZ_PRINT_REGISTER(iss, SRC_FMT1); + RSZ_PRINT_REGISTER(iss, SRC_VPS); + RSZ_PRINT_REGISTER(iss, SRC_VSZ); + RSZ_PRINT_REGISTER(iss, SRC_HPS); + RSZ_PRINT_REGISTER(iss, SRC_HSZ); + RSZ_PRINT_REGISTER(iss, DMA_RZA); + RSZ_PRINT_REGISTER(iss, DMA_RZB); + RSZ_PRINT_REGISTER(iss, DMA_STA); + RSZ_PRINT_REGISTER(iss, GCK_MMR); + RSZ_PRINT_REGISTER(iss, GCK_SDR); + RSZ_PRINT_REGISTER(iss, IRQ_RZA); + RSZ_PRINT_REGISTER(iss, IRQ_RZB); + RSZ_PRINT_REGISTER(iss, YUV_Y_MIN); + RSZ_PRINT_REGISTER(iss, YUV_Y_MAX); + RSZ_PRINT_REGISTER(iss, YUV_C_MIN); + RSZ_PRINT_REGISTER(iss, YUV_C_MAX); + RSZ_PRINT_REGISTER(iss, SEQ); + + RZA_PRINT_REGISTER(iss, EN); + RZA_PRINT_REGISTER(iss, MODE); + RZA_PRINT_REGISTER(iss, 420); + RZA_PRINT_REGISTER(iss, I_VPS); + RZA_PRINT_REGISTER(iss, I_HPS); + RZA_PRINT_REGISTER(iss, O_VSZ); + RZA_PRINT_REGISTER(iss, O_HSZ); + RZA_PRINT_REGISTER(iss, V_PHS_Y); + RZA_PRINT_REGISTER(iss, V_PHS_C); + RZA_PRINT_REGISTER(iss, V_DIF); + RZA_PRINT_REGISTER(iss, V_TYP); + RZA_PRINT_REGISTER(iss, V_LPF); + RZA_PRINT_REGISTER(iss, H_PHS); + RZA_PRINT_REGISTER(iss, H_DIF); + RZA_PRINT_REGISTER(iss, H_TYP); + RZA_PRINT_REGISTER(iss, H_LPF); + RZA_PRINT_REGISTER(iss, DWN_EN); + RZA_PRINT_REGISTER(iss, SDR_Y_BAD_H); + RZA_PRINT_REGISTER(iss, SDR_Y_BAD_L); + RZA_PRINT_REGISTER(iss, SDR_Y_SAD_H); + RZA_PRINT_REGISTER(iss, SDR_Y_SAD_L); + RZA_PRINT_REGISTER(iss, SDR_Y_OFT); + RZA_PRINT_REGISTER(iss, SDR_Y_PTR_S); + RZA_PRINT_REGISTER(iss, SDR_Y_PTR_E); + RZA_PRINT_REGISTER(iss, SDR_C_BAD_H); + RZA_PRINT_REGISTER(iss, SDR_C_BAD_L); + RZA_PRINT_REGISTER(iss, SDR_C_SAD_H); + RZA_PRINT_REGISTER(iss, SDR_C_SAD_L); + RZA_PRINT_REGISTER(iss, SDR_C_OFT); + RZA_PRINT_REGISTER(iss, SDR_C_PTR_S); + RZA_PRINT_REGISTER(iss, SDR_C_PTR_E); + + dev_dbg(iss->dev, "-----------------------------------------------\n"); +} + +/* + * resizer_enable - Enable/Disable RESIZER. + * @enable: enable flag + * + */ +static void resizer_enable(struct iss_resizer_device *resizer, u8 enable) +{ + struct iss_device *iss = to_iss_device(resizer); + + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_EN, + RSZ_SRC_EN_SRC_EN, enable ? RSZ_SRC_EN_SRC_EN : 0); + + /* TODO: Enable RSZB */ + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_EN, RSZ_EN_EN, + enable ? RSZ_EN_EN : 0); +} + +/* ----------------------------------------------------------------------------- + * Format- and pipeline-related configuration helpers + */ + +/* + * resizer_set_outaddr - Set memory address to save output image + * @resizer: Pointer to ISP RESIZER device. + * @addr: 32-bit memory address aligned on 32 byte boundary. + * + * Sets the memory address where the output will be saved. + */ +static void resizer_set_outaddr(struct iss_resizer_device *resizer, u32 addr) +{ + struct iss_device *iss = to_iss_device(resizer); + struct v4l2_mbus_framefmt *informat, *outformat; + + informat = &resizer->formats[RESIZER_PAD_SINK]; + outformat = &resizer->formats[RESIZER_PAD_SOURCE_MEM]; + + /* Save address splitted in Base Address H & L */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_BAD_H, + (addr >> 16) & 0xffff); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_BAD_L, + addr & 0xffff); + + /* SAD = BAD */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_SAD_H, + (addr >> 16) & 0xffff); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_SAD_L, + addr & 0xffff); + + /* Program UV buffer address... Hardcoded to be contiguous! */ + if ((informat->code == V4L2_MBUS_FMT_UYVY8_1X16) && + (outformat->code == V4L2_MBUS_FMT_YUYV8_1_5X8)) { + u32 c_addr = addr + (resizer->video_out.bpl_value * + (outformat->height - 1)); + + /* Ensure Y_BAD_L[6:0] = C_BAD_L[6:0]*/ + if ((c_addr ^ addr) & 0x7f) { + c_addr &= ~0x7f; + c_addr += 0x80; + c_addr |= addr & 0x7f; + } + + /* Save address splitted in Base Address H & L */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_BAD_H, + (c_addr >> 16) & 0xffff); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_BAD_L, + c_addr & 0xffff); + + /* SAD = BAD */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_SAD_H, + (c_addr >> 16) & 0xffff); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_SAD_L, + c_addr & 0xffff); + } +} + +static void resizer_configure(struct iss_resizer_device *resizer) +{ + struct iss_device *iss = to_iss_device(resizer); + struct v4l2_mbus_framefmt *informat, *outformat; + + informat = &resizer->formats[RESIZER_PAD_SINK]; + outformat = &resizer->formats[RESIZER_PAD_SOURCE_MEM]; + + /* Disable pass-through more. Despite its name, the BYPASS bit controls + * pass-through mode, not bypass mode. + */ + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_FMT0, + RSZ_SRC_FMT0_BYPASS); + + /* Select RSZ input */ + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_FMT0, + RSZ_SRC_FMT0_SEL, + resizer->input == RESIZER_INPUT_IPIPEIF ? + RSZ_SRC_FMT0_SEL : 0); + + /* RSZ ignores WEN signal from IPIPE/IPIPEIF */ + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_MODE, + RSZ_SRC_MODE_WRT); + + /* Set Resizer in free-running mode */ + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_MODE, + RSZ_SRC_MODE_OST); + + /* Init Resizer A */ + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_MODE, + RZA_MODE_ONE_SHOT); + + /* Set size related things now */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_VPS, 0); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_HPS, 0); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_VSZ, + informat->height - 2); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_HSZ, + informat->width - 1); + + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_I_VPS, 0); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_I_HPS, 0); + + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_O_VSZ, + outformat->height - 2); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_O_HSZ, + outformat->width - 1); + + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_V_DIF, 0x100); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_H_DIF, 0x100); + + /* Buffer output settings */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_PTR_S, 0); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_PTR_E, + outformat->height - 1); + + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_OFT, + resizer->video_out.bpl_value); + + /* UYVY -> NV12 conversion */ + if ((informat->code == V4L2_MBUS_FMT_UYVY8_1X16) && + (outformat->code == V4L2_MBUS_FMT_YUYV8_1_5X8)) { + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_420, + RSZ_420_CEN | RSZ_420_YEN); + + /* UV Buffer output settings */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_PTR_S, + 0); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_PTR_E, + outformat->height - 1); + + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_OFT, + resizer->video_out.bpl_value); + } else { + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_420, 0); + } +} + +/* ----------------------------------------------------------------------------- + * Interrupt handling + */ + +static void resizer_isr_buffer(struct iss_resizer_device *resizer) +{ + struct iss_buffer *buffer; + + /* The whole resizer needs to be stopped. Disabling RZA only produces + * input FIFO overflows, most probably when the next frame is received. + */ + resizer_enable(resizer, 0); + + buffer = omap4iss_video_buffer_next(&resizer->video_out); + if (buffer == NULL) + return; + + resizer_set_outaddr(resizer, buffer->iss_addr); + + resizer_enable(resizer, 1); +} + +/* + * resizer_isif0_isr - Handle ISIF0 event + * @resizer: Pointer to ISP RESIZER device. + * + * Executes LSC deferred enablement before next frame starts. + */ +static void resizer_int_dma_isr(struct iss_resizer_device *resizer) +{ + struct iss_pipeline *pipe = + to_iss_pipeline(&resizer->subdev.entity); + if (pipe->do_propagation) + atomic_inc(&pipe->frame_number); + + resizer_isr_buffer(resizer); +} + +/* + * omap4iss_resizer_isr - Configure resizer during interframe time. + * @resizer: Pointer to ISP RESIZER device. + * @events: RESIZER events + */ +void omap4iss_resizer_isr(struct iss_resizer_device *resizer, u32 events) +{ + struct iss_device *iss = to_iss_device(resizer); + struct iss_pipeline *pipe = + to_iss_pipeline(&resizer->subdev.entity); + + if (events & (ISP5_IRQ_RSZ_FIFO_IN_BLK_ERR | + ISP5_IRQ_RSZ_FIFO_OVF)) { + dev_dbg(iss->dev, "RSZ Err: FIFO_IN_BLK:%d, FIFO_OVF:%d\n", + events & ISP5_IRQ_RSZ_FIFO_IN_BLK_ERR ? 1 : 0, + events & ISP5_IRQ_RSZ_FIFO_OVF ? 1 : 0); + omap4iss_pipeline_cancel_stream(pipe); + } + + if (omap4iss_module_sync_is_stopping(&resizer->wait, + &resizer->stopping)) + return; + + if (events & ISP5_IRQ_RSZ_INT_DMA) + resizer_int_dma_isr(resizer); +} + +/* ----------------------------------------------------------------------------- + * ISS video operations + */ + +static int resizer_video_queue(struct iss_video *video, + struct iss_buffer *buffer) +{ + struct iss_resizer_device *resizer = container_of(video, + struct iss_resizer_device, video_out); + + if (!(resizer->output & RESIZER_OUTPUT_MEMORY)) + return -ENODEV; + + resizer_set_outaddr(resizer, buffer->iss_addr); + + /* + * If streaming was enabled before there was a buffer queued + * or underrun happened in the ISR, the hardware was not enabled + * and DMA queue flag ISS_VIDEO_DMAQUEUE_UNDERRUN is still set. + * Enable it now. + */ + if (video->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_UNDERRUN) { + resizer_enable(resizer, 1); + iss_video_dmaqueue_flags_clr(video); + } + + return 0; +} + +static const struct iss_video_operations resizer_video_ops = { + .queue = resizer_video_queue, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +/* + * resizer_set_stream - Enable/Disable streaming on the RESIZER module + * @sd: ISP RESIZER V4L2 subdevice + * @enable: Enable/disable stream + */ +static int resizer_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct iss_resizer_device *resizer = v4l2_get_subdevdata(sd); + struct iss_device *iss = to_iss_device(resizer); + struct iss_video *video_out = &resizer->video_out; + int ret = 0; + + if (resizer->state == ISS_PIPELINE_STREAM_STOPPED) { + if (enable == ISS_PIPELINE_STREAM_STOPPED) + return 0; + + omap4iss_isp_subclk_enable(iss, OMAP4_ISS_ISP_SUBCLK_RSZ); + + iss_reg_set(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_GCK_MMR, + RSZ_GCK_MMR_MMR); + iss_reg_set(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_GCK_SDR, + RSZ_GCK_SDR_CORE); + + /* FIXME: Enable RSZB also */ + iss_reg_set(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SYSCONFIG, + RSZ_SYSCONFIG_RSZA_CLK_EN); + } + + switch (enable) { + case ISS_PIPELINE_STREAM_CONTINUOUS: + + resizer_configure(resizer); + resizer_print_status(resizer); + + /* + * When outputting to memory with no buffer available, let the + * buffer queue handler start the hardware. A DMA queue flag + * ISS_VIDEO_DMAQUEUE_QUEUED will be set as soon as there is + * a buffer available. + */ + if (resizer->output & RESIZER_OUTPUT_MEMORY && + !(video_out->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_QUEUED)) + break; + + atomic_set(&resizer->stopping, 0); + resizer_enable(resizer, 1); + iss_video_dmaqueue_flags_clr(video_out); + break; + + case ISS_PIPELINE_STREAM_STOPPED: + if (resizer->state == ISS_PIPELINE_STREAM_STOPPED) + return 0; + if (omap4iss_module_sync_idle(&sd->entity, &resizer->wait, + &resizer->stopping)) + ret = -ETIMEDOUT; + + resizer_enable(resizer, 0); + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SYSCONFIG, + RSZ_SYSCONFIG_RSZA_CLK_EN); + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_GCK_SDR, + RSZ_GCK_SDR_CORE); + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_GCK_MMR, + RSZ_GCK_MMR_MMR); + omap4iss_isp_subclk_disable(iss, OMAP4_ISS_ISP_SUBCLK_RSZ); + iss_video_dmaqueue_flags_clr(video_out); + break; + } + + resizer->state = enable; + return ret; +} + +static struct v4l2_mbus_framefmt * +__resizer_get_format(struct iss_resizer_device *resizer, + struct v4l2_subdev_fh *fh, unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(fh, pad); + else + return &resizer->formats[pad]; +} + +/* + * resizer_try_format - Try video format on a pad + * @resizer: ISS RESIZER device + * @fh : V4L2 subdev file handle + * @pad: Pad number + * @fmt: Format + */ +static void +resizer_try_format(struct iss_resizer_device *resizer, + struct v4l2_subdev_fh *fh, unsigned int pad, + struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + enum v4l2_mbus_pixelcode pixelcode; + struct v4l2_mbus_framefmt *format; + unsigned int width = fmt->width; + unsigned int height = fmt->height; + unsigned int i; + + switch (pad) { + case RESIZER_PAD_SINK: + for (i = 0; i < ARRAY_SIZE(resizer_fmts); i++) { + if (fmt->code == resizer_fmts[i]) + break; + } + + /* If not found, use UYVY as default */ + if (i >= ARRAY_SIZE(resizer_fmts)) + fmt->code = V4L2_MBUS_FMT_UYVY8_1X16; + + /* Clamp the input size. */ + fmt->width = clamp_t(u32, width, 1, 8192); + fmt->height = clamp_t(u32, height, 1, 8192); + break; + + case RESIZER_PAD_SOURCE_MEM: + pixelcode = fmt->code; + format = __resizer_get_format(resizer, fh, RESIZER_PAD_SINK, + which); + memcpy(fmt, format, sizeof(*fmt)); + + if ((pixelcode == V4L2_MBUS_FMT_YUYV8_1_5X8) && + (fmt->code == V4L2_MBUS_FMT_UYVY8_1X16)) + fmt->code = pixelcode; + + /* The data formatter truncates the number of horizontal output + * pixels to a multiple of 16. To avoid clipping data, allow + * callers to request an output size bigger than the input size + * up to the nearest multiple of 16. + */ + fmt->width = clamp_t(u32, width, 32, (fmt->width + 15) & ~15); + fmt->width &= ~15; + fmt->height = clamp_t(u32, height, 32, fmt->height); + break; + + } + + fmt->colorspace = V4L2_COLORSPACE_JPEG; + fmt->field = V4L2_FIELD_NONE; +} + +/* + * resizer_enum_mbus_code - Handle pixel format enumeration + * @sd : pointer to v4l2 subdev structure + * @fh : V4L2 subdev file handle + * @code : pointer to v4l2_subdev_mbus_code_enum structure + * return -EINVAL or zero on success + */ +static int resizer_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct iss_resizer_device *resizer = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + switch (code->pad) { + case RESIZER_PAD_SINK: + if (code->index >= ARRAY_SIZE(resizer_fmts)) + return -EINVAL; + + code->code = resizer_fmts[code->index]; + break; + + case RESIZER_PAD_SOURCE_MEM: + format = __resizer_get_format(resizer, fh, RESIZER_PAD_SINK, + V4L2_SUBDEV_FORMAT_TRY); + + if (code->index == 0) { + code->code = format->code; + break; + } + + switch (format->code) { + case V4L2_MBUS_FMT_UYVY8_1X16: + if (code->index == 1) + code->code = V4L2_MBUS_FMT_YUYV8_1_5X8; + else + return -EINVAL; + break; + default: + if (code->index != 0) + return -EINVAL; + } + + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int resizer_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct iss_resizer_device *resizer = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index != 0) + return -EINVAL; + + format.code = fse->code; + format.width = 1; + format.height = 1; + resizer_try_format(resizer, fh, fse->pad, &format, + V4L2_SUBDEV_FORMAT_TRY); + fse->min_width = format.width; + fse->min_height = format.height; + + if (format.code != fse->code) + return -EINVAL; + + format.code = fse->code; + format.width = -1; + format.height = -1; + resizer_try_format(resizer, fh, fse->pad, &format, + V4L2_SUBDEV_FORMAT_TRY); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * resizer_get_format - Retrieve the video format on a pad + * @sd : ISP RESIZER V4L2 subdevice + * @fh : V4L2 subdev file handle + * @fmt: Format + * + * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond + * to the format type. + */ +static int resizer_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct iss_resizer_device *resizer = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __resizer_get_format(resizer, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + fmt->format = *format; + return 0; +} + +/* + * resizer_set_format - Set the video format on a pad + * @sd : ISP RESIZER V4L2 subdevice + * @fh : V4L2 subdev file handle + * @fmt: Format + * + * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond + * to the format type. + */ +static int resizer_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct iss_resizer_device *resizer = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __resizer_get_format(resizer, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + resizer_try_format(resizer, fh, fmt->pad, &fmt->format, fmt->which); + *format = fmt->format; + + /* Propagate the format from sink to source */ + if (fmt->pad == RESIZER_PAD_SINK) { + format = __resizer_get_format(resizer, fh, + RESIZER_PAD_SOURCE_MEM, + fmt->which); + *format = fmt->format; + resizer_try_format(resizer, fh, RESIZER_PAD_SOURCE_MEM, format, + fmt->which); + } + + return 0; +} + +static int resizer_link_validate(struct v4l2_subdev *sd, + struct media_link *link, + struct v4l2_subdev_format *source_fmt, + struct v4l2_subdev_format *sink_fmt) +{ + /* Check if the two ends match */ + if (source_fmt->format.width != sink_fmt->format.width || + source_fmt->format.height != sink_fmt->format.height) + return -EPIPE; + + if (source_fmt->format.code != sink_fmt->format.code) + return -EPIPE; + + return 0; +} + +/* + * resizer_init_formats - Initialize formats on all pads + * @sd: ISP RESIZER 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. + */ +static int resizer_init_formats(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format; + + memset(&format, 0, sizeof(format)); + format.pad = RESIZER_PAD_SINK; + format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + format.format.code = V4L2_MBUS_FMT_UYVY8_1X16; + format.format.width = 4096; + format.format.height = 4096; + resizer_set_format(sd, fh, &format); + + return 0; +} + +/* V4L2 subdev video operations */ +static const struct v4l2_subdev_video_ops resizer_v4l2_video_ops = { + .s_stream = resizer_set_stream, +}; + +/* V4L2 subdev pad operations */ +static const struct v4l2_subdev_pad_ops resizer_v4l2_pad_ops = { + .enum_mbus_code = resizer_enum_mbus_code, + .enum_frame_size = resizer_enum_frame_size, + .get_fmt = resizer_get_format, + .set_fmt = resizer_set_format, + .link_validate = resizer_link_validate, +}; + +/* V4L2 subdev operations */ +static const struct v4l2_subdev_ops resizer_v4l2_ops = { + .video = &resizer_v4l2_video_ops, + .pad = &resizer_v4l2_pad_ops, +}; + +/* V4L2 subdev internal operations */ +static const struct v4l2_subdev_internal_ops resizer_v4l2_internal_ops = { + .open = resizer_init_formats, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +/* + * resizer_link_setup - Setup RESIZER connections + * @entity: RESIZER media entity + * @local: Pad at the local end of the link + * @remote: Pad at the remote end of the link + * @flags: Link flags + * + * return -EINVAL or zero on success + */ +static int resizer_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct iss_resizer_device *resizer = v4l2_get_subdevdata(sd); + struct iss_device *iss = to_iss_device(resizer); + + switch (local->index | media_entity_type(remote->entity)) { + case RESIZER_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV: + /* Read from IPIPE or IPIPEIF. */ + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + resizer->input = RESIZER_INPUT_NONE; + break; + } + + if (resizer->input != RESIZER_INPUT_NONE) + return -EBUSY; + + if (remote->entity == &iss->ipipeif.subdev.entity) + resizer->input = RESIZER_INPUT_IPIPEIF; + else if (remote->entity == &iss->ipipe.subdev.entity) + resizer->input = RESIZER_INPUT_IPIPE; + + + break; + + case RESIZER_PAD_SOURCE_MEM | MEDIA_ENT_T_DEVNODE: + /* Write to memory */ + if (flags & MEDIA_LNK_FL_ENABLED) { + if (resizer->output & ~RESIZER_OUTPUT_MEMORY) + return -EBUSY; + resizer->output |= RESIZER_OUTPUT_MEMORY; + } else { + resizer->output &= ~RESIZER_OUTPUT_MEMORY; + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* media operations */ +static const struct media_entity_operations resizer_media_ops = { + .link_setup = resizer_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +/* + * resizer_init_entities - Initialize V4L2 subdev and media entity + * @resizer: ISS ISP RESIZER module + * + * Return 0 on success and a negative error code on failure. + */ +static int resizer_init_entities(struct iss_resizer_device *resizer) +{ + struct v4l2_subdev *sd = &resizer->subdev; + struct media_pad *pads = resizer->pads; + struct media_entity *me = &sd->entity; + int ret; + + resizer->input = RESIZER_INPUT_NONE; + + v4l2_subdev_init(sd, &resizer_v4l2_ops); + sd->internal_ops = &resizer_v4l2_internal_ops; + strlcpy(sd->name, "OMAP4 ISS ISP resizer", sizeof(sd->name)); + sd->grp_id = 1 << 16; /* group ID for iss subdevs */ + v4l2_set_subdevdata(sd, resizer); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + pads[RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[RESIZER_PAD_SOURCE_MEM].flags = MEDIA_PAD_FL_SOURCE; + + me->ops = &resizer_media_ops; + ret = media_entity_init(me, RESIZER_PADS_NUM, pads, 0); + if (ret < 0) + return ret; + + resizer_init_formats(sd, NULL); + + resizer->video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + resizer->video_out.ops = &resizer_video_ops; + resizer->video_out.iss = to_iss_device(resizer); + resizer->video_out.capture_mem = PAGE_ALIGN(4096 * 4096) * 3; + resizer->video_out.bpl_alignment = 32; + resizer->video_out.bpl_zero_padding = 1; + resizer->video_out.bpl_max = 0x1ffe0; + + ret = omap4iss_video_init(&resizer->video_out, "ISP resizer a"); + if (ret < 0) + return ret; + + /* Connect the RESIZER subdev to the video node. */ + ret = media_entity_create_link(&resizer->subdev.entity, + RESIZER_PAD_SOURCE_MEM, + &resizer->video_out.video.entity, 0, 0); + if (ret < 0) + return ret; + + return 0; +} + +void omap4iss_resizer_unregister_entities(struct iss_resizer_device *resizer) +{ + media_entity_cleanup(&resizer->subdev.entity); + + v4l2_device_unregister_subdev(&resizer->subdev); + omap4iss_video_unregister(&resizer->video_out); +} + +int omap4iss_resizer_register_entities(struct iss_resizer_device *resizer, + struct v4l2_device *vdev) +{ + int ret; + + /* Register the subdev and video node. */ + ret = v4l2_device_register_subdev(vdev, &resizer->subdev); + if (ret < 0) + goto error; + + ret = omap4iss_video_register(&resizer->video_out, vdev); + if (ret < 0) + goto error; + + return 0; + +error: + omap4iss_resizer_unregister_entities(resizer); + return ret; +} + +/* ----------------------------------------------------------------------------- + * ISP RESIZER initialisation and cleanup + */ + +/* + * omap4iss_resizer_init - RESIZER module initialization. + * @iss: Device pointer specific to the OMAP4 ISS. + * + * TODO: Get the initialisation values from platform data. + * + * Return 0 on success or a negative error code otherwise. + */ +int omap4iss_resizer_init(struct iss_device *iss) +{ + struct iss_resizer_device *resizer = &iss->resizer; + + resizer->state = ISS_PIPELINE_STREAM_STOPPED; + init_waitqueue_head(&resizer->wait); + + return resizer_init_entities(resizer); +} + +/* + * omap4iss_resizer_cleanup - RESIZER module cleanup. + * @iss: Device pointer specific to the OMAP4 ISS. + */ +void omap4iss_resizer_cleanup(struct iss_device *iss) +{ + /* FIXME: are you sure there's nothing to do? */ +} diff --git a/drivers/staging/media/omap4iss/iss_resizer.h b/drivers/staging/media/omap4iss/iss_resizer.h new file mode 100644 index 000000000000..3727498b06a3 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_resizer.h @@ -0,0 +1,75 @@ +/* + * TI OMAP4 ISS V4L2 Driver - ISP RESIZER module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.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 OMAP4_ISS_RESIZER_H +#define OMAP4_ISS_RESIZER_H + +#include "iss_video.h" + +enum resizer_input_entity { + RESIZER_INPUT_NONE, + RESIZER_INPUT_IPIPE, + RESIZER_INPUT_IPIPEIF +}; + +#define RESIZER_OUTPUT_MEMORY (1 << 0) + +/* Sink and source RESIZER pads */ +#define RESIZER_PAD_SINK 0 +#define RESIZER_PAD_SOURCE_MEM 1 +#define RESIZER_PADS_NUM 2 + +/* + * struct iss_resizer_device - Structure for the RESIZER module to store its own + * information + * @subdev: V4L2 subdevice + * @pads: Sink and source media entity pads + * @formats: Active video formats + * @input: Active input + * @output: Active outputs + * @video_out: Output video node + * @error: A hardware error occurred during capture + * @state: Streaming state + * @wait: Wait queue used to stop the module + * @stopping: Stopping state + */ +struct iss_resizer_device { + struct v4l2_subdev subdev; + struct media_pad pads[RESIZER_PADS_NUM]; + struct v4l2_mbus_framefmt formats[RESIZER_PADS_NUM]; + + enum resizer_input_entity input; + unsigned int output; + struct iss_video video_out; + unsigned int error; + + enum iss_pipeline_stream_state state; + wait_queue_head_t wait; + atomic_t stopping; +}; + +struct iss_device; + +int omap4iss_resizer_init(struct iss_device *iss); +void omap4iss_resizer_cleanup(struct iss_device *iss); +int omap4iss_resizer_register_entities(struct iss_resizer_device *resizer, + struct v4l2_device *vdev); +void omap4iss_resizer_unregister_entities(struct iss_resizer_device *resizer); + +int omap4iss_resizer_busy(struct iss_resizer_device *resizer); +void omap4iss_resizer_isr(struct iss_resizer_device *resizer, u32 events); +void omap4iss_resizer_restore_context(struct iss_device *iss); +void omap4iss_resizer_max_rate(struct iss_resizer_device *resizer, + unsigned int *max_rate); + +#endif /* OMAP4_ISS_RESIZER_H */ diff --git a/drivers/staging/media/omap4iss/iss_video.c b/drivers/staging/media/omap4iss/iss_video.c new file mode 100644 index 000000000000..8c7f35029cd5 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_video.c @@ -0,0 +1,1226 @@ +/* + * TI OMAP4 ISS V4L2 Driver - Generic video node + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.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 <asm/cacheflush.h> +#include <linux/clk.h> +#include <linux/mm.h> +#include <linux/pagemap.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/module.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-ioctl.h> + +#include "iss_video.h" +#include "iss.h" + + +/* ----------------------------------------------------------------------------- + * Helper functions + */ + +static struct iss_format_info formats[] = { + { V4L2_MBUS_FMT_Y8_1X8, V4L2_MBUS_FMT_Y8_1X8, + V4L2_MBUS_FMT_Y8_1X8, V4L2_MBUS_FMT_Y8_1X8, + V4L2_PIX_FMT_GREY, 8, "Greyscale 8 bpp", }, + { V4L2_MBUS_FMT_Y10_1X10, V4L2_MBUS_FMT_Y10_1X10, + V4L2_MBUS_FMT_Y10_1X10, V4L2_MBUS_FMT_Y8_1X8, + V4L2_PIX_FMT_Y10, 10, "Greyscale 10 bpp", }, + { V4L2_MBUS_FMT_Y12_1X12, V4L2_MBUS_FMT_Y10_1X10, + V4L2_MBUS_FMT_Y12_1X12, V4L2_MBUS_FMT_Y8_1X8, + V4L2_PIX_FMT_Y12, 12, "Greyscale 12 bpp", }, + { V4L2_MBUS_FMT_SBGGR8_1X8, V4L2_MBUS_FMT_SBGGR8_1X8, + V4L2_MBUS_FMT_SBGGR8_1X8, V4L2_MBUS_FMT_SBGGR8_1X8, + V4L2_PIX_FMT_SBGGR8, 8, "BGGR Bayer 8 bpp", }, + { V4L2_MBUS_FMT_SGBRG8_1X8, V4L2_MBUS_FMT_SGBRG8_1X8, + V4L2_MBUS_FMT_SGBRG8_1X8, V4L2_MBUS_FMT_SGBRG8_1X8, + V4L2_PIX_FMT_SGBRG8, 8, "GBRG Bayer 8 bpp", }, + { V4L2_MBUS_FMT_SGRBG8_1X8, V4L2_MBUS_FMT_SGRBG8_1X8, + V4L2_MBUS_FMT_SGRBG8_1X8, V4L2_MBUS_FMT_SGRBG8_1X8, + V4L2_PIX_FMT_SGRBG8, 8, "GRBG Bayer 8 bpp", }, + { V4L2_MBUS_FMT_SRGGB8_1X8, V4L2_MBUS_FMT_SRGGB8_1X8, + V4L2_MBUS_FMT_SRGGB8_1X8, V4L2_MBUS_FMT_SRGGB8_1X8, + V4L2_PIX_FMT_SRGGB8, 8, "RGGB Bayer 8 bpp", }, + { V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8, V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8, + V4L2_MBUS_FMT_SGRBG10_1X10, 0, + V4L2_PIX_FMT_SGRBG10DPCM8, 8, "GRBG Bayer 10 bpp DPCM8", }, + { V4L2_MBUS_FMT_SBGGR10_1X10, V4L2_MBUS_FMT_SBGGR10_1X10, + V4L2_MBUS_FMT_SBGGR10_1X10, V4L2_MBUS_FMT_SBGGR8_1X8, + V4L2_PIX_FMT_SBGGR10, 10, "BGGR Bayer 10 bpp", }, + { V4L2_MBUS_FMT_SGBRG10_1X10, V4L2_MBUS_FMT_SGBRG10_1X10, + V4L2_MBUS_FMT_SGBRG10_1X10, V4L2_MBUS_FMT_SGBRG8_1X8, + V4L2_PIX_FMT_SGBRG10, 10, "GBRG Bayer 10 bpp", }, + { V4L2_MBUS_FMT_SGRBG10_1X10, V4L2_MBUS_FMT_SGRBG10_1X10, + V4L2_MBUS_FMT_SGRBG10_1X10, V4L2_MBUS_FMT_SGRBG8_1X8, + V4L2_PIX_FMT_SGRBG10, 10, "GRBG Bayer 10 bpp", }, + { V4L2_MBUS_FMT_SRGGB10_1X10, V4L2_MBUS_FMT_SRGGB10_1X10, + V4L2_MBUS_FMT_SRGGB10_1X10, V4L2_MBUS_FMT_SRGGB8_1X8, + V4L2_PIX_FMT_SRGGB10, 10, "RGGB Bayer 10 bpp", }, + { V4L2_MBUS_FMT_SBGGR12_1X12, V4L2_MBUS_FMT_SBGGR10_1X10, + V4L2_MBUS_FMT_SBGGR12_1X12, V4L2_MBUS_FMT_SBGGR8_1X8, + V4L2_PIX_FMT_SBGGR12, 12, "BGGR Bayer 12 bpp", }, + { V4L2_MBUS_FMT_SGBRG12_1X12, V4L2_MBUS_FMT_SGBRG10_1X10, + V4L2_MBUS_FMT_SGBRG12_1X12, V4L2_MBUS_FMT_SGBRG8_1X8, + V4L2_PIX_FMT_SGBRG12, 12, "GBRG Bayer 12 bpp", }, + { V4L2_MBUS_FMT_SGRBG12_1X12, V4L2_MBUS_FMT_SGRBG10_1X10, + V4L2_MBUS_FMT_SGRBG12_1X12, V4L2_MBUS_FMT_SGRBG8_1X8, + V4L2_PIX_FMT_SGRBG12, 12, "GRBG Bayer 12 bpp", }, + { V4L2_MBUS_FMT_SRGGB12_1X12, V4L2_MBUS_FMT_SRGGB10_1X10, + V4L2_MBUS_FMT_SRGGB12_1X12, V4L2_MBUS_FMT_SRGGB8_1X8, + V4L2_PIX_FMT_SRGGB12, 12, "RGGB Bayer 12 bpp", }, + { V4L2_MBUS_FMT_UYVY8_1X16, V4L2_MBUS_FMT_UYVY8_1X16, + V4L2_MBUS_FMT_UYVY8_1X16, 0, + V4L2_PIX_FMT_UYVY, 16, "YUV 4:2:2 (UYVY)", }, + { V4L2_MBUS_FMT_YUYV8_1X16, V4L2_MBUS_FMT_YUYV8_1X16, + V4L2_MBUS_FMT_YUYV8_1X16, 0, + V4L2_PIX_FMT_YUYV, 16, "YUV 4:2:2 (YUYV)", }, + { V4L2_MBUS_FMT_YUYV8_1_5X8, V4L2_MBUS_FMT_YUYV8_1_5X8, + V4L2_MBUS_FMT_YUYV8_1_5X8, 0, + V4L2_PIX_FMT_NV12, 8, "YUV 4:2:0 (NV12)", }, +}; + +const struct iss_format_info * +omap4iss_video_format_info(enum v4l2_mbus_pixelcode code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); ++i) { + if (formats[i].code == code) + return &formats[i]; + } + + return NULL; +} + +/* + * iss_video_mbus_to_pix - Convert v4l2_mbus_framefmt to v4l2_pix_format + * @video: ISS video instance + * @mbus: v4l2_mbus_framefmt format (input) + * @pix: v4l2_pix_format format (output) + * + * Fill the output pix structure with information from the input mbus format. + * The bytesperline and sizeimage fields are computed from the requested bytes + * per line value in the pix format and information from the video instance. + * + * Return the number of padding bytes at end of line. + */ +static unsigned int iss_video_mbus_to_pix(const struct iss_video *video, + const struct v4l2_mbus_framefmt *mbus, + struct v4l2_pix_format *pix) +{ + unsigned int bpl = pix->bytesperline; + unsigned int min_bpl; + unsigned int i; + + memset(pix, 0, sizeof(*pix)); + pix->width = mbus->width; + pix->height = mbus->height; + + /* Skip the last format in the loop so that it will be selected if no + * match is found. + */ + for (i = 0; i < ARRAY_SIZE(formats) - 1; ++i) { + if (formats[i].code == mbus->code) + break; + } + + min_bpl = pix->width * ALIGN(formats[i].bpp, 8) / 8; + + /* Clamp the requested bytes per line value. If the maximum bytes per + * line value is zero, the module doesn't support user configurable line + * sizes. Override the requested value with the minimum in that case. + */ + if (video->bpl_max) + bpl = clamp(bpl, min_bpl, video->bpl_max); + else + bpl = min_bpl; + + if (!video->bpl_zero_padding || bpl != min_bpl) + bpl = ALIGN(bpl, video->bpl_alignment); + + pix->pixelformat = formats[i].pixelformat; + pix->bytesperline = bpl; + pix->sizeimage = pix->bytesperline * pix->height; + pix->colorspace = mbus->colorspace; + pix->field = mbus->field; + + /* FIXME: Special case for NV12! We should make this nicer... */ + if (pix->pixelformat == V4L2_PIX_FMT_NV12) + pix->sizeimage += (pix->bytesperline * pix->height) / 2; + + return bpl - min_bpl; +} + +static void iss_video_pix_to_mbus(const struct v4l2_pix_format *pix, + struct v4l2_mbus_framefmt *mbus) +{ + unsigned int i; + + memset(mbus, 0, sizeof(*mbus)); + mbus->width = pix->width; + mbus->height = pix->height; + + for (i = 0; i < ARRAY_SIZE(formats); ++i) { + if (formats[i].pixelformat == pix->pixelformat) + break; + } + + if (WARN_ON(i == ARRAY_SIZE(formats))) + return; + + mbus->code = formats[i].code; + mbus->colorspace = pix->colorspace; + mbus->field = pix->field; +} + +static struct v4l2_subdev * +iss_video_remote_subdev(struct iss_video *video, u32 *pad) +{ + struct media_pad *remote; + + remote = media_entity_remote_pad(&video->pad); + + 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); +} + +/* Return a pointer to the ISS video instance at the far end of the pipeline. */ +static struct iss_video * +iss_video_far_end(struct iss_video *video) +{ + struct media_entity_graph graph; + struct media_entity *entity = &video->video.entity; + struct media_device *mdev = entity->parent; + struct iss_video *far_end = NULL; + + mutex_lock(&mdev->graph_mutex); + media_entity_graph_walk_start(&graph, entity); + + while ((entity = media_entity_graph_walk_next(&graph))) { + if (entity == &video->video.entity) + continue; + + if (media_entity_type(entity) != MEDIA_ENT_T_DEVNODE) + continue; + + far_end = to_iss_video(media_entity_to_video_device(entity)); + if (far_end->type != video->type) + break; + + far_end = NULL; + } + + mutex_unlock(&mdev->graph_mutex); + return far_end; +} + +static int +__iss_video_get_format(struct iss_video *video, + struct v4l2_mbus_framefmt *format) +{ + struct v4l2_subdev_format fmt; + struct v4l2_subdev *subdev; + u32 pad; + int ret; + + subdev = iss_video_remote_subdev(video, &pad); + if (subdev == NULL) + return -EINVAL; + + memset(&fmt, 0, sizeof(fmt)); + fmt.pad = pad; + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + + mutex_lock(&video->mutex); + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); + mutex_unlock(&video->mutex); + + if (ret) + return ret; + + *format = fmt.format; + return 0; +} + +static int +iss_video_check_format(struct iss_video *video, struct iss_video_fh *vfh) +{ + struct v4l2_mbus_framefmt format; + struct v4l2_pix_format pixfmt; + int ret; + + ret = __iss_video_get_format(video, &format); + if (ret < 0) + return ret; + + pixfmt.bytesperline = 0; + ret = iss_video_mbus_to_pix(video, &format, &pixfmt); + + if (vfh->format.fmt.pix.pixelformat != pixfmt.pixelformat || + vfh->format.fmt.pix.height != pixfmt.height || + vfh->format.fmt.pix.width != pixfmt.width || + vfh->format.fmt.pix.bytesperline != pixfmt.bytesperline || + vfh->format.fmt.pix.sizeimage != pixfmt.sizeimage) + return -EINVAL; + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Video queue operations + */ + +static int iss_video_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *count, unsigned int *num_planes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct iss_video_fh *vfh = vb2_get_drv_priv(vq); + struct iss_video *video = vfh->video; + + /* Revisit multi-planar support for NV12 */ + *num_planes = 1; + + sizes[0] = vfh->format.fmt.pix.sizeimage; + if (sizes[0] == 0) + return -EINVAL; + + alloc_ctxs[0] = video->alloc_ctx; + + *count = min(*count, video->capture_mem / PAGE_ALIGN(sizes[0])); + + return 0; +} + +static void iss_video_buf_cleanup(struct vb2_buffer *vb) +{ + struct iss_buffer *buffer = container_of(vb, struct iss_buffer, vb); + + if (buffer->iss_addr) + buffer->iss_addr = 0; +} + +static int iss_video_buf_prepare(struct vb2_buffer *vb) +{ + struct iss_video_fh *vfh = vb2_get_drv_priv(vb->vb2_queue); + struct iss_buffer *buffer = container_of(vb, struct iss_buffer, vb); + struct iss_video *video = vfh->video; + unsigned long size = vfh->format.fmt.pix.sizeimage; + dma_addr_t addr; + + if (vb2_plane_size(vb, 0) < size) + return -ENOBUFS; + + /* Refuse to prepare the buffer is the video node has registered an + * error. We don't need to take any lock here as the operation is + * inherently racy. The authoritative check will be performed in the + * queue handler, which can't return an error, this check is just a best + * effort to notify userspace as early as possible. + */ + if (unlikely(video->error)) + return -EIO; + + addr = vb2_dma_contig_plane_dma_addr(vb, 0); + if (!IS_ALIGNED(addr, 32)) { + dev_dbg(video->iss->dev, + "Buffer address must be aligned to 32 bytes boundary.\n"); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + buffer->iss_addr = addr; + return 0; +} + +static void iss_video_buf_queue(struct vb2_buffer *vb) +{ + struct iss_video_fh *vfh = vb2_get_drv_priv(vb->vb2_queue); + struct iss_video *video = vfh->video; + struct iss_buffer *buffer = container_of(vb, struct iss_buffer, vb); + struct iss_pipeline *pipe = to_iss_pipeline(&video->video.entity); + unsigned long flags; + bool empty; + + spin_lock_irqsave(&video->qlock, flags); + + if (unlikely(video->error)) { + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + spin_unlock_irqrestore(&video->qlock, flags); + return; + } + + empty = list_empty(&video->dmaqueue); + list_add_tail(&buffer->list, &video->dmaqueue); + + spin_unlock_irqrestore(&video->qlock, flags); + + if (empty) { + enum iss_pipeline_state state; + unsigned int start; + + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + state = ISS_PIPELINE_QUEUE_OUTPUT; + else + state = ISS_PIPELINE_QUEUE_INPUT; + + spin_lock_irqsave(&pipe->lock, flags); + pipe->state |= state; + video->ops->queue(video, buffer); + video->dmaqueue_flags |= ISS_VIDEO_DMAQUEUE_QUEUED; + + start = iss_pipeline_ready(pipe); + if (start) + pipe->state |= ISS_PIPELINE_STREAM; + spin_unlock_irqrestore(&pipe->lock, flags); + + if (start) + omap4iss_pipeline_set_stream(pipe, + ISS_PIPELINE_STREAM_SINGLESHOT); + } +} + +static struct vb2_ops iss_video_vb2ops = { + .queue_setup = iss_video_queue_setup, + .buf_prepare = iss_video_buf_prepare, + .buf_queue = iss_video_buf_queue, + .buf_cleanup = iss_video_buf_cleanup, +}; + +/* + * omap4iss_video_buffer_next - Complete the current buffer and return the next + * @video: ISS video object + * + * Remove the current video buffer from the DMA queue and fill its timestamp, + * field count and state fields before waking up its completion handler. + * + * For capture video nodes, the buffer state is set to VB2_BUF_STATE_DONE if no + * error has been flagged in the pipeline, or to VB2_BUF_STATE_ERROR otherwise. + * + * The DMA queue is expected to contain at least one buffer. + * + * Return a pointer to the next buffer in the DMA queue, or NULL if the queue is + * empty. + */ +struct iss_buffer *omap4iss_video_buffer_next(struct iss_video *video) +{ + struct iss_pipeline *pipe = to_iss_pipeline(&video->video.entity); + enum iss_pipeline_state state; + struct iss_buffer *buf; + unsigned long flags; + struct timespec ts; + + spin_lock_irqsave(&video->qlock, flags); + if (WARN_ON(list_empty(&video->dmaqueue))) { + spin_unlock_irqrestore(&video->qlock, flags); + return NULL; + } + + buf = list_first_entry(&video->dmaqueue, struct iss_buffer, + list); + list_del(&buf->list); + spin_unlock_irqrestore(&video->qlock, flags); + + ktime_get_ts(&ts); + buf->vb.v4l2_buf.timestamp.tv_sec = ts.tv_sec; + buf->vb.v4l2_buf.timestamp.tv_usec = ts.tv_nsec / NSEC_PER_USEC; + + /* Do frame number propagation only if this is the output video node. + * Frame number either comes from the CSI receivers or it gets + * incremented here if H3A is not active. + * Note: There is no guarantee that the output buffer will finish + * first, so the input number might lag behind by 1 in some cases. + */ + if (video == pipe->output && !pipe->do_propagation) + buf->vb.v4l2_buf.sequence = + atomic_inc_return(&pipe->frame_number); + else + buf->vb.v4l2_buf.sequence = atomic_read(&pipe->frame_number); + + vb2_buffer_done(&buf->vb, pipe->error ? + VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); + pipe->error = false; + + spin_lock_irqsave(&video->qlock, flags); + if (list_empty(&video->dmaqueue)) { + spin_unlock_irqrestore(&video->qlock, flags); + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + state = ISS_PIPELINE_QUEUE_OUTPUT + | ISS_PIPELINE_STREAM; + else + state = ISS_PIPELINE_QUEUE_INPUT + | ISS_PIPELINE_STREAM; + + spin_lock_irqsave(&pipe->lock, flags); + pipe->state &= ~state; + if (video->pipe.stream_state == ISS_PIPELINE_STREAM_CONTINUOUS) + video->dmaqueue_flags |= ISS_VIDEO_DMAQUEUE_UNDERRUN; + spin_unlock_irqrestore(&pipe->lock, flags); + return NULL; + } + + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && pipe->input != NULL) { + spin_lock(&pipe->lock); + pipe->state &= ~ISS_PIPELINE_STREAM; + spin_unlock(&pipe->lock); + } + + buf = list_first_entry(&video->dmaqueue, struct iss_buffer, + list); + spin_unlock_irqrestore(&video->qlock, flags); + buf->vb.state = VB2_BUF_STATE_ACTIVE; + return buf; +} + +/* + * omap4iss_video_cancel_stream - Cancel stream on a video node + * @video: ISS video object + * + * Cancelling a stream mark all buffers on the video node as erroneous and makes + * sure no new buffer can be queued. + */ +void omap4iss_video_cancel_stream(struct iss_video *video) +{ + unsigned long flags; + + spin_lock_irqsave(&video->qlock, flags); + + while (!list_empty(&video->dmaqueue)) { + struct iss_buffer *buf; + + buf = list_first_entry(&video->dmaqueue, struct iss_buffer, + list); + list_del(&buf->list); + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + } + + video->error = true; + + spin_unlock_irqrestore(&video->qlock, flags); +} + +/* ----------------------------------------------------------------------------- + * V4L2 ioctls + */ + +static int +iss_video_querycap(struct file *file, void *fh, struct v4l2_capability *cap) +{ + struct iss_video *video = video_drvdata(file); + + strlcpy(cap->driver, ISS_VIDEO_DRIVER_NAME, sizeof(cap->driver)); + strlcpy(cap->card, video->video.name, sizeof(cap->card)); + strlcpy(cap->bus_info, "media", sizeof(cap->bus_info)); + + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + else + cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + + cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING + | V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT; + + return 0; +} + +static int +iss_video_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ + struct iss_video *video = video_drvdata(file); + struct v4l2_mbus_framefmt format; + unsigned int index = f->index; + unsigned int i; + int ret; + + if (f->type != video->type) + return -EINVAL; + + ret = __iss_video_get_format(video, &format); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(formats); ++i) { + const struct iss_format_info *info = &formats[i]; + + if (format.code != info->code) + continue; + + if (index == 0) { + f->pixelformat = info->pixelformat; + strlcpy(f->description, info->description, + sizeof(f->description)); + return 0; + } + + index--; + } + + return -EINVAL; +} + +static int +iss_video_get_format(struct file *file, void *fh, struct v4l2_format *format) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + struct iss_video *video = video_drvdata(file); + + if (format->type != video->type) + return -EINVAL; + + mutex_lock(&video->mutex); + *format = vfh->format; + mutex_unlock(&video->mutex); + + return 0; +} + +static int +iss_video_set_format(struct file *file, void *fh, struct v4l2_format *format) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + struct iss_video *video = video_drvdata(file); + struct v4l2_mbus_framefmt fmt; + + if (format->type != video->type) + return -EINVAL; + + mutex_lock(&video->mutex); + + /* Fill the bytesperline and sizeimage fields by converting to media bus + * format and back to pixel format. + */ + iss_video_pix_to_mbus(&format->fmt.pix, &fmt); + iss_video_mbus_to_pix(video, &fmt, &format->fmt.pix); + + vfh->format = *format; + + mutex_unlock(&video->mutex); + return 0; +} + +static int +iss_video_try_format(struct file *file, void *fh, struct v4l2_format *format) +{ + struct iss_video *video = video_drvdata(file); + struct v4l2_subdev_format fmt; + struct v4l2_subdev *subdev; + u32 pad; + int ret; + + if (format->type != video->type) + return -EINVAL; + + subdev = iss_video_remote_subdev(video, &pad); + if (subdev == NULL) + return -EINVAL; + + iss_video_pix_to_mbus(&format->fmt.pix, &fmt.format); + + fmt.pad = pad; + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); + if (ret) + return ret; + + iss_video_mbus_to_pix(video, &fmt.format, &format->fmt.pix); + return 0; +} + +static int +iss_video_cropcap(struct file *file, void *fh, struct v4l2_cropcap *cropcap) +{ + struct iss_video *video = video_drvdata(file); + struct v4l2_subdev *subdev; + int ret; + + subdev = iss_video_remote_subdev(video, NULL); + if (subdev == NULL) + return -EINVAL; + + mutex_lock(&video->mutex); + ret = v4l2_subdev_call(subdev, video, cropcap, cropcap); + mutex_unlock(&video->mutex); + + return ret == -ENOIOCTLCMD ? -ENOTTY : ret; +} + +static int +iss_video_get_crop(struct file *file, void *fh, struct v4l2_crop *crop) +{ + struct iss_video *video = video_drvdata(file); + struct v4l2_subdev_format format; + struct v4l2_subdev *subdev; + u32 pad; + int ret; + + subdev = iss_video_remote_subdev(video, &pad); + if (subdev == NULL) + return -EINVAL; + + /* Try the get crop operation first and fallback to get format if not + * implemented. + */ + ret = v4l2_subdev_call(subdev, video, g_crop, crop); + if (ret != -ENOIOCTLCMD) + return ret; + + format.pad = pad; + format.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &format); + if (ret < 0) + return ret == -ENOIOCTLCMD ? -ENOTTY : ret; + + crop->c.left = 0; + crop->c.top = 0; + crop->c.width = format.format.width; + crop->c.height = format.format.height; + + return 0; +} + +static int +iss_video_set_crop(struct file *file, void *fh, const struct v4l2_crop *crop) +{ + struct iss_video *video = video_drvdata(file); + struct v4l2_subdev *subdev; + int ret; + + subdev = iss_video_remote_subdev(video, NULL); + if (subdev == NULL) + return -EINVAL; + + mutex_lock(&video->mutex); + ret = v4l2_subdev_call(subdev, video, s_crop, crop); + mutex_unlock(&video->mutex); + + return ret == -ENOIOCTLCMD ? -ENOTTY : ret; +} + +static int +iss_video_get_param(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + struct iss_video *video = video_drvdata(file); + + if (video->type != V4L2_BUF_TYPE_VIDEO_OUTPUT || + video->type != a->type) + return -EINVAL; + + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + a->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + a->parm.output.timeperframe = vfh->timeperframe; + + return 0; +} + +static int +iss_video_set_param(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + struct iss_video *video = video_drvdata(file); + + if (video->type != V4L2_BUF_TYPE_VIDEO_OUTPUT || + video->type != a->type) + return -EINVAL; + + if (a->parm.output.timeperframe.denominator == 0) + a->parm.output.timeperframe.denominator = 1; + + vfh->timeperframe = a->parm.output.timeperframe; + + return 0; +} + +static int +iss_video_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *rb) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + + return vb2_reqbufs(&vfh->queue, rb); +} + +static int +iss_video_querybuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + + return vb2_querybuf(&vfh->queue, b); +} + +static int +iss_video_qbuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + + return vb2_qbuf(&vfh->queue, b); +} + +static int +iss_video_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + + return vb2_dqbuf(&vfh->queue, b, file->f_flags & O_NONBLOCK); +} + +/* + * Stream management + * + * Every ISS pipeline has a single input and a single output. The input can be + * either a sensor or a video node. The output is always a video node. + * + * As every pipeline has an output video node, the ISS video objects at the + * pipeline output stores the pipeline state. It tracks the streaming state of + * both the input and output, as well as the availability of buffers. + * + * In sensor-to-memory mode, frames are always available at the pipeline input. + * Starting the sensor usually requires I2C transfers and must be done in + * interruptible context. The pipeline is started and stopped synchronously + * to the stream on/off commands. All modules in the pipeline will get their + * subdev set stream handler called. The module at the end of the pipeline must + * delay starting the hardware until buffers are available at its output. + * + * In memory-to-memory mode, starting/stopping the stream requires + * synchronization between the input and output. ISS modules can't be stopped + * in the middle of a frame, and at least some of the modules seem to become + * busy as soon as they're started, even if they don't receive a frame start + * event. For that reason frames need to be processed in single-shot mode. The + * driver needs to wait until a frame is completely processed and written to + * memory before restarting the pipeline for the next frame. Pipelined + * processing might be possible but requires more testing. + * + * Stream start must be delayed until buffers are available at both the input + * and output. The pipeline must be started in the videobuf queue callback with + * the buffers queue spinlock held. The modules subdev set stream operation must + * not sleep. + */ +static int +iss_video_streamon(struct file *file, void *fh, enum v4l2_buf_type type) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + struct iss_video *video = video_drvdata(file); + struct media_entity_graph graph; + struct media_entity *entity; + enum iss_pipeline_state state; + struct iss_pipeline *pipe; + struct iss_video *far_end; + unsigned long flags; + int ret; + + if (type != video->type) + return -EINVAL; + + mutex_lock(&video->stream_lock); + + /* Start streaming on the pipeline. No link touching an entity in the + * pipeline can be activated or deactivated once streaming is started. + */ + pipe = video->video.entity.pipe + ? to_iss_pipeline(&video->video.entity) : &video->pipe; + pipe->external = NULL; + pipe->external_rate = 0; + pipe->external_bpp = 0; + pipe->entities = 0; + + if (video->iss->pdata->set_constraints) + video->iss->pdata->set_constraints(video->iss, true); + + ret = media_entity_pipeline_start(&video->video.entity, &pipe->pipe); + if (ret < 0) + goto err_media_entity_pipeline_start; + + entity = &video->video.entity; + media_entity_graph_walk_start(&graph, entity); + while ((entity = media_entity_graph_walk_next(&graph))) + pipe->entities |= 1 << entity->id; + + /* Verify that the currently configured format matches the output of + * the connected subdev. + */ + ret = iss_video_check_format(video, vfh); + if (ret < 0) + goto err_iss_video_check_format; + + video->bpl_padding = ret; + video->bpl_value = vfh->format.fmt.pix.bytesperline; + + /* Find the ISS video node connected at the far end of the pipeline and + * update the pipeline. + */ + far_end = iss_video_far_end(video); + + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + state = ISS_PIPELINE_STREAM_OUTPUT | ISS_PIPELINE_IDLE_OUTPUT; + pipe->input = far_end; + pipe->output = video; + } else { + if (far_end == NULL) { + ret = -EPIPE; + goto err_iss_video_check_format; + } + + state = ISS_PIPELINE_STREAM_INPUT | ISS_PIPELINE_IDLE_INPUT; + pipe->input = video; + pipe->output = far_end; + } + + spin_lock_irqsave(&pipe->lock, flags); + pipe->state &= ~ISS_PIPELINE_STREAM; + pipe->state |= state; + spin_unlock_irqrestore(&pipe->lock, flags); + + /* Set the maximum time per frame as the value requested by userspace. + * This is a soft limit that can be overridden if the hardware doesn't + * support the request limit. + */ + if (video->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + pipe->max_timeperframe = vfh->timeperframe; + + video->queue = &vfh->queue; + INIT_LIST_HEAD(&video->dmaqueue); + spin_lock_init(&video->qlock); + video->error = false; + atomic_set(&pipe->frame_number, -1); + + ret = vb2_streamon(&vfh->queue, type); + if (ret < 0) + goto err_iss_video_check_format; + + /* In sensor-to-memory mode, the stream can be started synchronously + * to the stream on command. In memory-to-memory mode, it will be + * started when buffers are queued on both the input and output. + */ + if (pipe->input == NULL) { + unsigned long flags; + ret = omap4iss_pipeline_set_stream(pipe, + ISS_PIPELINE_STREAM_CONTINUOUS); + if (ret < 0) + goto err_omap4iss_set_stream; + spin_lock_irqsave(&video->qlock, flags); + if (list_empty(&video->dmaqueue)) + video->dmaqueue_flags |= ISS_VIDEO_DMAQUEUE_UNDERRUN; + spin_unlock_irqrestore(&video->qlock, flags); + } + + mutex_unlock(&video->stream_lock); + return 0; + +err_omap4iss_set_stream: + vb2_streamoff(&vfh->queue, type); +err_iss_video_check_format: + media_entity_pipeline_stop(&video->video.entity); +err_media_entity_pipeline_start: + if (video->iss->pdata->set_constraints) + video->iss->pdata->set_constraints(video->iss, false); + video->queue = NULL; + + mutex_unlock(&video->stream_lock); + return ret; +} + +static int +iss_video_streamoff(struct file *file, void *fh, enum v4l2_buf_type type) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + struct iss_video *video = video_drvdata(file); + struct iss_pipeline *pipe = to_iss_pipeline(&video->video.entity); + enum iss_pipeline_state state; + unsigned long flags; + + if (type != video->type) + return -EINVAL; + + mutex_lock(&video->stream_lock); + + if (!vb2_is_streaming(&vfh->queue)) + goto done; + + /* Update the pipeline state. */ + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + state = ISS_PIPELINE_STREAM_OUTPUT + | ISS_PIPELINE_QUEUE_OUTPUT; + else + state = ISS_PIPELINE_STREAM_INPUT + | ISS_PIPELINE_QUEUE_INPUT; + + spin_lock_irqsave(&pipe->lock, flags); + pipe->state &= ~state; + spin_unlock_irqrestore(&pipe->lock, flags); + + /* Stop the stream. */ + omap4iss_pipeline_set_stream(pipe, ISS_PIPELINE_STREAM_STOPPED); + vb2_streamoff(&vfh->queue, type); + video->queue = NULL; + + if (video->iss->pdata->set_constraints) + video->iss->pdata->set_constraints(video->iss, false); + media_entity_pipeline_stop(&video->video.entity); + +done: + mutex_unlock(&video->stream_lock); + return 0; +} + +static int +iss_video_enum_input(struct file *file, void *fh, struct v4l2_input *input) +{ + if (input->index > 0) + return -EINVAL; + + strlcpy(input->name, "camera", sizeof(input->name)); + input->type = V4L2_INPUT_TYPE_CAMERA; + + return 0; +} + +static int +iss_video_g_input(struct file *file, void *fh, unsigned int *input) +{ + *input = 0; + + return 0; +} + +static int +iss_video_s_input(struct file *file, void *fh, unsigned int input) +{ + return input == 0 ? 0 : -EINVAL; +} + +static const struct v4l2_ioctl_ops iss_video_ioctl_ops = { + .vidioc_querycap = iss_video_querycap, + .vidioc_enum_fmt_vid_cap = iss_video_enum_format, + .vidioc_g_fmt_vid_cap = iss_video_get_format, + .vidioc_s_fmt_vid_cap = iss_video_set_format, + .vidioc_try_fmt_vid_cap = iss_video_try_format, + .vidioc_g_fmt_vid_out = iss_video_get_format, + .vidioc_s_fmt_vid_out = iss_video_set_format, + .vidioc_try_fmt_vid_out = iss_video_try_format, + .vidioc_cropcap = iss_video_cropcap, + .vidioc_g_crop = iss_video_get_crop, + .vidioc_s_crop = iss_video_set_crop, + .vidioc_g_parm = iss_video_get_param, + .vidioc_s_parm = iss_video_set_param, + .vidioc_reqbufs = iss_video_reqbufs, + .vidioc_querybuf = iss_video_querybuf, + .vidioc_qbuf = iss_video_qbuf, + .vidioc_dqbuf = iss_video_dqbuf, + .vidioc_streamon = iss_video_streamon, + .vidioc_streamoff = iss_video_streamoff, + .vidioc_enum_input = iss_video_enum_input, + .vidioc_g_input = iss_video_g_input, + .vidioc_s_input = iss_video_s_input, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 file operations + */ + +static int iss_video_open(struct file *file) +{ + struct iss_video *video = video_drvdata(file); + struct iss_video_fh *handle; + struct vb2_queue *q; + int ret = 0; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (handle == NULL) + return -ENOMEM; + + v4l2_fh_init(&handle->vfh, &video->video); + v4l2_fh_add(&handle->vfh); + + /* If this is the first user, initialise the pipeline. */ + if (omap4iss_get(video->iss) == NULL) { + ret = -EBUSY; + goto done; + } + + ret = omap4iss_pipeline_pm_use(&video->video.entity, 1); + if (ret < 0) { + omap4iss_put(video->iss); + goto done; + } + + video->alloc_ctx = vb2_dma_contig_init_ctx(video->iss->dev); + if (IS_ERR(video->alloc_ctx)) { + ret = PTR_ERR(video->alloc_ctx); + omap4iss_put(video->iss); + goto done; + } + + q = &handle->queue; + + q->type = video->type; + q->io_modes = VB2_MMAP; + q->drv_priv = handle; + q->ops = &iss_video_vb2ops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct iss_buffer); + q->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + + ret = vb2_queue_init(q); + if (ret) { + omap4iss_put(video->iss); + goto done; + } + + memset(&handle->format, 0, sizeof(handle->format)); + handle->format.type = video->type; + handle->timeperframe.denominator = 1; + + handle->video = video; + file->private_data = &handle->vfh; + +done: + if (ret < 0) { + v4l2_fh_del(&handle->vfh); + kfree(handle); + } + + return ret; +} + +static int iss_video_release(struct file *file) +{ + struct iss_video *video = video_drvdata(file); + struct v4l2_fh *vfh = file->private_data; + struct iss_video_fh *handle = to_iss_video_fh(vfh); + + /* Disable streaming and free the buffers queue resources. */ + iss_video_streamoff(file, vfh, video->type); + + omap4iss_pipeline_pm_use(&video->video.entity, 0); + + /* Release the videobuf2 queue */ + vb2_queue_release(&handle->queue); + + /* Release the file handle. */ + v4l2_fh_del(vfh); + kfree(handle); + file->private_data = NULL; + + omap4iss_put(video->iss); + + return 0; +} + +static unsigned int iss_video_poll(struct file *file, poll_table *wait) +{ + struct iss_video_fh *vfh = to_iss_video_fh(file->private_data); + + return vb2_poll(&vfh->queue, file, wait); +} + +static int iss_video_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct iss_video_fh *vfh = to_iss_video_fh(file->private_data); + + return vb2_mmap(&vfh->queue, vma); +} + +static struct v4l2_file_operations iss_video_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = iss_video_open, + .release = iss_video_release, + .poll = iss_video_poll, + .mmap = iss_video_mmap, +}; + +/* ----------------------------------------------------------------------------- + * ISS video core + */ + +static const struct iss_video_operations iss_video_dummy_ops = { +}; + +int omap4iss_video_init(struct iss_video *video, const char *name) +{ + const char *direction; + int ret; + + switch (video->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + direction = "output"; + video->pad.flags = MEDIA_PAD_FL_SINK; + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + direction = "input"; + video->pad.flags = MEDIA_PAD_FL_SOURCE; + break; + + default: + return -EINVAL; + } + + ret = media_entity_init(&video->video.entity, 1, &video->pad, 0); + if (ret < 0) + return ret; + + mutex_init(&video->mutex); + atomic_set(&video->active, 0); + + spin_lock_init(&video->pipe.lock); + mutex_init(&video->stream_lock); + + /* Initialize the video device. */ + if (video->ops == NULL) + video->ops = &iss_video_dummy_ops; + + video->video.fops = &iss_video_fops; + snprintf(video->video.name, sizeof(video->video.name), + "OMAP4 ISS %s %s", name, direction); + video->video.vfl_type = VFL_TYPE_GRABBER; + video->video.release = video_device_release_empty; + video->video.ioctl_ops = &iss_video_ioctl_ops; + video->pipe.stream_state = ISS_PIPELINE_STREAM_STOPPED; + + video_set_drvdata(&video->video, video); + + return 0; +} + +void omap4iss_video_cleanup(struct iss_video *video) +{ + media_entity_cleanup(&video->video.entity); + mutex_destroy(&video->stream_lock); + mutex_destroy(&video->mutex); +} + +int omap4iss_video_register(struct iss_video *video, struct v4l2_device *vdev) +{ + int ret; + + video->video.v4l2_dev = vdev; + + ret = video_register_device(&video->video, VFL_TYPE_GRABBER, -1); + if (ret < 0) + dev_err(video->iss->dev, + "%s: could not register video device (%d)\n", + __func__, ret); + + return ret; +} + +void omap4iss_video_unregister(struct iss_video *video) +{ + video_unregister_device(&video->video); +} diff --git a/drivers/staging/media/omap4iss/iss_video.h b/drivers/staging/media/omap4iss/iss_video.h new file mode 100644 index 000000000000..878e4a3082e7 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_video.h @@ -0,0 +1,204 @@ +/* + * TI OMAP4 ISS V4L2 Driver - Generic video node + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.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 OMAP4_ISS_VIDEO_H +#define OMAP4_ISS_VIDEO_H + +#include <linux/v4l2-mediabus.h> +#include <media/media-entity.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-fh.h> +#include <media/videobuf2-core.h> +#include <media/videobuf2-dma-contig.h> + +#define ISS_VIDEO_DRIVER_NAME "issvideo" +#define ISS_VIDEO_DRIVER_VERSION "0.0.2" + +struct iss_device; +struct iss_video; +struct v4l2_mbus_framefmt; +struct v4l2_pix_format; + +/* + * struct iss_format_info - ISS media bus format information + * @code: V4L2 media bus format code + * @truncated: V4L2 media bus format code for the same format truncated to 10 + * bits. Identical to @code if the format is 10 bits wide or less. + * @uncompressed: V4L2 media bus format code for the corresponding uncompressed + * format. Identical to @code if the format is not DPCM compressed. + * @flavor: V4L2 media bus format code for the same pixel layout but + * shifted to be 8 bits per pixel. =0 if format is not shiftable. + * @pixelformat: V4L2 pixel format FCC identifier + * @bpp: Bits per pixel + * @description: Human-readable format description + */ +struct iss_format_info { + enum v4l2_mbus_pixelcode code; + enum v4l2_mbus_pixelcode truncated; + enum v4l2_mbus_pixelcode uncompressed; + enum v4l2_mbus_pixelcode flavor; + u32 pixelformat; + unsigned int bpp; + const char *description; +}; + +enum iss_pipeline_stream_state { + ISS_PIPELINE_STREAM_STOPPED = 0, + ISS_PIPELINE_STREAM_CONTINUOUS = 1, + ISS_PIPELINE_STREAM_SINGLESHOT = 2, +}; + +enum iss_pipeline_state { + /* The stream has been started on the input video node. */ + ISS_PIPELINE_STREAM_INPUT = 1, + /* The stream has been started on the output video node. */ + ISS_PIPELINE_STREAM_OUTPUT = (1 << 1), + /* At least one buffer is queued on the input video node. */ + ISS_PIPELINE_QUEUE_INPUT = (1 << 2), + /* At least one buffer is queued on the output video node. */ + ISS_PIPELINE_QUEUE_OUTPUT = (1 << 3), + /* The input entity is idle, ready to be started. */ + ISS_PIPELINE_IDLE_INPUT = (1 << 4), + /* The output entity is idle, ready to be started. */ + ISS_PIPELINE_IDLE_OUTPUT = (1 << 5), + /* The pipeline is currently streaming. */ + ISS_PIPELINE_STREAM = (1 << 6), +}; + +/* + * struct iss_pipeline - An OMAP4 ISS hardware pipeline + * @entities: Bitmask of entities in the pipeline (indexed by entity ID) + * @error: A hardware error occurred during capture + */ +struct iss_pipeline { + struct media_pipeline pipe; + spinlock_t lock; /* Pipeline state and queue flags */ + unsigned int state; + enum iss_pipeline_stream_state stream_state; + struct iss_video *input; + struct iss_video *output; + unsigned int entities; + atomic_t frame_number; + bool do_propagation; /* of frame number */ + bool error; + struct v4l2_fract max_timeperframe; + struct v4l2_subdev *external; + unsigned int external_rate; + int external_bpp; +}; + +#define to_iss_pipeline(__e) \ + container_of((__e)->pipe, struct iss_pipeline, pipe) + +static inline int iss_pipeline_ready(struct iss_pipeline *pipe) +{ + return pipe->state == (ISS_PIPELINE_STREAM_INPUT | + ISS_PIPELINE_STREAM_OUTPUT | + ISS_PIPELINE_QUEUE_INPUT | + ISS_PIPELINE_QUEUE_OUTPUT | + ISS_PIPELINE_IDLE_INPUT | + ISS_PIPELINE_IDLE_OUTPUT); +} + +/* + * struct iss_buffer - ISS buffer + * @buffer: ISS video buffer + * @iss_addr: Physical address of the buffer. + */ +struct iss_buffer { + /* common v4l buffer stuff -- must be first */ + struct vb2_buffer vb; + struct list_head list; + dma_addr_t iss_addr; +}; + +#define to_iss_buffer(buf) container_of(buf, struct iss_buffer, buffer) + +enum iss_video_dmaqueue_flags { + /* Set if DMA queue becomes empty when ISS_PIPELINE_STREAM_CONTINUOUS */ + ISS_VIDEO_DMAQUEUE_UNDERRUN = (1 << 0), + /* Set when queuing buffer to an empty DMA queue */ + ISS_VIDEO_DMAQUEUE_QUEUED = (1 << 1), +}; + +#define iss_video_dmaqueue_flags_clr(video) \ + ({ (video)->dmaqueue_flags = 0; }) + +/* + * struct iss_video_operations - ISS video operations + * @queue: Resume streaming when a buffer is queued. Called on VIDIOC_QBUF + * if there was no buffer previously queued. + */ +struct iss_video_operations { + int(*queue)(struct iss_video *video, struct iss_buffer *buffer); +}; + +struct iss_video { + struct video_device video; + enum v4l2_buf_type type; + struct media_pad pad; + + struct mutex mutex; /* format and crop settings */ + atomic_t active; + + struct iss_device *iss; + + unsigned int capture_mem; + unsigned int bpl_alignment; /* alignment value */ + unsigned int bpl_zero_padding; /* whether the alignment is optional */ + unsigned int bpl_max; /* maximum bytes per line value */ + unsigned int bpl_value; /* bytes per line value */ + unsigned int bpl_padding; /* padding at end of line */ + + /* Pipeline state */ + struct iss_pipeline pipe; + struct mutex stream_lock; /* pipeline and stream states */ + bool error; + + /* Video buffers queue */ + struct vb2_queue *queue; + spinlock_t qlock; /* protects dmaqueue and error */ + struct list_head dmaqueue; + enum iss_video_dmaqueue_flags dmaqueue_flags; + struct vb2_alloc_ctx *alloc_ctx; + + const struct iss_video_operations *ops; +}; + +#define to_iss_video(vdev) container_of(vdev, struct iss_video, video) + +struct iss_video_fh { + struct v4l2_fh vfh; + struct iss_video *video; + struct vb2_queue queue; + struct v4l2_format format; + struct v4l2_fract timeperframe; +}; + +#define to_iss_video_fh(fh) container_of(fh, struct iss_video_fh, vfh) +#define iss_video_queue_to_iss_video_fh(q) \ + container_of(q, struct iss_video_fh, queue) + +int omap4iss_video_init(struct iss_video *video, const char *name); +void omap4iss_video_cleanup(struct iss_video *video); +int omap4iss_video_register(struct iss_video *video, + struct v4l2_device *vdev); +void omap4iss_video_unregister(struct iss_video *video); +struct iss_buffer *omap4iss_video_buffer_next(struct iss_video *video); +void omap4iss_video_cancel_stream(struct iss_video *video); +struct media_pad *omap4iss_video_remote_pad(struct iss_video *video); + +const struct iss_format_info * +omap4iss_video_format_info(enum v4l2_mbus_pixelcode code); + +#endif /* OMAP4_ISS_VIDEO_H */ diff --git a/drivers/media/usb/sn9c102/Kconfig b/drivers/staging/media/sn9c102/Kconfig index 6ebaf2940d06..c9aba59258d9 100644 --- a/drivers/media/usb/sn9c102/Kconfig +++ b/drivers/staging/media/sn9c102/Kconfig @@ -1,14 +1,17 @@ config USB_SN9C102 tristate "USB SN9C1xx PC Camera Controller support (DEPRECATED)" - depends on VIDEO_V4L2 + depends on VIDEO_V4L2 && MEDIA_USB_SUPPORT ---help--- - This driver is DEPRECATED please use the gspca sonixb and + This driver is DEPRECATED, please use the gspca sonixb and sonixj modules instead. Say Y here if you want support for cameras based on SONiX SN9C101, SN9C102, SN9C103, SN9C105 and SN9C120 PC Camera Controllers. - See <file:Documentation/video4linux/sn9c102.txt> for more info. + See <file:drivers/staging/media/sn9c102/sn9c102.txt> for more info. + + If you have webcams that are only supported by this driver and not by + the gspca driver, then contact the linux-media mailinglist. To compile this driver as a module, choose M here: the module will be called sn9c102. diff --git a/drivers/media/usb/sn9c102/Makefile b/drivers/staging/media/sn9c102/Makefile index 7ecd5a90c7c9..7ecd5a90c7c9 100644 --- a/drivers/media/usb/sn9c102/Makefile +++ b/drivers/staging/media/sn9c102/Makefile diff --git a/drivers/media/usb/sn9c102/sn9c102.h b/drivers/staging/media/sn9c102/sn9c102.h index 8a917f060503..8a917f060503 100644 --- a/drivers/media/usb/sn9c102/sn9c102.h +++ b/drivers/staging/media/sn9c102/sn9c102.h diff --git a/drivers/staging/media/sn9c102/sn9c102.txt b/drivers/staging/media/sn9c102/sn9c102.txt new file mode 100644 index 000000000000..b4f67040403a --- /dev/null +++ b/drivers/staging/media/sn9c102/sn9c102.txt @@ -0,0 +1,592 @@ + + SN9C1xx PC Camera Controllers + Driver for Linux + ============================= + + - Documentation - + + +Index +===== +1. Copyright +2. Disclaimer +3. License +4. Overview and features +5. Module dependencies +6. Module loading +7. Module parameters +8. Optional device control through "sysfs" +9. Supported devices +10. Notes for V4L2 application developers +11. Video frame formats +12. Contact information +13. Credits + + +1. Copyright +============ +Copyright (C) 2004-2007 by Luca Risolia <luca.risolia@studio.unibo.it> + + +2. Disclaimer +============= +SONiX is a trademark of SONiX Technology Company Limited, inc. +This software is not sponsored or developed by SONiX. + + +3. License +========== +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., 675 Mass Ave, Cambridge, MA 02139, USA. + + +4. Overview and features +======================== +This driver attempts to support the video interface of the devices assembling +the SONiX SN9C101, SN9C102, SN9C103, SN9C105 and SN9C120 PC Camera Controllers +("SN9C1xx" from now on). + +The driver relies on the Video4Linux2 and USB core modules. It has been +designed to run properly on SMP systems as well. + +The latest version of the SN9C1xx driver can be found at the following URL: +http://www.linux-projects.org/ + +Some of the features of the driver are: + +- full compliance with the Video4Linux2 API (see also "Notes for V4L2 + application developers" paragraph); +- available mmap or read/poll methods for video streaming through isochronous + data transfers; +- automatic detection of image sensor; +- support for built-in microphone interface; +- support for any window resolutions and optional panning within the maximum + pixel area of image sensor; +- image downscaling with arbitrary scaling factors from 1, 2 and 4 in both + directions (see "Notes for V4L2 application developers" paragraph); +- two different video formats for uncompressed or compressed data in low or + high compression quality (see also "Notes for V4L2 application developers" + and "Video frame formats" paragraphs); +- full support for the capabilities of many of the possible image sensors that + can be connected to the SN9C1xx bridges, including, for instance, red, green, + blue and global gain adjustments and exposure (see "Supported devices" + paragraph for details); +- use of default color settings for sunlight conditions; +- dynamic I/O interface for both SN9C1xx and image sensor control and + monitoring (see "Optional device control through 'sysfs'" paragraph); +- dynamic driver control thanks to various module parameters (see "Module + parameters" paragraph); +- up to 64 cameras can be handled at the same time; they can be connected and + disconnected from the host many times without turning off the computer, if + the system supports hotplugging; +- no known bugs. + + +5. Module dependencies +====================== +For it to work properly, the driver needs kernel support for Video4Linux and +USB. + +The following options of the kernel configuration file must be enabled and +corresponding modules must be compiled: + + # Multimedia devices + # + CONFIG_VIDEO_DEV=m + +To enable advanced debugging functionality on the device through /sysfs: + + # Multimedia devices + # + CONFIG_VIDEO_ADV_DEBUG=y + + # USB support + # + CONFIG_USB=m + +In addition, depending on the hardware being used, the modules below are +necessary: + + # USB Host Controller Drivers + # + CONFIG_USB_EHCI_HCD=m + CONFIG_USB_UHCI_HCD=m + CONFIG_USB_OHCI_HCD=m + +The SN9C103, SN9c105 and SN9C120 controllers also provide a built-in microphone +interface. It is supported by the USB Audio driver thanks to the ALSA API: + + # Sound + # + CONFIG_SOUND=y + + # Advanced Linux Sound Architecture + # + CONFIG_SND=m + + # USB devices + # + CONFIG_SND_USB_AUDIO=m + +And finally: + + # USB Multimedia devices + # + CONFIG_USB_SN9C102=m + + +6. Module loading +================= +To use the driver, it is necessary to load the "sn9c102" module into memory +after every other module required: "videodev", "v4l2_common", "compat_ioctl32", +"usbcore" and, depending on the USB host controller you have, "ehci-hcd", +"uhci-hcd" or "ohci-hcd". + +Loading can be done as shown below: + + [root@localhost home]# modprobe sn9c102 + +Note that the module is called "sn9c102" for historic reasons, although it +does not just support the SN9C102. + +At this point all the devices supported by the driver and connected to the USB +ports should be recognized. You can invoke "dmesg" to analyze kernel messages +and verify that the loading process has gone well: + + [user@localhost home]$ dmesg + +or, to isolate all the kernel messages generated by the driver: + + [user@localhost home]$ dmesg | grep sn9c102 + + +7. Module parameters +==================== +Module parameters are listed below: +------------------------------------------------------------------------------- +Name: video_nr +Type: short array (min = 0, max = 64) +Syntax: <-1|n[,...]> +Description: Specify V4L2 minor mode number: + -1 = use next available + n = use minor number n + You can specify up to 64 cameras this way. + For example: + video_nr=-1,2,-1 would assign minor number 2 to the second + recognized camera and use auto for the first one and for every + other camera. +Default: -1 +------------------------------------------------------------------------------- +Name: force_munmap +Type: bool array (min = 0, max = 64) +Syntax: <0|1[,...]> +Description: Force the application to unmap previously mapped buffer memory + before calling any VIDIOC_S_CROP or VIDIOC_S_FMT ioctl's. Not + all the applications support this feature. This parameter is + specific for each detected camera. + 0 = do not force memory unmapping + 1 = force memory unmapping (save memory) +Default: 0 +------------------------------------------------------------------------------- +Name: frame_timeout +Type: uint array (min = 0, max = 64) +Syntax: <0|n[,...]> +Description: Timeout for a video frame in seconds before returning an I/O + error; 0 for infinity. This parameter is specific for each + detected camera and can be changed at runtime thanks to the + /sys filesystem interface. +Default: 2 +------------------------------------------------------------------------------- +Name: debug +Type: ushort +Syntax: <n> +Description: Debugging information level, from 0 to 3: + 0 = none (use carefully) + 1 = critical errors + 2 = significant information + 3 = more verbose messages + Level 3 is useful for testing only. It also shows some more + information about the hardware being detected. + This parameter can be changed at runtime thanks to the /sys + filesystem interface. +Default: 2 +------------------------------------------------------------------------------- + + +8. Optional device control through "sysfs" [1] +========================================== +If the kernel has been compiled with the CONFIG_VIDEO_ADV_DEBUG option enabled, +it is possible to read and write both the SN9C1xx and the image sensor +registers by using the "sysfs" filesystem interface. + +Every time a supported device is recognized, a write-only file named "green" is +created in the /sys/class/video4linux/videoX directory. You can set the green +channel's gain by writing the desired value to it. The value may range from 0 +to 15 for the SN9C101 or SN9C102 bridges, from 0 to 127 for the SN9C103, +SN9C105 and SN9C120 bridges. +Similarly, only for the SN9C103, SN9C105 and SN9C120 controllers, blue and red +gain control files are available in the same directory, for which accepted +values may range from 0 to 127. + +There are other four entries in the directory above for each registered camera: +"reg", "val", "i2c_reg" and "i2c_val". The first two files control the +SN9C1xx bridge, while the other two control the sensor chip. "reg" and +"i2c_reg" hold the values of the current register index where the following +reading/writing operations are addressed at through "val" and "i2c_val". Their +use is not intended for end-users. Note that "i2c_reg" and "i2c_val" will not +be created if the sensor does not actually support the standard I2C protocol or +its registers are not 8-bit long. Also, remember that you must be logged in as +root before writing to them. + +As an example, suppose we were to want to read the value contained in the +register number 1 of the sensor register table - which is usually the product +identifier - of the camera registered as "/dev/video0": + + [root@localhost #] cd /sys/class/video4linux/video0 + [root@localhost #] echo 1 > i2c_reg + [root@localhost #] cat i2c_val + +Note that "cat" will fail if sensor registers cannot be read. + +Now let's set the green gain's register of the SN9C101 or SN9C102 chips to 2: + + [root@localhost #] echo 0x11 > reg + [root@localhost #] echo 2 > val + +Note that the SN9C1xx always returns 0 when some of its registers are read. +To avoid race conditions, all the I/O accesses to the above files are +serialized. +The sysfs interface also provides the "frame_header" entry, which exports the +frame header of the most recent requested and captured video frame. The header +is always 18-bytes long and is appended to every video frame by the SN9C1xx +controllers. As an example, this additional information can be used by the user +application for implementing auto-exposure features via software. + +The following table describes the frame header exported by the SN9C101 and +SN9C102: + +Byte # Value or bits Description +------ ------------- ----------- +0x00 0xFF Frame synchronisation pattern +0x01 0xFF Frame synchronisation pattern +0x02 0x00 Frame synchronisation pattern +0x03 0xC4 Frame synchronisation pattern +0x04 0xC4 Frame synchronisation pattern +0x05 0x96 Frame synchronisation pattern +0x06 [3:0] Read channel gain control = (1+R_GAIN/8) + [7:4] Blue channel gain control = (1+B_GAIN/8) +0x07 [ 0 ] Compression mode. 0=No compression, 1=Compression enabled + [2:1] Maximum scale factor for compression + [ 3 ] 1 = USB fifo(2K bytes) is full + [ 4 ] 1 = Digital gain is finish + [ 5 ] 1 = Exposure is finish + [7:6] Frame index +0x08 [7:0] Y sum inside Auto-Exposure area (low-byte) +0x09 [7:0] Y sum inside Auto-Exposure area (high-byte) + where Y sum = (R/4 + 5G/16 + B/8) / 32 +0x0A [7:0] Y sum outside Auto-Exposure area (low-byte) +0x0B [7:0] Y sum outside Auto-Exposure area (high-byte) + where Y sum = (R/4 + 5G/16 + B/8) / 128 +0x0C 0xXX Not used +0x0D 0xXX Not used +0x0E 0xXX Not used +0x0F 0xXX Not used +0x10 0xXX Not used +0x11 0xXX Not used + +The following table describes the frame header exported by the SN9C103: + +Byte # Value or bits Description +------ ------------- ----------- +0x00 0xFF Frame synchronisation pattern +0x01 0xFF Frame synchronisation pattern +0x02 0x00 Frame synchronisation pattern +0x03 0xC4 Frame synchronisation pattern +0x04 0xC4 Frame synchronisation pattern +0x05 0x96 Frame synchronisation pattern +0x06 [6:0] Read channel gain control = (1/2+R_GAIN/64) +0x07 [6:0] Blue channel gain control = (1/2+B_GAIN/64) + [7:4] +0x08 [ 0 ] Compression mode. 0=No compression, 1=Compression enabled + [2:1] Maximum scale factor for compression + [ 3 ] 1 = USB fifo(2K bytes) is full + [ 4 ] 1 = Digital gain is finish + [ 5 ] 1 = Exposure is finish + [7:6] Frame index +0x09 [7:0] Y sum inside Auto-Exposure area (low-byte) +0x0A [7:0] Y sum inside Auto-Exposure area (high-byte) + where Y sum = (R/4 + 5G/16 + B/8) / 32 +0x0B [7:0] Y sum outside Auto-Exposure area (low-byte) +0x0C [7:0] Y sum outside Auto-Exposure area (high-byte) + where Y sum = (R/4 + 5G/16 + B/8) / 128 +0x0D [1:0] Audio frame number + [ 2 ] 1 = Audio is recording +0x0E [7:0] Audio summation (low-byte) +0x0F [7:0] Audio summation (high-byte) +0x10 [7:0] Audio sample count +0x11 [7:0] Audio peak data in audio frame + +The AE area (sx, sy, ex, ey) in the active window can be set by programming the +registers 0x1c, 0x1d, 0x1e and 0x1f of the SN9C1xx controllers, where one unit +corresponds to 32 pixels. + +[1] The frame headers exported by the SN9C105 and SN9C120 are not described. + + +9. Supported devices +==================== +None of the names of the companies as well as their products will be mentioned +here. They have never collaborated with the author, so no advertising. + +From the point of view of a driver, what unambiguously identify a device are +its vendor and product USB identifiers. Below is a list of known identifiers of +devices assembling the SN9C1xx PC camera controllers: + +Vendor ID Product ID +--------- ---------- +0x0458 0x7025 +0x045e 0x00f5 +0x045e 0x00f7 +0x0471 0x0327 +0x0471 0x0328 +0x0c45 0x6001 +0x0c45 0x6005 +0x0c45 0x6007 +0x0c45 0x6009 +0x0c45 0x600d +0x0c45 0x6011 +0x0c45 0x6019 +0x0c45 0x6024 +0x0c45 0x6025 +0x0c45 0x6028 +0x0c45 0x6029 +0x0c45 0x602a +0x0c45 0x602b +0x0c45 0x602c +0x0c45 0x602d +0x0c45 0x602e +0x0c45 0x6030 +0x0c45 0x603f +0x0c45 0x6080 +0x0c45 0x6082 +0x0c45 0x6083 +0x0c45 0x6088 +0x0c45 0x608a +0x0c45 0x608b +0x0c45 0x608c +0x0c45 0x608e +0x0c45 0x608f +0x0c45 0x60a0 +0x0c45 0x60a2 +0x0c45 0x60a3 +0x0c45 0x60a8 +0x0c45 0x60aa +0x0c45 0x60ab +0x0c45 0x60ac +0x0c45 0x60ae +0x0c45 0x60af +0x0c45 0x60b0 +0x0c45 0x60b2 +0x0c45 0x60b3 +0x0c45 0x60b8 +0x0c45 0x60ba +0x0c45 0x60bb +0x0c45 0x60bc +0x0c45 0x60be +0x0c45 0x60c0 +0x0c45 0x60c2 +0x0c45 0x60c8 +0x0c45 0x60cc +0x0c45 0x60ea +0x0c45 0x60ec +0x0c45 0x60ef +0x0c45 0x60fa +0x0c45 0x60fb +0x0c45 0x60fc +0x0c45 0x60fe +0x0c45 0x6102 +0x0c45 0x6108 +0x0c45 0x610f +0x0c45 0x6130 +0x0c45 0x6138 +0x0c45 0x613a +0x0c45 0x613b +0x0c45 0x613c +0x0c45 0x613e + +The list above does not imply that all those devices work with this driver: up +until now only the ones that assemble the following pairs of SN9C1xx bridges +and image sensors are supported; kernel messages will always tell you whether +this is the case (see "Module loading" paragraph): + +Image sensor / SN9C1xx bridge | SN9C10[12] SN9C103 SN9C105 SN9C120 +------------------------------------------------------------------------------- +HV7131D Hynix Semiconductor | Yes No No No +HV7131R Hynix Semiconductor | No Yes Yes Yes +MI-0343 Micron Technology | Yes No No No +MI-0360 Micron Technology | No Yes Yes Yes +OV7630 OmniVision Technologies | Yes Yes Yes Yes +OV7660 OmniVision Technologies | No No Yes Yes +PAS106B PixArt Imaging | Yes No No No +PAS202B PixArt Imaging | Yes Yes No No +TAS5110C1B Taiwan Advanced Sensor | Yes No No No +TAS5110D Taiwan Advanced Sensor | Yes No No No +TAS5130D1B Taiwan Advanced Sensor | Yes No No No + +"Yes" means that the pair is supported by the driver, while "No" means that the +pair does not exist or is not supported by the driver. + +Only some of the available control settings of each image sensor are supported +through the V4L2 interface. + +Donations of new models for further testing and support would be much +appreciated. Non-available hardware will not be supported by the author of this +driver. + + +10. Notes for V4L2 application developers +========================================= +This driver follows the V4L2 API specifications. In particular, it enforces two +rules: + +- exactly one I/O method, either "mmap" or "read", is associated with each +file descriptor. Once it is selected, the application must close and reopen the +device to switch to the other I/O method; + +- although it is not mandatory, previously mapped buffer memory should always +be unmapped before calling any "VIDIOC_S_CROP" or "VIDIOC_S_FMT" ioctl's. +The same number of buffers as before will be allocated again to match the size +of the new video frames, so you have to map the buffers again before any I/O +attempts on them. + +Consistently with the hardware limits, this driver also supports image +downscaling with arbitrary scaling factors from 1, 2 and 4 in both directions. +However, the V4L2 API specifications don't correctly define how the scaling +factor can be chosen arbitrarily by the "negotiation" of the "source" and +"target" rectangles. To work around this flaw, we have added the convention +that, during the negotiation, whenever the "VIDIOC_S_CROP" ioctl is issued, the +scaling factor is restored to 1. + +This driver supports two different video formats: the first one is the "8-bit +Sequential Bayer" format and can be used to obtain uncompressed video data +from the device through the current I/O method, while the second one provides +either "raw" compressed video data (without frame headers not related to the +compressed data) or standard JPEG (with frame headers). The compression quality +may vary from 0 to 1 and can be selected or queried thanks to the +VIDIOC_S_JPEGCOMP and VIDIOC_G_JPEGCOMP V4L2 ioctl's. For maximum flexibility, +both the default active video format and the default compression quality +depend on how the image sensor being used is initialized. + + +11. Video frame formats [1] +======================= +The SN9C1xx PC Camera Controllers can send images in two possible video +formats over the USB: either native "Sequential RGB Bayer" or compressed. +The compression is used to achieve high frame rates. With regard to the +SN9C101, SN9C102 and SN9C103, the compression is based on the Huffman encoding +algorithm described below, while with regard to the SN9C105 and SN9C120 the +compression is based on the JPEG standard. +The current video format may be selected or queried from the user application +by calling the VIDIOC_S_FMT or VIDIOC_G_FMT ioctl's, as described in the V4L2 +API specifications. + +The name "Sequential Bayer" indicates the organization of the red, green and +blue pixels in one video frame. Each pixel is associated with a 8-bit long +value and is disposed in memory according to the pattern shown below: + +B[0] G[1] B[2] G[3] ... B[m-2] G[m-1] +G[m] R[m+1] G[m+2] R[m+2] ... G[2m-2] R[2m-1] +... +... B[(n-1)(m-2)] G[(n-1)(m-1)] +... G[n(m-2)] R[n(m-1)] + +The above matrix also represents the sequential or progressive read-out mode of +the (n, m) Bayer color filter array used in many CCD or CMOS image sensors. + +The Huffman compressed video frame consists of a bitstream that encodes for +every R, G, or B pixel the difference between the value of the pixel itself and +some reference pixel value. Pixels are organised in the Bayer pattern and the +Bayer sub-pixels are tracked individually and alternatingly. For example, in +the first line values for the B and G1 pixels are alternatingly encoded, while +in the second line values for the G2 and R pixels are alternatingly encoded. + +The pixel reference value is calculated as follows: +- the 4 top left pixels are encoded in raw uncompressed 8-bit format; +- the value in the top two rows is the value of the pixel left of the current + pixel; +- the value in the left column is the value of the pixel above the current + pixel; +- for all other pixels, the reference value is the average of the value of the + pixel on the left and the value of the pixel above the current pixel; +- there is one code in the bitstream that specifies the value of a pixel + directly (in 4-bit resolution); +- pixel values need to be clamped inside the range [0..255] for proper + decoding. + +The algorithm purely describes the conversion from compressed Bayer code used +in the SN9C101, SN9C102 and SN9C103 chips to uncompressed Bayer. Additional +steps are required to convert this to a color image (i.e. a color interpolation +algorithm). + +The following Huffman codes have been found: +0: +0 (relative to reference pixel value) +100: +4 +101: -4? +1110xxxx: set absolute value to xxxx.0000 +1101: +11 +1111: -11 +11001: +20 +110000: -20 +110001: ??? - these codes are apparently not used + +[1] The Huffman compression algorithm has been reverse-engineered and + documented by Bertrik Sikken. + + +12. Contact information +======================= +The author may be contacted by e-mail at <luca.risolia@studio.unibo.it>. + +GPG/PGP encrypted e-mail's are accepted. The GPG key ID of the author is +'FCE635A4'; the public 1024-bit key should be available at any keyserver; +the fingerprint is: '88E8 F32F 7244 68BA 3958 5D40 99DA 5D2A FCE6 35A4'. + + +13. Credits +=========== +Many thanks to following persons for their contribute (listed in alphabetical +order): + +- David Anderson for the donation of a webcam; +- Luca Capello for the donation of a webcam; +- Philippe Coval for having helped testing the PAS202BCA image sensor; +- Joao Rodrigo Fuzaro, Joao Limirio, Claudio Filho and Caio Begotti for the + donation of a webcam; +- Dennis Heitmann for the donation of a webcam; +- Jon Hollstrom for the donation of a webcam; +- Nick McGill for the donation of a webcam; +- Carlos Eduardo Medaglia Dyonisio, who added the support for the PAS202BCB + image sensor; +- Stefano Mozzi, who donated 45 EU; +- Andrew Pearce for the donation of a webcam; +- John Pullan for the donation of a webcam; +- Bertrik Sikken, who reverse-engineered and documented the Huffman compression + algorithm used in the SN9C101, SN9C102 and SN9C103 controllers and + implemented the first decoder; +- Ronny Standke for the donation of a webcam; +- Mizuno Takafumi for the donation of a webcam; +- an "anonymous" donator (who didn't want his name to be revealed) for the + donation of a webcam. +- an anonymous donator for the donation of four webcams and two boards with ten + image sensors. diff --git a/drivers/media/usb/sn9c102/sn9c102_config.h b/drivers/staging/media/sn9c102/sn9c102_config.h index 0f4e0378b071..0f4e0378b071 100644 --- a/drivers/media/usb/sn9c102/sn9c102_config.h +++ b/drivers/staging/media/sn9c102/sn9c102_config.h diff --git a/drivers/media/usb/sn9c102/sn9c102_core.c b/drivers/staging/media/sn9c102/sn9c102_core.c index 2cb44de2b92c..2cb44de2b92c 100644 --- a/drivers/media/usb/sn9c102/sn9c102_core.c +++ b/drivers/staging/media/sn9c102/sn9c102_core.c diff --git a/drivers/media/usb/sn9c102/sn9c102_devtable.h b/drivers/staging/media/sn9c102/sn9c102_devtable.h index b3d2cc729657..b3d2cc729657 100644 --- a/drivers/media/usb/sn9c102/sn9c102_devtable.h +++ b/drivers/staging/media/sn9c102/sn9c102_devtable.h diff --git a/drivers/media/usb/sn9c102/sn9c102_hv7131d.c b/drivers/staging/media/sn9c102/sn9c102_hv7131d.c index 2dce5c908c8e..2dce5c908c8e 100644 --- a/drivers/media/usb/sn9c102/sn9c102_hv7131d.c +++ b/drivers/staging/media/sn9c102/sn9c102_hv7131d.c diff --git a/drivers/media/usb/sn9c102/sn9c102_hv7131r.c b/drivers/staging/media/sn9c102/sn9c102_hv7131r.c index 4295887ff609..4295887ff609 100644 --- a/drivers/media/usb/sn9c102/sn9c102_hv7131r.c +++ b/drivers/staging/media/sn9c102/sn9c102_hv7131r.c diff --git a/drivers/media/usb/sn9c102/sn9c102_mi0343.c b/drivers/staging/media/sn9c102/sn9c102_mi0343.c index 1f5b09bec89c..1f5b09bec89c 100644 --- a/drivers/media/usb/sn9c102/sn9c102_mi0343.c +++ b/drivers/staging/media/sn9c102/sn9c102_mi0343.c diff --git a/drivers/media/usb/sn9c102/sn9c102_mi0360.c b/drivers/staging/media/sn9c102/sn9c102_mi0360.c index d973fc1973d9..d973fc1973d9 100644 --- a/drivers/media/usb/sn9c102/sn9c102_mi0360.c +++ b/drivers/staging/media/sn9c102/sn9c102_mi0360.c diff --git a/drivers/media/usb/sn9c102/sn9c102_mt9v111.c b/drivers/staging/media/sn9c102/sn9c102_mt9v111.c index 95986eb492e4..95986eb492e4 100644 --- a/drivers/media/usb/sn9c102/sn9c102_mt9v111.c +++ b/drivers/staging/media/sn9c102/sn9c102_mt9v111.c diff --git a/drivers/media/usb/sn9c102/sn9c102_ov7630.c b/drivers/staging/media/sn9c102/sn9c102_ov7630.c index 803712c29f02..803712c29f02 100644 --- a/drivers/media/usb/sn9c102/sn9c102_ov7630.c +++ b/drivers/staging/media/sn9c102/sn9c102_ov7630.c diff --git a/drivers/media/usb/sn9c102/sn9c102_ov7660.c b/drivers/staging/media/sn9c102/sn9c102_ov7660.c index 7977795d342b..7977795d342b 100644 --- a/drivers/media/usb/sn9c102/sn9c102_ov7660.c +++ b/drivers/staging/media/sn9c102/sn9c102_ov7660.c diff --git a/drivers/media/usb/sn9c102/sn9c102_pas106b.c b/drivers/staging/media/sn9c102/sn9c102_pas106b.c index 81cd969c1b7b..81cd969c1b7b 100644 --- a/drivers/media/usb/sn9c102/sn9c102_pas106b.c +++ b/drivers/staging/media/sn9c102/sn9c102_pas106b.c diff --git a/drivers/media/usb/sn9c102/sn9c102_pas202bcb.c b/drivers/staging/media/sn9c102/sn9c102_pas202bcb.c index 2e86fdc86989..2e86fdc86989 100644 --- a/drivers/media/usb/sn9c102/sn9c102_pas202bcb.c +++ b/drivers/staging/media/sn9c102/sn9c102_pas202bcb.c diff --git a/drivers/media/usb/sn9c102/sn9c102_sensor.h b/drivers/staging/media/sn9c102/sn9c102_sensor.h index 3679970dba2c..3679970dba2c 100644 --- a/drivers/media/usb/sn9c102/sn9c102_sensor.h +++ b/drivers/staging/media/sn9c102/sn9c102_sensor.h diff --git a/drivers/media/usb/sn9c102/sn9c102_tas5110c1b.c b/drivers/staging/media/sn9c102/sn9c102_tas5110c1b.c index 04cdfdde8564..04cdfdde8564 100644 --- a/drivers/media/usb/sn9c102/sn9c102_tas5110c1b.c +++ b/drivers/staging/media/sn9c102/sn9c102_tas5110c1b.c diff --git a/drivers/media/usb/sn9c102/sn9c102_tas5110d.c b/drivers/staging/media/sn9c102/sn9c102_tas5110d.c index 9372e6f9fcff..9372e6f9fcff 100644 --- a/drivers/media/usb/sn9c102/sn9c102_tas5110d.c +++ b/drivers/staging/media/sn9c102/sn9c102_tas5110d.c diff --git a/drivers/media/usb/sn9c102/sn9c102_tas5130d1b.c b/drivers/staging/media/sn9c102/sn9c102_tas5130d1b.c index a30bbc4389f5..a30bbc4389f5 100644 --- a/drivers/media/usb/sn9c102/sn9c102_tas5130d1b.c +++ b/drivers/staging/media/sn9c102/sn9c102_tas5130d1b.c diff --git a/drivers/staging/media/solo6x10/solo6x10-v4l2-enc.c b/drivers/staging/media/solo6x10/solo6x10-v4l2-enc.c index d582c5b84c14..ce9e5aaf7fd4 100644 --- a/drivers/staging/media/solo6x10/solo6x10-v4l2-enc.c +++ b/drivers/staging/media/solo6x10/solo6x10-v4l2-enc.c @@ -964,7 +964,7 @@ static int solo_enc_s_std(struct file *file, void *priv, v4l2_std_id std) { struct solo_enc_dev *solo_enc = video_drvdata(file); - return solo_set_video_type(solo_enc->solo_dev, std & V4L2_STD_PAL); + return solo_set_video_type(solo_enc->solo_dev, std & V4L2_STD_625_50); } static int solo_enum_framesizes(struct file *file, void *priv, diff --git a/drivers/staging/media/solo6x10/solo6x10-v4l2.c b/drivers/staging/media/solo6x10/solo6x10-v4l2.c index 7b26de3488da..47e72dac9b13 100644 --- a/drivers/staging/media/solo6x10/solo6x10-v4l2.c +++ b/drivers/staging/media/solo6x10/solo6x10-v4l2.c @@ -527,7 +527,7 @@ static int solo_g_std(struct file *file, void *priv, v4l2_std_id *i) return 0; } -int solo_set_video_type(struct solo_dev *solo_dev, bool type) +int solo_set_video_type(struct solo_dev *solo_dev, bool is_50hz) { int i; @@ -537,7 +537,8 @@ int solo_set_video_type(struct solo_dev *solo_dev, bool type) for (i = 0; i < solo_dev->nr_chans; i++) if (vb2_is_busy(&solo_dev->v4l2_enc[i]->vidq)) return -EBUSY; - solo_dev->video_type = type; + solo_dev->video_type = is_50hz ? SOLO_VO_FMT_TYPE_PAL : + SOLO_VO_FMT_TYPE_NTSC; /* Reconfigure for the new standard */ solo_disp_init(solo_dev); solo_enc_init(solo_dev); @@ -551,7 +552,7 @@ static int solo_s_std(struct file *file, void *priv, v4l2_std_id std) { struct solo_dev *solo_dev = video_drvdata(file); - return solo_set_video_type(solo_dev, std & V4L2_STD_PAL); + return solo_set_video_type(solo_dev, std & V4L2_STD_625_50); } static int solo_s_ctrl(struct v4l2_ctrl *ctrl) diff --git a/drivers/staging/media/solo6x10/solo6x10.h b/drivers/staging/media/solo6x10/solo6x10.h index f1bbb8cb74e6..8964f8be158e 100644 --- a/drivers/staging/media/solo6x10/solo6x10.h +++ b/drivers/staging/media/solo6x10/solo6x10.h @@ -398,7 +398,7 @@ int solo_p2m_dma_desc(struct solo_dev *solo_dev, int desc_cnt); /* Global s_std ioctl */ -int solo_set_video_type(struct solo_dev *solo_dev, bool type); +int solo_set_video_type(struct solo_dev *solo_dev, bool is_50hz); void solo_update_mode(struct solo_enc_dev *solo_enc); /* Set the threshold for motion detection */ |