From 66752f8396f97360cafb226aa1fe230f66785c5a Mon Sep 17 00:00:00 2001 From: Andy Walls Date: Sun, 18 Jul 2010 18:51:10 -0300 Subject: V4L/DVB: v4l2_subdev: Add s_io_pin_config to v4l2_subdev_core_ops Add a method to v4l2_sudev_core_ops to allow bridge drivers to manage what signal pads/functions are routed out to multiplexed IO pins on a pin by pin basis. The interface also allows specifying initial output settings for pins and disabling an IO pin altogether. Signed-off-by: Andy Walls Signed-off-by: Mauro Carvalho Chehab --- include/media/v4l2-subdev.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'include') diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h index 02c6f4d11ed..9195ad490ee 100644 --- a/include/media/v4l2-subdev.h +++ b/include/media/v4l2-subdev.h @@ -90,10 +90,31 @@ struct v4l2_decode_vbi_line { not yet implemented) since ops provide proper type-checking. */ +/* Subdevice external IO pin configuration */ +#define V4L2_SUBDEV_IO_PIN_DISABLE (1 << 0) /* ENABLE assumed */ +#define V4L2_SUBDEV_IO_PIN_OUTPUT (1 << 1) +#define V4L2_SUBDEV_IO_PIN_INPUT (1 << 2) +#define V4L2_SUBDEV_IO_PIN_SET_VALUE (1 << 3) /* Set output value */ +#define V4L2_SUBDEV_IO_PIN_ACTIVE_LOW (1 << 4) /* ACTIVE HIGH assumed */ + +struct v4l2_subdev_io_pin_config { + u32 flags; /* V4L2_SUBDEV_IO_PIN_* flags for this pin's config */ + u8 pin; /* Chip external IO pin to configure */ + u8 function; /* Internal signal pad/function to route to IO pin */ + u8 value; /* Initial value for pin - e.g. GPIO output value */ + u8 strength; /* Pin drive strength */ +}; + /* s_config: if set, then it is always called by the v4l2_i2c_new_subdev* functions after the v4l2_subdev was registered. It is used to pass platform data to the subdev which can be used during initialization. + s_io_pin_config: configure one or more chip I/O pins for chips that + multiplex different internal signal pads out to IO pins. This function + takes a pointer to an array of 'n' pin configuration entries, one for + each pin being configured. This function could be called at times + other than just subdevice initialization. + init: initialize the sensor registors to some sort of reasonable default values. Do not use for new drivers and should be removed in existing drivers. @@ -115,6 +136,8 @@ struct v4l2_subdev_core_ops { int (*g_chip_ident)(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip); int (*log_status)(struct v4l2_subdev *sd); int (*s_config)(struct v4l2_subdev *sd, int irq, void *platform_data); + int (*s_io_pin_config)(struct v4l2_subdev *sd, size_t n, + struct v4l2_subdev_io_pin_config *pincfg); int (*init)(struct v4l2_subdev *sd, u32 val); int (*load_fw)(struct v4l2_subdev *sd); int (*reset)(struct v4l2_subdev *sd, u32 val); -- cgit v1.2.3-70-g09d2 From d06d5777b211112e8355e2f5a700c6a9babfdd6f Mon Sep 17 00:00:00 2001 From: Andy Walls Date: Sun, 18 Jul 2010 19:39:54 -0300 Subject: V4L/DVB: cx25840: Add s_io_pin_config core subdev ops for the CX2388[578] Add s_io_pin_config core subdev op for the CX2388[578] AV cores. This is complete for IR_RX, IR_TX, GPIOs 16,19-23, and IRQ_N. It likely needs work for the I2S signal direction. Signed-off-by: Andy Walls Signed-off-by: Mauro Carvalho Chehab --- drivers/media/video/cx25840/cx25840-core.c | 153 +++++++++++++++++++++++++++++ include/media/cx25840.h | 75 ++++++++++++++ 2 files changed, 228 insertions(+) (limited to 'include') diff --git a/drivers/media/video/cx25840/cx25840-core.c b/drivers/media/video/cx25840/cx25840-core.c index 4f908fa4da0..46a046d6405 100644 --- a/drivers/media/video/cx25840/cx25840-core.c +++ b/drivers/media/video/cx25840/cx25840-core.c @@ -144,6 +144,158 @@ static int set_input(struct i2c_client *client, enum cx25840_video_input vid_inp /* ----------------------------------------------------------------------- */ +static int cx23885_s_io_pin_config(struct v4l2_subdev *sd, size_t n, + struct v4l2_subdev_io_pin_config *p) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int i; + u32 pin_ctrl; + u8 gpio_oe, gpio_data, strength; + + pin_ctrl = cx25840_read4(client, 0x120); + gpio_oe = cx25840_read(client, 0x160); + gpio_data = cx25840_read(client, 0x164); + + for (i = 0; i < n; i++) { + strength = p[i].strength; + if (strength > CX25840_PIN_DRIVE_FAST) + strength = CX25840_PIN_DRIVE_FAST; + + switch (p[i].pin) { + case CX23885_PIN_IRQ_N_GPIO16: + if (p[i].function != CX23885_PAD_IRQ_N) { + /* GPIO16 */ + pin_ctrl &= ~(0x1 << 25); + } else { + /* IRQ_N */ + if (p[i].flags & + (V4L2_SUBDEV_IO_PIN_DISABLE | + V4L2_SUBDEV_IO_PIN_INPUT)) { + pin_ctrl &= ~(0x1 << 25); + } else { + pin_ctrl |= (0x1 << 25); + } + if (p[i].flags & + V4L2_SUBDEV_IO_PIN_ACTIVE_LOW) { + pin_ctrl &= ~(0x1 << 24); + } else { + pin_ctrl |= (0x1 << 24); + } + } + break; + case CX23885_PIN_IR_RX_GPIO19: + if (p[i].function != CX23885_PAD_GPIO19) { + /* IR_RX */ + gpio_oe |= (0x1 << 0); + pin_ctrl &= ~(0x3 << 18); + pin_ctrl |= (strength << 18); + } else { + /* GPIO19 */ + gpio_oe &= ~(0x1 << 0); + if (p[i].flags & V4L2_SUBDEV_IO_PIN_SET_VALUE) { + gpio_data &= ~(0x1 << 0); + gpio_data |= ((p[i].value & 0x1) << 0); + } + pin_ctrl &= ~(0x3 << 12); + pin_ctrl |= (strength << 12); + } + break; + case CX23885_PIN_IR_TX_GPIO20: + if (p[i].function != CX23885_PAD_GPIO20) { + /* IR_TX */ + gpio_oe |= (0x1 << 1); + if (p[i].flags & V4L2_SUBDEV_IO_PIN_DISABLE) + pin_ctrl &= ~(0x1 << 10); + else + pin_ctrl |= (0x1 << 10); + pin_ctrl &= ~(0x3 << 18); + pin_ctrl |= (strength << 18); + } else { + /* GPIO20 */ + gpio_oe &= ~(0x1 << 1); + if (p[i].flags & V4L2_SUBDEV_IO_PIN_SET_VALUE) { + gpio_data &= ~(0x1 << 1); + gpio_data |= ((p[i].value & 0x1) << 1); + } + pin_ctrl &= ~(0x3 << 12); + pin_ctrl |= (strength << 12); + } + break; + case CX23885_PIN_I2S_SDAT_GPIO21: + if (p[i].function != CX23885_PAD_GPIO21) { + /* I2S_SDAT */ + /* TODO: Input or Output config */ + gpio_oe |= (0x1 << 2); + pin_ctrl &= ~(0x3 << 22); + pin_ctrl |= (strength << 22); + } else { + /* GPIO21 */ + gpio_oe &= ~(0x1 << 2); + if (p[i].flags & V4L2_SUBDEV_IO_PIN_SET_VALUE) { + gpio_data &= ~(0x1 << 2); + gpio_data |= ((p[i].value & 0x1) << 2); + } + pin_ctrl &= ~(0x3 << 12); + pin_ctrl |= (strength << 12); + } + break; + case CX23885_PIN_I2S_WCLK_GPIO22: + if (p[i].function != CX23885_PAD_GPIO22) { + /* I2S_WCLK */ + /* TODO: Input or Output config */ + gpio_oe |= (0x1 << 3); + pin_ctrl &= ~(0x3 << 22); + pin_ctrl |= (strength << 22); + } else { + /* GPIO22 */ + gpio_oe &= ~(0x1 << 3); + if (p[i].flags & V4L2_SUBDEV_IO_PIN_SET_VALUE) { + gpio_data &= ~(0x1 << 3); + gpio_data |= ((p[i].value & 0x1) << 3); + } + pin_ctrl &= ~(0x3 << 12); + pin_ctrl |= (strength << 12); + } + break; + case CX23885_PIN_I2S_BCLK_GPIO23: + if (p[i].function != CX23885_PAD_GPIO23) { + /* I2S_BCLK */ + /* TODO: Input or Output config */ + gpio_oe |= (0x1 << 4); + pin_ctrl &= ~(0x3 << 22); + pin_ctrl |= (strength << 22); + } else { + /* GPIO23 */ + gpio_oe &= ~(0x1 << 4); + if (p[i].flags & V4L2_SUBDEV_IO_PIN_SET_VALUE) { + gpio_data &= ~(0x1 << 4); + gpio_data |= ((p[i].value & 0x1) << 4); + } + pin_ctrl &= ~(0x3 << 12); + pin_ctrl |= (strength << 12); + } + break; + } + } + + cx25840_write(client, 0x164, gpio_data); + cx25840_write(client, 0x160, gpio_oe); + cx25840_write4(client, 0x120, pin_ctrl); + return 0; +} + +static int common_s_io_pin_config(struct v4l2_subdev *sd, size_t n, + struct v4l2_subdev_io_pin_config *pincfg) +{ + struct cx25840_state *state = to_state(sd); + + if (is_cx2388x(state)) + return cx23885_s_io_pin_config(sd, n, pincfg); + return 0; +} + +/* ----------------------------------------------------------------------- */ + static void init_dll1(struct i2c_client *client) { /* This is the Hauppauge sequence used to @@ -1610,6 +1762,7 @@ static const struct v4l2_subdev_core_ops cx25840_core_ops = { .s_std = cx25840_s_std, .reset = cx25840_reset, .load_fw = cx25840_load_fw, + .s_io_pin_config = common_s_io_pin_config, #ifdef CONFIG_VIDEO_ADV_DEBUG .g_register = cx25840_g_register, .s_register = cx25840_s_register, diff --git a/include/media/cx25840.h b/include/media/cx25840.h index 0b0cb177679..1bba39e3d40 100644 --- a/include/media/cx25840.h +++ b/include/media/cx25840.h @@ -97,4 +97,79 @@ enum cx25840_audio_input { CX25840_AUDIO8, }; +enum cx25840_io_pin { + CX25840_PIN_DVALID_PRGM0 = 0, + CX25840_PIN_FIELD_PRGM1, + CX25840_PIN_HRESET_PRGM2, + CX25840_PIN_VRESET_HCTL_PRGM3, + CX25840_PIN_IRQ_N_PRGM4, + CX25840_PIN_IR_TX_PRGM6, + CX25840_PIN_IR_RX_PRGM5, + CX25840_PIN_GPIO0_PRGM8, + CX25840_PIN_GPIO1_PRGM9, + CX25840_PIN_SA_SDIN, /* Alternate GP Input only */ + CX25840_PIN_SA_SDOUT, /* Alternate GP Input only */ + CX25840_PIN_PLL_CLK_PRGM7, + CX25840_PIN_CHIP_SEL_VIPCLK, /* Output only */ +}; + +enum cx25840_io_pad { + /* Output pads */ + CX25840_PAD_DEFAULT = 0, + CX25840_PAD_ACTIVE, + CX25840_PAD_VACTIVE, + CX25840_PAD_CBFLAG, + CX25840_PAD_VID_DATA_EXT0, + CX25840_PAD_VID_DATA_EXT1, + CX25840_PAD_GPO0, + CX25840_PAD_GPO1, + CX25840_PAD_GPO2, + CX25840_PAD_GPO3, + CX25840_PAD_IRQ_N, + CX25840_PAD_AC_SYNC, + CX25840_PAD_AC_SDOUT, + CX25840_PAD_PLL_CLK, + CX25840_PAD_VRESET, + CX25840_PAD_RESERVED, + /* Pads for PLL_CLK output only */ + CX25840_PAD_XTI_X5_DLL, + CX25840_PAD_AUX_PLL, + CX25840_PAD_VID_PLL, + CX25840_PAD_XTI, + /* Input Pads */ + CX25840_PAD_GPI0, + CX25840_PAD_GPI1, + CX25840_PAD_GPI2, + CX25840_PAD_GPI3, +}; + +enum cx25840_io_pin_strength { + CX25840_PIN_DRIVE_MEDIUM = 0, + CX25840_PIN_DRIVE_SLOW, + CX25840_PIN_DRIVE_FAST, +}; + +enum cx23885_io_pin { + CX23885_PIN_IR_RX_GPIO19, + CX23885_PIN_IR_TX_GPIO20, + CX23885_PIN_I2S_SDAT_GPIO21, + CX23885_PIN_I2S_WCLK_GPIO22, + CX23885_PIN_I2S_BCLK_GPIO23, + CX23885_PIN_IRQ_N_GPIO16, +}; + +enum cx23885_io_pad { + CX23885_PAD_IR_RX, + CX23885_PAD_GPIO19, + CX23885_PAD_IR_TX, + CX23885_PAD_GPIO20, + CX23885_PAD_I2S_SDAT, + CX23885_PAD_GPIO21, + CX23885_PAD_I2S_WCLK, + CX23885_PAD_GPIO22, + CX23885_PAD_I2S_BCLK, + CX23885_PAD_GPIO23, + CX23885_PAD_IRQ_N, + CX23885_PAD_GPIO16, +}; #endif -- cgit v1.2.3-70-g09d2 From 5a28d9a320c865007da830c065bcaecfe91b3932 Mon Sep 17 00:00:00 2001 From: Andy Walls Date: Sun, 18 Jul 2010 19:57:25 -0300 Subject: V4L/DVB: v4l2_subdev, cx23885: Differentiate IR carrier sense and I/O pin inversion There is a distinction on IR Tx for the CX2388[578] chips of carrier sense inversion (space is a carrier burst and mark is no burst) and I/O pin level inversion (0 is high output level, 1 is low output level). Allow the caller to set these parameters distinctly as v4l2_subdevice IR parameters. This permits the IR device to be configured and enabled without the IR Tx LED being on during idle/space time due to an external hardware level inversion Signed-off-by: Andy Walls Signed-off-by: Mauro Carvalho Chehab --- drivers/media/video/cx23885/cx23885-input.c | 2 +- drivers/media/video/cx23885/cx23888-ir.c | 32 +++++++++++++++++++++-------- include/media/v4l2-subdev.h | 5 ++++- 3 files changed, 29 insertions(+), 10 deletions(-) (limited to 'include') diff --git a/drivers/media/video/cx23885/cx23885-input.c b/drivers/media/video/cx23885/cx23885-input.c index d0b1613ede2..496d751ffdc 100644 --- a/drivers/media/video/cx23885/cx23885-input.c +++ b/drivers/media/video/cx23885/cx23885-input.c @@ -170,7 +170,7 @@ static int cx23885_input_ir_start(struct cx23885_dev *dev) * mark is received as low logic level; * falling edges are detected as rising edges; etc. */ - params.invert = true; + params.invert_level = true; break; } v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, ¶ms); diff --git a/drivers/media/video/cx23885/cx23888-ir.c b/drivers/media/video/cx23885/cx23888-ir.c index f63d378257a..28ca90ff223 100644 --- a/drivers/media/video/cx23885/cx23888-ir.c +++ b/drivers/media/video/cx23885/cx23888-ir.c @@ -60,6 +60,8 @@ MODULE_PARM_DESC(ir_888_debug, "enable debug messages [CX23888 IR controller]"); #define CNTRL_CPL 0x00001000 #define CNTRL_LBM 0x00002000 #define CNTRL_R 0x00004000 +/* CX23888 specific control flag */ +#define CNTRL_IVO 0x00008000 #define CX23888_IR_TXCLK_REG 0x170004 #define TXCLK_TCD 0x0000FFFF @@ -423,6 +425,13 @@ static inline void control_tx_polarity_invert(struct cx23885_dev *dev, invert ? CNTRL_CPL : 0); } +static inline void control_tx_level_invert(struct cx23885_dev *dev, + bool invert) +{ + cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~CNTRL_IVO, + invert ? CNTRL_IVO : 0); +} + /* * IR Rx & Tx Clock Register helpers */ @@ -782,8 +791,8 @@ static int cx23888_ir_rx_s_parameters(struct v4l2_subdev *sd, control_rx_s_edge_detection(dev, CNTRL_EDG_BOTH); - o->invert = p->invert; - atomic_set(&state->rx_invert, p->invert); + o->invert_level = p->invert_level; + atomic_set(&state->rx_invert, p->invert_level); o->interrupt_enable = p->interrupt_enable; o->enable = p->enable; @@ -894,8 +903,11 @@ static int cx23888_ir_tx_s_parameters(struct v4l2_subdev *sd, /* FIXME - make this dependent on resolution for better performance */ control_tx_irq_watermark(dev, TX_FIFO_HALF_EMPTY); - control_tx_polarity_invert(dev, p->invert); - o->invert = p->invert; + control_tx_polarity_invert(dev, p->invert_carrier_sense); + o->invert_carrier_sense = p->invert_carrier_sense; + + control_tx_level_invert(dev, p->invert_level); + o->invert_level = p->invert_level; o->interrupt_enable = p->interrupt_enable; o->enable = p->enable; @@ -1025,8 +1037,11 @@ static int cx23888_ir_log_status(struct v4l2_subdev *sd) cntrl & CNTRL_TFE ? "enabled" : "disabled"); v4l2_info(sd, "\tFIFO interrupt watermark: %s\n", cntrl & CNTRL_TIC ? "not empty" : "half full or less"); - v4l2_info(sd, "\tSignal polarity: %s\n", - cntrl & CNTRL_CPL ? "0:mark 1:space" : "0:space 1:mark"); + v4l2_info(sd, "\tOutput pin level inversion %s\n", + cntrl & CNTRL_IVO ? "yes" : "no"); + v4l2_info(sd, "\tCarrier polarity: %s\n", + cntrl & CNTRL_CPL ? "space:burst mark:noburst" + : "space:noburst mark:burst"); if (cntrl & CNTRL_MOD) { v4l2_info(sd, "\tCarrier (16 clocks): %u Hz\n", clock_divider_to_carrier_freq(txclk)); @@ -1146,7 +1161,7 @@ static const struct v4l2_subdev_ir_parameters default_rx_params = { .noise_filter_min_width = 333333, /* ns */ .carrier_range_lower = 35000, .carrier_range_upper = 37000, - .invert = false, + .invert_level = false, }; static const struct v4l2_subdev_ir_parameters default_tx_params = { @@ -1160,7 +1175,8 @@ static const struct v4l2_subdev_ir_parameters default_tx_params = { .modulation = true, .carrier_freq = 36000, /* 36 kHz - RC-5 carrier */ .duty_cycle = 25, /* 25 % - RC-5 carrier */ - .invert = false, + .invert_level = false, + .invert_carrier_sense = false, }; int cx23888_ir_probe(struct cx23885_dev *dev) diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h index 9195ad490ee..a780cca185f 100644 --- a/include/media/v4l2-subdev.h +++ b/include/media/v4l2-subdev.h @@ -379,7 +379,10 @@ struct v4l2_subdev_ir_parameters { u32 max_pulse_width; /* ns, valid only for baseband signal */ unsigned int carrier_freq; /* Hz, valid only for modulated signal*/ unsigned int duty_cycle; /* percent, valid only for modulated signal*/ - bool invert; /* logically invert sense of mark/space */ + bool invert_level; /* invert signal level */ + + /* Tx only */ + bool invert_carrier_sense; /* Send 0/space as a carrier burst */ /* Rx only */ u32 noise_filter_min_width; /* ns, min time of a valid pulse */ -- cgit v1.2.3-70-g09d2 From 260e689ba237fb513b49e98841d98e93ea639c75 Mon Sep 17 00:00:00 2001 From: Andy Walls Date: Sun, 18 Jul 2010 20:54:52 -0300 Subject: V4L/DVB: v4l2_subdev: Move interrupt_service_routine ptr to v4l2_subdev_core_ops The CX2584x and related cores are multifunction subdevices with a number of internal blocks that act as interrupt sources. Move the v4L2_subdev interrupt_service_routine callback from v4l_subdev_ir_ops to v4l2_subdev_core_ops, as the video and audio blocks of a CX2584x and related cores can generate interrupts along with the IR block. This change also makes sense for other subdev's that generate interrupts and do not have an IR block. Signed-off-by: Andy Walls Signed-off-by: Mauro Carvalho Chehab --- drivers/media/video/cx23885/cx23885-core.c | 2 +- drivers/media/video/cx23885/cx23888-ir.c | 3 +-- include/media/v4l2-subdev.h | 16 +++++++--------- 3 files changed, 9 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/drivers/media/video/cx23885/cx23885-core.c b/drivers/media/video/cx23885/cx23885-core.c index ff76f64edac..a74c0ab859e 100644 --- a/drivers/media/video/cx23885/cx23885-core.c +++ b/drivers/media/video/cx23885/cx23885-core.c @@ -1765,7 +1765,7 @@ static irqreturn_t cx23885_irq(int irq, void *dev_id) handled += cx23885_video_irq(dev, vida_status); if (pci_status & PCI_MSK_IR) { - v4l2_subdev_call(dev->sd_ir, ir, interrupt_service_routine, + v4l2_subdev_call(dev->sd_ir, core, interrupt_service_routine, pci_status, &ir_handled); if (ir_handled) handled++; diff --git a/drivers/media/video/cx23885/cx23888-ir.c b/drivers/media/video/cx23885/cx23888-ir.c index 28ca90ff223..51f21636e63 100644 --- a/drivers/media/video/cx23885/cx23888-ir.c +++ b/drivers/media/video/cx23885/cx23888-ir.c @@ -1126,11 +1126,10 @@ static const struct v4l2_subdev_core_ops cx23888_ir_core_ops = { .g_register = cx23888_ir_g_register, .s_register = cx23888_ir_s_register, #endif + .interrupt_service_routine = cx23888_ir_irq_handler, }; static const struct v4l2_subdev_ir_ops cx23888_ir_ir_ops = { - .interrupt_service_routine = cx23888_ir_irq_handler, - .rx_read = cx23888_ir_rx_read, .rx_g_parameters = cx23888_ir_rx_g_parameters, .rx_s_parameters = cx23888_ir_rx_s_parameters, diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h index a780cca185f..bacd52568ef 100644 --- a/include/media/v4l2-subdev.h +++ b/include/media/v4l2-subdev.h @@ -131,6 +131,11 @@ struct v4l2_subdev_io_pin_config { s_power: puts subdevice in power saving mode (on == 0) or normal operation mode (on == 1). + + interrupt_service_routine: Called by the bridge chip's interrupt service + handler, when an interrupt status has be raised due to this subdev, + so that this subdev can handle the details. It may schedule work to be + performed later. It must not sleep. *Called from an IRQ context*. */ struct v4l2_subdev_core_ops { int (*g_chip_ident)(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip); @@ -156,6 +161,8 @@ struct v4l2_subdev_core_ops { int (*s_register)(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg); #endif int (*s_power)(struct v4l2_subdev *sd, int on); + int (*interrupt_service_routine)(struct v4l2_subdev *sd, + u32 status, bool *handled); }; /* s_mode: switch the tuner to a specific tuner mode. Replacement of s_radio. @@ -330,11 +337,6 @@ struct v4l2_subdev_sensor_ops { }; /* - interrupt_service_routine: Called by the bridge chip's interrupt service - handler, when an IR interrupt status has be raised due to this subdev, - so that this subdev can handle the details. It may schedule work to be - performed later. It must not sleep. *Called from an IRQ context*. - [rt]x_g_parameters: Get the current operating parameters and state of the the IR receiver or transmitter. @@ -392,10 +394,6 @@ struct v4l2_subdev_ir_parameters { }; struct v4l2_subdev_ir_ops { - /* Common to receiver and transmitter */ - int (*interrupt_service_routine)(struct v4l2_subdev *sd, - u32 status, bool *handled); - /* Receiver */ int (*rx_read)(struct v4l2_subdev *sd, u8 *buf, size_t count, ssize_t *num); -- cgit v1.2.3-70-g09d2 From d69e85b644dbc8c68dec3b4149941989d4bcc66f Mon Sep 17 00:00:00 2001 From: Andy Walls Date: Sun, 1 Aug 2010 02:36:20 -0300 Subject: V4L/DVB: v4l2_subdev: Get rid of now unused IR pulse width defines Signed-off-by: Andy Walls Signed-off-by: Mauro Carvalho Chehab --- drivers/media/video/cx25840/cx25840-ir.c | 4 ++-- include/media/v4l2-subdev.h | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/drivers/media/video/cx25840/cx25840-ir.c b/drivers/media/video/cx25840/cx25840-ir.c index 34e284b06df..c2b4c14dc9a 100644 --- a/drivers/media/video/cx25840/cx25840-ir.c +++ b/drivers/media/video/cx25840/cx25840-ir.c @@ -886,10 +886,10 @@ static int cx25840_ir_tx_write(struct v4l2_subdev *sd, u8 *buf, size_t count, */ for (i = 0; i < n; ) { for (j = 0; j < FIFO_TX_DEPTH / 2 && i < n; j++) { - mark = ns_pulse[i] & V4L2_SUBDEV_IR_PULSE_LEVEL_MASK; + mark = ns_pulse[i] & LEVEL_MASK; fifo_pulse[j] = ns_to_pulse_width_count( ns_pulse[i] & - ~V4L2_SUBDEV_IR_PULSE_LEVEL_MASK, + ~LEVEL_MASK, ir_state->txclk_divider); if (mark) fifo_pulse[j] &= FIFO_RXTX_LVL; diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h index bacd52568ef..08880dd15d2 100644 --- a/include/media/v4l2-subdev.h +++ b/include/media/v4l2-subdev.h @@ -360,14 +360,9 @@ struct v4l2_subdev_sensor_ops { */ enum v4l2_subdev_ir_mode { - V4L2_SUBDEV_IR_MODE_PULSE_WIDTH, /* space & mark widths in nanosecs */ + V4L2_SUBDEV_IR_MODE_PULSE_WIDTH, /* uses struct ir_raw_event records */ }; -/* Data format of data read or written for V4L2_SUBDEV_IR_MODE_PULSE_WIDTH */ -#define V4L2_SUBDEV_IR_PULSE_MAX_WIDTH_NS 0x7fffffff -#define V4L2_SUBDEV_IR_PULSE_LEVEL_MASK 0x80000000 -#define V4L2_SUBDEV_IR_PULSE_RX_SEQ_END 0xffffffff - struct v4l2_subdev_ir_parameters { /* Either Rx or Tx */ unsigned int bytes_per_data_element; /* of data in read or write call */ -- cgit v1.2.3-70-g09d2 From 8e9e60640067858e8036d4d43bbf725c60613359 Mon Sep 17 00:00:00 2001 From: Jarod Wilson Date: Tue, 3 Aug 2010 01:07:04 -0300 Subject: V4L/DVB: staging/lirc: port lirc_streamzap to ir-core This ports lirc_streamzap.c over to ir-core in-place, to be followed by a patch moving the driver over to drivers/media/IR/streamzap.c and enabling the proper Kconfig bits. Presently, the in-kernel keymap doesn't work, as the stock Streamzap remote uses an RC-5-like, but not-quite-RC-5 protocol, which the in-kernel RC-5 decoder doesn't cope with. The remote can be used right now with the lirc bridge driver though, and other remotes (at least an RC-6(A) MCE remote) work perfectly with the driver. I'll take a look at making the existing RC-5 decoder cope with this odd duck, possibly implement another standalone decoder engine, or just throw up my hands and say "meh, use lirc"... But the driver itself should be perfectly sound. Remaining items on the streamzap TODO list: - add LIRC_SET_REC_TIMEOUT-alike support - add LIRC_GET_M{AX,IN}_TIMEOUT-alike support - add LIRC_GET_REC_RESOLUTION-alike support All of the above should be trivial to add. There are patches pending to add this support to ir-core from Maxim Levitsky, and I'll take care of these once his patches get integrated. None of them are currently essential though. Signed-off-by: Jarod Wilson Signed-off-by: Mauro Carvalho Chehab --- drivers/media/IR/keymaps/Makefile | 1 + drivers/media/IR/keymaps/rc-rc5-streamzap.c | 81 +++ drivers/staging/lirc/lirc_streamzap.c | 812 +++++++++++++--------------- include/media/rc-map.h | 1 + 4 files changed, 449 insertions(+), 446 deletions(-) create mode 100644 drivers/media/IR/keymaps/rc-rc5-streamzap.c (limited to 'include') diff --git a/drivers/media/IR/keymaps/Makefile b/drivers/media/IR/keymaps/Makefile index cbee06243b5..c9fcc41944e 100644 --- a/drivers/media/IR/keymaps/Makefile +++ b/drivers/media/IR/keymaps/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_RC_MAP) += rc-adstech-dvb-t-pci.o \ rc-purpletv.o \ rc-pv951.o \ rc-rc5-hauppauge-new.o \ + rc-rc5-streamzap.o \ rc-rc5-tv.o \ rc-rc6-mce.o \ rc-real-audio-220-32-keys.o \ diff --git a/drivers/media/IR/keymaps/rc-rc5-streamzap.c b/drivers/media/IR/keymaps/rc-rc5-streamzap.c new file mode 100644 index 00000000000..4c19c58b46d --- /dev/null +++ b/drivers/media/IR/keymaps/rc-rc5-streamzap.c @@ -0,0 +1,81 @@ +/* rc-rc5-streamzap.c - Keytable for Streamzap PC Remote, for use + * with the Streamzap PC Remote IR Receiver. + * + * Copyright (c) 2010 by Jarod Wilson + * + * 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 + +static struct ir_scancode rc5_streamzap[] = { +/* + * FIXME: The Streamzap remote isn't actually true RC-5, it has an extra + * bit in it, which presently throws the in-kernel RC-5 decoder for a loop. + * We either have to enhance the decoder to support it, add a new decoder, + * or just rely on lirc userspace decoding. + */ + { 0x00, KEY_NUMERIC_0 }, + { 0x01, KEY_NUMERIC_1 }, + { 0x02, KEY_NUMERIC_2 }, + { 0x03, KEY_NUMERIC_3 }, + { 0x04, KEY_NUMERIC_4 }, + { 0x05, KEY_NUMERIC_5 }, + { 0x06, KEY_NUMERIC_6 }, + { 0x07, KEY_NUMERIC_7 }, + { 0x08, KEY_NUMERIC_8 }, + { 0x0a, KEY_POWER }, + { 0x0b, KEY_MUTE }, + { 0x0c, KEY_CHANNELUP }, + { 0x0d, KEY_VOLUMEUP }, + { 0x0e, KEY_CHANNELDOWN }, + { 0x0f, KEY_VOLUMEDOWN }, + { 0x10, KEY_UP }, + { 0x11, KEY_LEFT }, + { 0x12, KEY_OK }, + { 0x13, KEY_RIGHT }, + { 0x14, KEY_DOWN }, + { 0x15, KEY_MENU }, + { 0x16, KEY_EXIT }, + { 0x17, KEY_PLAY }, + { 0x18, KEY_PAUSE }, + { 0x19, KEY_STOP }, + { 0x1a, KEY_BACK }, + { 0x1b, KEY_FORWARD }, + { 0x1c, KEY_RECORD }, + { 0x1d, KEY_REWIND }, + { 0x1e, KEY_FASTFORWARD }, + { 0x20, KEY_RED }, + { 0x21, KEY_GREEN }, + { 0x22, KEY_YELLOW }, + { 0x23, KEY_BLUE }, + +}; + +static struct rc_keymap rc5_streamzap_map = { + .map = { + .scan = rc5_streamzap, + .size = ARRAY_SIZE(rc5_streamzap), + .ir_type = IR_TYPE_RC5, + .name = RC_MAP_RC5_STREAMZAP, + } +}; + +static int __init init_rc_map_rc5_streamzap(void) +{ + return ir_register_map(&rc5_streamzap_map); +} + +static void __exit exit_rc_map_rc5_streamzap(void) +{ + ir_unregister_map(&rc5_streamzap_map); +} + +module_init(init_rc_map_rc5_streamzap) +module_exit(exit_rc_map_rc5_streamzap) + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jarod Wilson "); diff --git a/drivers/staging/lirc/lirc_streamzap.c b/drivers/staging/lirc/lirc_streamzap.c index be09c103f0c..058e29fd478 100644 --- a/drivers/staging/lirc/lirc_streamzap.c +++ b/drivers/staging/lirc/lirc_streamzap.c @@ -2,6 +2,7 @@ * Streamzap Remote Control driver * * Copyright (c) 2005 Christoph Bartelmus + * Copyright (c) 2010 Jarod Wilson * * This driver was based on the work of Greg Wickham and Adrian * Dewhurst. It was substantially rewritten to support correct signal @@ -10,6 +11,8 @@ * delay buffer an ugly hack would be required in lircd, which can * cause sluggish signal decoding in certain situations. * + * Ported to in-kernel ir-core interface by Jarod Wilson + * * This driver is based on the USB skeleton driver packaged with the * kernel; copyright (C) 2001-2003 Greg Kroah-Hartman (greg@kroah.com) * @@ -28,36 +31,26 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include -#include -#include -#include +#include #include -#include -#include -#include +#include #include +#include +#include -#include -#include - -#define DRIVER_VERSION "1.28" -#define DRIVER_NAME "lirc_streamzap" +#define DRIVER_VERSION "1.60" +#define DRIVER_NAME "streamzap" #define DRIVER_DESC "Streamzap Remote Control driver" +#ifdef CONFIG_USB_DEBUG +static int debug = 1; +#else static int debug; +#endif #define USB_STREAMZAP_VENDOR_ID 0x0e9c #define USB_STREAMZAP_PRODUCT_ID 0x0000 -/* Use our own dbg macro */ -#define dprintk(fmt, args...) \ - do { \ - if (debug) \ - printk(KERN_DEBUG DRIVER_NAME "[%d]: " \ - fmt "\n", ## args); \ - } while (0) - /* table of devices that work with this driver */ static struct usb_device_id streamzap_table[] = { /* Streamzap Remote Control */ @@ -74,7 +67,7 @@ MODULE_DEVICE_TABLE(usb, streamzap_table); #define STREAMZAP_RESOLUTION 256 /* number of samples buffered */ -#define STREAMZAP_BUF_LEN 128 +#define SZ_BUF_LEN 128 enum StreamzapDecoderState { PulseSpace, @@ -83,65 +76,52 @@ enum StreamzapDecoderState { IgnorePulse }; -/* Structure to hold all of our device specific stuff - * - * some remarks regarding locking: - * theoretically this struct can be accessed from three threads: - * - * - from lirc_dev through set_use_inc/set_use_dec - * - * - from the USB layer throuh probe/disconnect/irq - * - * Careful placement of lirc_register_driver/lirc_unregister_driver - * calls will prevent conflicts. lirc_dev makes sure that - * set_use_inc/set_use_dec are not being executed and will not be - * called after lirc_unregister_driver returns. - * - * - by the timer callback - * - * The timer is only running when the device is connected and the - * LIRC device is open. Making sure the timer is deleted by - * set_use_dec will make conflicts impossible. - */ -struct usb_streamzap { +/* structure to hold our device specific stuff */ +struct streamzap_ir { + + /* ir-core */ + struct ir_dev_props *props; + struct ir_raw_event rawir; + + /* core device info */ + struct device *dev; + struct input_dev *idev; /* usb */ - /* save off the usb device pointer */ - struct usb_device *udev; - /* the interface for this device */ + struct usb_device *usbdev; struct usb_interface *interface; + struct usb_endpoint_descriptor *endpoint; + struct urb *urb_in; /* buffer & dma */ unsigned char *buf_in; dma_addr_t dma_in; unsigned int buf_in_len; - struct usb_endpoint_descriptor *endpoint; - - /* IRQ */ - struct urb *urb_in; - - /* lirc */ - struct lirc_driver *driver; - struct lirc_buffer *delay_buf; - /* timer used to support delay buffering */ struct timer_list delay_timer; - int timer_running; + bool timer_running; spinlock_t timer_lock; + struct timer_list flush_timer; + bool flush; + + /* delay buffer */ + struct kfifo fifo; + bool fifo_initialized; + /* track what state we're in */ + enum StreamzapDecoderState decoder_state; /* tracks whether we are currently receiving some signal */ - int idle; + bool idle; /* sum of signal lengths received since signal start */ unsigned long sum; /* start time of signal; necessary for gap tracking */ struct timeval signal_last; struct timeval signal_start; - enum StreamzapDecoderState decoder_state; - struct timer_list flush_timer; - int flush; - int in_use; - int timeout_enabled; + /* bool timeout_enabled; */ + + char name[128]; + char phys[64]; }; @@ -149,16 +129,11 @@ struct usb_streamzap { static int streamzap_probe(struct usb_interface *interface, const struct usb_device_id *id); static void streamzap_disconnect(struct usb_interface *interface); -static void usb_streamzap_irq(struct urb *urb); -static int streamzap_use_inc(void *data); -static void streamzap_use_dec(void *data); -static long streamzap_ioctl(struct file *filep, unsigned int cmd, - unsigned long arg); +static void streamzap_callback(struct urb *urb); static int streamzap_suspend(struct usb_interface *intf, pm_message_t message); static int streamzap_resume(struct usb_interface *intf); /* usb specific object needed to register this driver with the usb subsystem */ - static struct usb_driver streamzap_driver = { .name = DRIVER_NAME, .probe = streamzap_probe, @@ -168,13 +143,13 @@ static struct usb_driver streamzap_driver = { .id_table = streamzap_table, }; -static void stop_timer(struct usb_streamzap *sz) +static void streamzap_stop_timer(struct streamzap_ir *sz) { unsigned long flags; spin_lock_irqsave(&sz->timer_lock, flags); if (sz->timer_running) { - sz->timer_running = 0; + sz->timer_running = false; spin_unlock_irqrestore(&sz->timer_lock, flags); del_timer_sync(&sz->delay_timer); } else { @@ -182,175 +157,183 @@ static void stop_timer(struct usb_streamzap *sz) } } -static void flush_timeout(unsigned long arg) +static void streamzap_flush_timeout(unsigned long arg) { - struct usb_streamzap *sz = (struct usb_streamzap *) arg; + struct streamzap_ir *sz = (struct streamzap_ir *)arg; + + dev_info(sz->dev, "%s: callback firing\n", __func__); /* finally start accepting data */ - sz->flush = 0; + sz->flush = false; } -static void delay_timeout(unsigned long arg) + +static void streamzap_delay_timeout(unsigned long arg) { + struct streamzap_ir *sz = (struct streamzap_ir *)arg; + struct ir_raw_event rawir = { .pulse = false, .duration = 0 }; unsigned long flags; + int len, ret; + static unsigned long delay; + bool wake = false; + /* deliver data every 10 ms */ - static unsigned long timer_inc = - (10000/(1000000/HZ)) == 0 ? 1 : (10000/(1000000/HZ)); - struct usb_streamzap *sz = (struct usb_streamzap *) arg; - int data; + delay = msecs_to_jiffies(10); spin_lock_irqsave(&sz->timer_lock, flags); - if (!lirc_buffer_empty(sz->delay_buf) && - !lirc_buffer_full(sz->driver->rbuf)) { - lirc_buffer_read(sz->delay_buf, (unsigned char *) &data); - lirc_buffer_write(sz->driver->rbuf, (unsigned char *) &data); + if (kfifo_len(&sz->fifo) > 0) { + ret = kfifo_out(&sz->fifo, &rawir, sizeof(rawir)); + if (ret != sizeof(rawir)) + dev_err(sz->dev, "Problem w/kfifo_out...\n"); + ir_raw_event_store(sz->idev, &rawir); + wake = true; } - if (!lirc_buffer_empty(sz->delay_buf)) { - while (lirc_buffer_available(sz->delay_buf) < - STREAMZAP_BUF_LEN / 2 && - !lirc_buffer_full(sz->driver->rbuf)) { - lirc_buffer_read(sz->delay_buf, - (unsigned char *) &data); - lirc_buffer_write(sz->driver->rbuf, - (unsigned char *) &data); - } - if (sz->timer_running) { - sz->delay_timer.expires = jiffies + timer_inc; - add_timer(&sz->delay_timer); + + len = kfifo_len(&sz->fifo); + if (len > 0) { + while ((len < SZ_BUF_LEN / 2) && + (len < SZ_BUF_LEN * sizeof(int))) { + ret = kfifo_out(&sz->fifo, &rawir, sizeof(rawir)); + if (ret != sizeof(rawir)) + dev_err(sz->dev, "Problem w/kfifo_out...\n"); + ir_raw_event_store(sz->idev, &rawir); + wake = true; + len = kfifo_len(&sz->fifo); } + if (sz->timer_running) + mod_timer(&sz->delay_timer, jiffies + delay); + } else { - sz->timer_running = 0; + sz->timer_running = false; } - if (!lirc_buffer_empty(sz->driver->rbuf)) - wake_up(&sz->driver->rbuf->wait_poll); + if (wake) + ir_raw_event_handle(sz->idev); spin_unlock_irqrestore(&sz->timer_lock, flags); } -static void flush_delay_buffer(struct usb_streamzap *sz) +static void streamzap_flush_delay_buffer(struct streamzap_ir *sz) { - int data; - int empty = 1; - - while (!lirc_buffer_empty(sz->delay_buf)) { - empty = 0; - lirc_buffer_read(sz->delay_buf, (unsigned char *) &data); - if (!lirc_buffer_full(sz->driver->rbuf)) { - lirc_buffer_write(sz->driver->rbuf, - (unsigned char *) &data); - } else { - dprintk("buffer overflow", sz->driver->minor); - } + struct ir_raw_event rawir = { .pulse = false, .duration = 0 }; + bool wake = false; + int ret; + + while (kfifo_len(&sz->fifo) > 0) { + ret = kfifo_out(&sz->fifo, &rawir, sizeof(rawir)); + if (ret != sizeof(rawir)) + dev_err(sz->dev, "Problem w/kfifo_out...\n"); + ir_raw_event_store(sz->idev, &rawir); + wake = true; } - if (!empty) - wake_up(&sz->driver->rbuf->wait_poll); + + if (wake) + ir_raw_event_handle(sz->idev); } -static void push(struct usb_streamzap *sz, unsigned char *data) +static void sz_push(struct streamzap_ir *sz) { + struct ir_raw_event rawir = { .pulse = false, .duration = 0 }; unsigned long flags; + int ret; spin_lock_irqsave(&sz->timer_lock, flags); - if (lirc_buffer_full(sz->delay_buf)) { - int read_data; - - lirc_buffer_read(sz->delay_buf, - (unsigned char *) &read_data); - if (!lirc_buffer_full(sz->driver->rbuf)) { - lirc_buffer_write(sz->driver->rbuf, - (unsigned char *) &read_data); - } else { - dprintk("buffer overflow", sz->driver->minor); - } + if (kfifo_len(&sz->fifo) >= sizeof(int) * SZ_BUF_LEN) { + ret = kfifo_out(&sz->fifo, &rawir, sizeof(rawir)); + if (ret != sizeof(rawir)) + dev_err(sz->dev, "Problem w/kfifo_out...\n"); + ir_raw_event_store(sz->idev, &rawir); } - lirc_buffer_write(sz->delay_buf, data); + kfifo_in(&sz->fifo, &sz->rawir, sizeof(rawir)); if (!sz->timer_running) { - sz->delay_timer.expires = jiffies + HZ/10; + sz->delay_timer.expires = jiffies + (HZ / 10); add_timer(&sz->delay_timer); - sz->timer_running = 1; + sz->timer_running = true; } spin_unlock_irqrestore(&sz->timer_lock, flags); } -static void push_full_pulse(struct usb_streamzap *sz, - unsigned char value) +static void sz_push_full_pulse(struct streamzap_ir *sz, + unsigned char value) { - int pulse; - if (sz->idle) { long deltv; - int tmp; sz->signal_last = sz->signal_start; do_gettimeofday(&sz->signal_start); - deltv = sz->signal_start.tv_sec-sz->signal_last.tv_sec; + deltv = sz->signal_start.tv_sec - sz->signal_last.tv_sec; + sz->rawir.pulse = false; if (deltv > 15) { /* really long time */ - tmp = LIRC_SPACE(LIRC_VALUE_MASK); + sz->rawir.duration = IR_MAX_DURATION; } else { - tmp = (int) (deltv*1000000+ - sz->signal_start.tv_usec - - sz->signal_last.tv_usec); - tmp -= sz->sum; - tmp = LIRC_SPACE(tmp); + sz->rawir.duration = (int)(deltv * 1000000 + + sz->signal_start.tv_usec - + sz->signal_last.tv_usec); + sz->rawir.duration -= sz->sum; + sz->rawir.duration *= 1000; + sz->rawir.duration &= IR_MAX_DURATION; } - dprintk("ls %u", sz->driver->minor, tmp); - push(sz, (char *)&tmp); + dev_dbg(sz->dev, "ls %u\n", sz->rawir.duration); + sz_push(sz); sz->idle = 0; sz->sum = 0; } - pulse = ((int) value) * STREAMZAP_RESOLUTION; - pulse += STREAMZAP_RESOLUTION / 2; - sz->sum += pulse; - pulse = LIRC_PULSE(pulse); - - dprintk("p %u", sz->driver->minor, pulse & PULSE_MASK); - push(sz, (char *)&pulse); + sz->rawir.pulse = true; + sz->rawir.duration = ((int) value) * STREAMZAP_RESOLUTION; + sz->rawir.duration += STREAMZAP_RESOLUTION / 2; + sz->sum += sz->rawir.duration; + sz->rawir.duration *= 1000; + sz->rawir.duration &= IR_MAX_DURATION; + dev_dbg(sz->dev, "p %u\n", sz->rawir.duration); + sz_push(sz); } -static void push_half_pulse(struct usb_streamzap *sz, - unsigned char value) +static void sz_push_half_pulse(struct streamzap_ir *sz, + unsigned char value) { - push_full_pulse(sz, (value & STREAMZAP_PULSE_MASK)>>4); + sz_push_full_pulse(sz, (value & STREAMZAP_PULSE_MASK) >> 4); } -static void push_full_space(struct usb_streamzap *sz, - unsigned char value) +static void sz_push_full_space(struct streamzap_ir *sz, + unsigned char value) { - int space; - - space = ((int) value)*STREAMZAP_RESOLUTION; - space += STREAMZAP_RESOLUTION/2; - sz->sum += space; - space = LIRC_SPACE(space); - dprintk("s %u", sz->driver->minor, space); - push(sz, (char *)&space); + sz->rawir.pulse = false; + sz->rawir.duration = ((int) value) * STREAMZAP_RESOLUTION; + sz->rawir.duration += STREAMZAP_RESOLUTION / 2; + sz->sum += sz->rawir.duration; + sz->rawir.duration *= 1000; + dev_dbg(sz->dev, "s %u\n", sz->rawir.duration); + sz_push(sz); } -static void push_half_space(struct usb_streamzap *sz, - unsigned char value) +static void sz_push_half_space(struct streamzap_ir *sz, + unsigned long value) { - push_full_space(sz, value & STREAMZAP_SPACE_MASK); + sz_push_full_space(sz, value & STREAMZAP_SPACE_MASK); } /** - * usb_streamzap_irq - IRQ handler + * streamzap_callback - usb IRQ handler callback * * This procedure is invoked on reception of data from * the usb remote. */ -static void usb_streamzap_irq(struct urb *urb) +static void streamzap_callback(struct urb *urb) { - struct usb_streamzap *sz; - int len; - unsigned int i = 0; + struct streamzap_ir *sz; + unsigned int i; + int len; + #if 0 + static int timeout = (((STREAMZAP_TIMEOUT * STREAMZAP_RESOLUTION) & + IR_MAX_DURATION) | 0x03000000); + #endif if (!urb) return; @@ -366,51 +349,52 @@ static void usb_streamzap_irq(struct urb *urb) * this urb is terminated, clean up. * sz might already be invalid at this point */ - dprintk("urb status: %d", -1, urb->status); + dev_err(sz->dev, "urb terminated, status: %d\n", urb->status); return; default: break; } - dprintk("received %d", sz->driver->minor, urb->actual_length); + dev_dbg(sz->dev, "%s: received urb, len %d\n", __func__, len); if (!sz->flush) { for (i = 0; i < urb->actual_length; i++) { - dprintk("%d: %x", sz->driver->minor, - i, (unsigned char) sz->buf_in[i]); + dev_dbg(sz->dev, "%d: %x\n", i, + (unsigned char)sz->buf_in[i]); switch (sz->decoder_state) { case PulseSpace: - if ((sz->buf_in[i]&STREAMZAP_PULSE_MASK) == + if ((sz->buf_in[i] & STREAMZAP_PULSE_MASK) == STREAMZAP_PULSE_MASK) { sz->decoder_state = FullPulse; continue; - } else if ((sz->buf_in[i]&STREAMZAP_SPACE_MASK) + } else if ((sz->buf_in[i] & STREAMZAP_SPACE_MASK) == STREAMZAP_SPACE_MASK) { - push_half_pulse(sz, sz->buf_in[i]); + sz_push_half_pulse(sz, sz->buf_in[i]); sz->decoder_state = FullSpace; continue; } else { - push_half_pulse(sz, sz->buf_in[i]); - push_half_space(sz, sz->buf_in[i]); + sz_push_half_pulse(sz, sz->buf_in[i]); + sz_push_half_space(sz, sz->buf_in[i]); } break; case FullPulse: - push_full_pulse(sz, sz->buf_in[i]); + sz_push_full_pulse(sz, sz->buf_in[i]); sz->decoder_state = IgnorePulse; break; case FullSpace: if (sz->buf_in[i] == STREAMZAP_TIMEOUT) { sz->idle = 1; - stop_timer(sz); + streamzap_stop_timer(sz); + #if 0 if (sz->timeout_enabled) { - int timeout = - LIRC_TIMEOUT - (STREAMZAP_TIMEOUT * - STREAMZAP_RESOLUTION); - push(sz, (char *)&timeout); + sz->rawir.pulse = false; + sz->rawir.duration = timeout; + sz->rawir.duration *= 1000; + sz_push(sz); } - flush_delay_buffer(sz); + #endif + streamzap_flush_delay_buffer(sz); } else - push_full_space(sz, sz->buf_in[i]); + sz_push_full_space(sz, sz->buf_in[i]); sz->decoder_state = PulseSpace; break; case IgnorePulse: @@ -419,7 +403,7 @@ static void usb_streamzap_irq(struct urb *urb) sz->decoder_state = FullSpace; continue; } - push_half_space(sz, sz->buf_in[i]); + sz_push_half_space(sz, sz->buf_in[i]); sz->decoder_state = PulseSpace; break; } @@ -431,16 +415,80 @@ static void usb_streamzap_irq(struct urb *urb) return; } -static const struct file_operations streamzap_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = streamzap_ioctl, - .read = lirc_dev_fop_read, - .write = lirc_dev_fop_write, - .poll = lirc_dev_fop_poll, - .open = lirc_dev_fop_open, - .release = lirc_dev_fop_close, -}; +static struct input_dev *streamzap_init_input_dev(struct streamzap_ir *sz) +{ + struct input_dev *idev; + struct ir_dev_props *props; + struct device *dev = sz->dev; + int ret; + + idev = input_allocate_device(); + if (!idev) { + dev_err(dev, "remote input dev allocation failed\n"); + goto idev_alloc_failed; + } + + props = kzalloc(sizeof(struct ir_dev_props), GFP_KERNEL); + if (!props) { + dev_err(dev, "remote ir dev props allocation failed\n"); + goto props_alloc_failed; + } + + snprintf(sz->name, sizeof(sz->name), "Streamzap PC Remote Infrared " + "Receiver (%04x:%04x)", + le16_to_cpu(sz->usbdev->descriptor.idVendor), + le16_to_cpu(sz->usbdev->descriptor.idProduct)); + + idev->name = sz->name; + usb_make_path(sz->usbdev, sz->phys, sizeof(sz->phys)); + strlcat(sz->phys, "/input0", sizeof(sz->phys)); + idev->phys = sz->phys; + + props->priv = sz; + props->driver_type = RC_DRIVER_IR_RAW; + /* FIXME: not sure about supported protocols, check on this */ + props->allowed_protos = IR_TYPE_RC5 | IR_TYPE_RC6; + + sz->props = props; + + ret = ir_input_register(idev, RC_MAP_RC5_STREAMZAP, props, DRIVER_NAME); + if (ret < 0) { + dev_err(dev, "remote input device register failed\n"); + goto irdev_failed; + } + + return idev; + +irdev_failed: + kfree(props); +props_alloc_failed: + input_free_device(idev); +idev_alloc_failed: + return NULL; +} + +static int streamzap_delay_buf_init(struct streamzap_ir *sz) +{ + int ret; + + ret = kfifo_alloc(&sz->fifo, sizeof(int) * SZ_BUF_LEN, + GFP_KERNEL); + if (ret == 0) + sz->fifo_initialized = 1; + return ret; +} + +static void streamzap_start_flush_timer(struct streamzap_ir *sz) +{ + sz->flush_timer.expires = jiffies + HZ; + sz->flush = true; + add_timer(&sz->flush_timer); + + sz->urb_in->dev = sz->usbdev; + if (usb_submit_urb(sz->urb_in, GFP_ATOMIC)) + dev_err(sz->dev, "urb submit failed\n"); +} /** * streamzap_probe @@ -449,33 +497,30 @@ static const struct file_operations streamzap_fops = { * On any failure the return value is the ERROR * On success return 0 */ -static int streamzap_probe(struct usb_interface *interface, - const struct usb_device_id *id) +static int __devinit streamzap_probe(struct usb_interface *intf, + const struct usb_device_id *id) { - struct usb_device *udev = interface_to_usbdev(interface); + struct usb_device *usbdev = interface_to_usbdev(intf); struct usb_host_interface *iface_host; - struct usb_streamzap *sz; - struct lirc_driver *driver; - struct lirc_buffer *lirc_buf; - struct lirc_buffer *delay_buf; + struct streamzap_ir *sz = NULL; char buf[63], name[128] = ""; int retval = -ENOMEM; - int minor = 0; + int pipe, maxp; /* Allocate space for device driver specific data */ - sz = kzalloc(sizeof(struct usb_streamzap), GFP_KERNEL); - if (sz == NULL) + sz = kzalloc(sizeof(struct streamzap_ir), GFP_KERNEL); + if (!sz) return -ENOMEM; - sz->udev = udev; - sz->interface = interface; + sz->usbdev = usbdev; + sz->interface = intf; /* Check to ensure endpoint information matches requirements */ - iface_host = interface->cur_altsetting; + iface_host = intf->cur_altsetting; if (iface_host->desc.bNumEndpoints != 1) { - err("%s: Unexpected desc.bNumEndpoints (%d)", __func__, - iface_host->desc.bNumEndpoints); + dev_err(&intf->dev, "%s: Unexpected desc.bNumEndpoints (%d)\n", + __func__, iface_host->desc.bNumEndpoints); retval = -ENODEV; goto free_sz; } @@ -483,219 +528,110 @@ static int streamzap_probe(struct usb_interface *interface, sz->endpoint = &(iface_host->endpoint[0].desc); if ((sz->endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) != USB_DIR_IN) { - err("%s: endpoint doesn't match input device 02%02x", - __func__, sz->endpoint->bEndpointAddress); + dev_err(&intf->dev, "%s: endpoint doesn't match input device " + "02%02x\n", __func__, sz->endpoint->bEndpointAddress); retval = -ENODEV; goto free_sz; } if ((sz->endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT) { - err("%s: endpoint attributes don't match xfer 02%02x", - __func__, sz->endpoint->bmAttributes); + dev_err(&intf->dev, "%s: endpoint attributes don't match xfer " + "02%02x\n", __func__, sz->endpoint->bmAttributes); retval = -ENODEV; goto free_sz; } - if (sz->endpoint->wMaxPacketSize == 0) { - err("%s: endpoint message size==0? ", __func__); + pipe = usb_rcvintpipe(usbdev, sz->endpoint->bEndpointAddress); + maxp = usb_maxpacket(usbdev, pipe, usb_pipeout(pipe)); + + if (maxp == 0) { + dev_err(&intf->dev, "%s: endpoint Max Packet Size is 0!?!\n", + __func__); retval = -ENODEV; goto free_sz; } /* Allocate the USB buffer and IRQ URB */ - - sz->buf_in_len = sz->endpoint->wMaxPacketSize; - sz->buf_in = usb_alloc_coherent(sz->udev, sz->buf_in_len, - GFP_ATOMIC, &sz->dma_in); - if (sz->buf_in == NULL) + sz->buf_in = usb_alloc_coherent(usbdev, maxp, GFP_ATOMIC, &sz->dma_in); + if (!sz->buf_in) goto free_sz; sz->urb_in = usb_alloc_urb(0, GFP_KERNEL); - if (sz->urb_in == NULL) - goto free_sz; - - /* Connect this device to the LIRC sub-system */ - driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL); - if (!driver) - goto free_sz; + if (!sz->urb_in) + goto free_buf_in; - lirc_buf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL); - if (!lirc_buf) - goto free_driver; - if (lirc_buffer_init(lirc_buf, sizeof(int), STREAMZAP_BUF_LEN)) - goto kfree_lirc_buf; - - delay_buf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL); - if (!delay_buf) - goto free_lirc_buf; - if (lirc_buffer_init(delay_buf, sizeof(int), STREAMZAP_BUF_LEN)) - goto kfree_delay_buf; - - sz->driver = driver; - strcpy(sz->driver->name, DRIVER_NAME); - sz->driver->minor = -1; - sz->driver->sample_rate = 0; - sz->driver->code_length = sizeof(int) * 8; - sz->driver->features = LIRC_CAN_REC_MODE2 | - LIRC_CAN_GET_REC_RESOLUTION | - LIRC_CAN_SET_REC_TIMEOUT; - sz->driver->data = sz; - sz->driver->min_timeout = STREAMZAP_TIMEOUT * STREAMZAP_RESOLUTION; - sz->driver->max_timeout = STREAMZAP_TIMEOUT * STREAMZAP_RESOLUTION; - sz->driver->rbuf = lirc_buf; - sz->delay_buf = delay_buf; - sz->driver->set_use_inc = &streamzap_use_inc; - sz->driver->set_use_dec = &streamzap_use_dec; - sz->driver->fops = &streamzap_fops; - sz->driver->dev = &interface->dev; - sz->driver->owner = THIS_MODULE; - - sz->idle = 1; - sz->decoder_state = PulseSpace; - init_timer(&sz->delay_timer); - sz->delay_timer.function = delay_timeout; - sz->delay_timer.data = (unsigned long) sz; - sz->timer_running = 0; - spin_lock_init(&sz->timer_lock); - - init_timer(&sz->flush_timer); - sz->flush_timer.function = flush_timeout; - sz->flush_timer.data = (unsigned long) sz; - /* Complete final initialisations */ + sz->dev = &intf->dev; + sz->buf_in_len = maxp; - usb_fill_int_urb(sz->urb_in, udev, - usb_rcvintpipe(udev, sz->endpoint->bEndpointAddress), - sz->buf_in, sz->buf_in_len, usb_streamzap_irq, sz, - sz->endpoint->bInterval); - sz->urb_in->transfer_dma = sz->dma_in; - sz->urb_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; - - if (udev->descriptor.iManufacturer - && usb_string(udev, udev->descriptor.iManufacturer, + if (usbdev->descriptor.iManufacturer + && usb_string(usbdev, usbdev->descriptor.iManufacturer, buf, sizeof(buf)) > 0) strlcpy(name, buf, sizeof(name)); - if (udev->descriptor.iProduct - && usb_string(udev, udev->descriptor.iProduct, + if (usbdev->descriptor.iProduct + && usb_string(usbdev, usbdev->descriptor.iProduct, buf, sizeof(buf)) > 0) snprintf(name + strlen(name), sizeof(name) - strlen(name), " %s", buf); - minor = lirc_register_driver(driver); - - if (minor < 0) - goto free_delay_buf; - - sz->driver->minor = minor; - - usb_set_intfdata(interface, sz); - - printk(KERN_INFO DRIVER_NAME "[%d]: %s on usb%d:%d attached\n", - sz->driver->minor, name, - udev->bus->busnum, sz->udev->devnum); - - return 0; - -free_delay_buf: - lirc_buffer_free(sz->delay_buf); -kfree_delay_buf: - kfree(delay_buf); -free_lirc_buf: - lirc_buffer_free(sz->driver->rbuf); -kfree_lirc_buf: - kfree(lirc_buf); -free_driver: - kfree(driver); -free_sz: - if (retval == -ENOMEM) - err("Out of memory"); - - if (sz) { - usb_free_urb(sz->urb_in); - usb_free_coherent(udev, sz->buf_in_len, sz->buf_in, sz->dma_in); - kfree(sz); - } - - return retval; -} - -static int streamzap_use_inc(void *data) -{ - struct usb_streamzap *sz = data; - - if (!sz) { - dprintk("%s called with no context", -1, __func__); - return -EINVAL; + retval = streamzap_delay_buf_init(sz); + if (retval) { + dev_err(&intf->dev, "%s: delay buffer init failed\n", __func__); + goto free_urb_in; } - dprintk("set use inc", sz->driver->minor); - lirc_buffer_clear(sz->driver->rbuf); - lirc_buffer_clear(sz->delay_buf); + sz->idev = streamzap_init_input_dev(sz); + if (!sz->idev) + goto input_dev_fail; - sz->flush_timer.expires = jiffies + HZ; - sz->flush = 1; - add_timer(&sz->flush_timer); + sz->idle = true; + sz->decoder_state = PulseSpace; + #if 0 + /* not yet supported, depends on patches from maxim */ + /* see also: LIRC_GET_REC_RESOLUTION and LIRC_SET_REC_TIMEOUT */ + sz->timeout_enabled = false; + sz->min_timeout = STREAMZAP_TIMEOUT * STREAMZAP_RESOLUTION * 1000; + sz->max_timeout = STREAMZAP_TIMEOUT * STREAMZAP_RESOLUTION * 1000; + #endif - sz->urb_in->dev = sz->udev; - if (usb_submit_urb(sz->urb_in, GFP_ATOMIC)) { - dprintk("open result = -EIO error submitting urb", - sz->driver->minor); - return -EIO; - } - sz->in_use++; + init_timer(&sz->delay_timer); + sz->delay_timer.function = streamzap_delay_timeout; + sz->delay_timer.data = (unsigned long)sz; + spin_lock_init(&sz->timer_lock); - return 0; -} + init_timer(&sz->flush_timer); + sz->flush_timer.function = streamzap_flush_timeout; + sz->flush_timer.data = (unsigned long)sz; -static void streamzap_use_dec(void *data) -{ - struct usb_streamzap *sz = data; + do_gettimeofday(&sz->signal_start); - if (!sz) { - dprintk("%s called with no context", -1, __func__); - return; - } - dprintk("set use dec", sz->driver->minor); + /* Complete final initialisations */ + usb_fill_int_urb(sz->urb_in, usbdev, pipe, sz->buf_in, + maxp, (usb_complete_t)streamzap_callback, + sz, sz->endpoint->bInterval); + sz->urb_in->transfer_dma = sz->dma_in; + sz->urb_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; - if (sz->flush) { - sz->flush = 0; - del_timer_sync(&sz->flush_timer); - } + usb_set_intfdata(intf, sz); - usb_kill_urb(sz->urb_in); + streamzap_start_flush_timer(sz); - stop_timer(sz); + dev_info(sz->dev, "Registered %s on usb%d:%d\n", name, + usbdev->bus->busnum, usbdev->devnum); - sz->in_use--; -} + return 0; -static long streamzap_ioctl(struct file *filep, unsigned int cmd, - unsigned long arg) -{ - int result = 0; - int val; - struct usb_streamzap *sz = lirc_get_pdata(filep); +input_dev_fail: + kfifo_free(&sz->fifo); +free_urb_in: + usb_free_urb(sz->urb_in); +free_buf_in: + usb_free_coherent(usbdev, maxp, sz->buf_in, sz->dma_in); +free_sz: + kfree(sz); - switch (cmd) { - case LIRC_GET_REC_RESOLUTION: - result = put_user(STREAMZAP_RESOLUTION, (unsigned int *) arg); - break; - case LIRC_SET_REC_TIMEOUT: - result = get_user(val, (int *)arg); - if (result == 0) { - if (val == STREAMZAP_TIMEOUT * STREAMZAP_RESOLUTION) - sz->timeout_enabled = 1; - else if (val == 0) - sz->timeout_enabled = 0; - else - result = -EINVAL; - } - break; - default: - return lirc_dev_fop_ioctl(filep, cmd, arg); - } - return result; + return retval; } /** @@ -704,116 +640,100 @@ static long streamzap_ioctl(struct file *filep, unsigned int cmd, * Called by the usb core when the device is removed from the system. * * This routine guarantees that the driver will not submit any more urbs - * by clearing dev->udev. It is also supposed to terminate any currently + * by clearing dev->usbdev. It is also supposed to terminate any currently * active urbs. Unfortunately, usb_bulk_msg(), used in streamzap_read(), * does not provide any way to do this. */ static void streamzap_disconnect(struct usb_interface *interface) { - struct usb_streamzap *sz; - int errnum; - int minor; - - sz = usb_get_intfdata(interface); + struct streamzap_ir *sz = usb_get_intfdata(interface); + struct usb_device *usbdev = interface_to_usbdev(interface); - /* unregister from the LIRC sub-system */ + usb_set_intfdata(interface, NULL); - errnum = lirc_unregister_driver(sz->driver->minor); - if (errnum != 0) - dprintk("error in lirc_unregister: (returned %d)", - sz->driver->minor, errnum); + if (!sz) + return; - lirc_buffer_free(sz->delay_buf); - lirc_buffer_free(sz->driver->rbuf); + if (sz->flush) { + sz->flush = false; + del_timer_sync(&sz->flush_timer); + } - /* unregister from the USB sub-system */ + streamzap_stop_timer(sz); + sz->usbdev = NULL; + ir_input_unregister(sz->idev); + usb_kill_urb(sz->urb_in); usb_free_urb(sz->urb_in); + usb_free_coherent(usbdev, sz->buf_in_len, sz->buf_in, sz->dma_in); - usb_free_coherent(sz->udev, sz->buf_in_len, sz->buf_in, sz->dma_in); - - minor = sz->driver->minor; - kfree(sz->driver->rbuf); - kfree(sz->driver); - kfree(sz->delay_buf); kfree(sz); - - printk(KERN_INFO DRIVER_NAME "[%d]: disconnected\n", minor); } static int streamzap_suspend(struct usb_interface *intf, pm_message_t message) { - struct usb_streamzap *sz = usb_get_intfdata(intf); + struct streamzap_ir *sz = usb_get_intfdata(intf); - printk(KERN_INFO DRIVER_NAME "[%d]: suspend\n", sz->driver->minor); - if (sz->in_use) { - if (sz->flush) { - sz->flush = 0; - del_timer_sync(&sz->flush_timer); - } + if (sz->flush) { + sz->flush = false; + del_timer_sync(&sz->flush_timer); + } - stop_timer(sz); + streamzap_stop_timer(sz); + + usb_kill_urb(sz->urb_in); - usb_kill_urb(sz->urb_in); - } return 0; } static int streamzap_resume(struct usb_interface *intf) { - struct usb_streamzap *sz = usb_get_intfdata(intf); + struct streamzap_ir *sz = usb_get_intfdata(intf); - lirc_buffer_clear(sz->driver->rbuf); - lirc_buffer_clear(sz->delay_buf); + if (sz->fifo_initialized) + kfifo_reset(&sz->fifo); - if (sz->in_use) { - sz->flush_timer.expires = jiffies + HZ; - sz->flush = 1; - add_timer(&sz->flush_timer); + sz->flush_timer.expires = jiffies + HZ; + sz->flush = true; + add_timer(&sz->flush_timer); - sz->urb_in->dev = sz->udev; - if (usb_submit_urb(sz->urb_in, GFP_ATOMIC)) { - dprintk("open result = -EIO error submitting urb", - sz->driver->minor); - return -EIO; - } + if (usb_submit_urb(sz->urb_in, GFP_ATOMIC)) { + dev_err(sz->dev, "Error sumbiting urb\n"); + return -EIO; } + return 0; } /** - * usb_streamzap_init + * streamzap_init */ -static int __init usb_streamzap_init(void) +static int __init streamzap_init(void) { - int result; + int ret; /* register this driver with the USB subsystem */ - result = usb_register(&streamzap_driver); - - if (result) { - err("usb_register failed. Error number %d", - result); - return result; - } + ret = usb_register(&streamzap_driver); + if (ret < 0) + printk(KERN_ERR DRIVER_NAME ": usb register failed, " + "result = %d\n", ret); - printk(KERN_INFO DRIVER_NAME " " DRIVER_VERSION " registered\n"); - return 0; + return ret; } /** - * usb_streamzap_exit + * streamzap_exit */ -static void __exit usb_streamzap_exit(void) +static void __exit streamzap_exit(void) { usb_deregister(&streamzap_driver); } -module_init(usb_streamzap_init); -module_exit(usb_streamzap_exit); +module_init(streamzap_init); +module_exit(streamzap_exit); -MODULE_AUTHOR("Christoph Bartelmus, Greg Wickham, Adrian Dewhurst"); +MODULE_AUTHOR("Jarod Wilson "); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_LICENSE("GPL"); diff --git a/include/media/rc-map.h b/include/media/rc-map.h index 9569d0863f8..a9c041d4966 100644 --- a/include/media/rc-map.h +++ b/include/media/rc-map.h @@ -114,6 +114,7 @@ void rc_map_init(void); #define RC_MAP_PURPLETV "rc-purpletv" #define RC_MAP_PV951 "rc-pv951" #define RC_MAP_RC5_HAUPPAUGE_NEW "rc-rc5-hauppauge-new" +#define RC_MAP_RC5_STREAMZAP "rc-rc5-streamzap" #define RC_MAP_RC5_TV "rc-rc5-tv" #define RC_MAP_RC6_MCE "rc-rc6-mce" #define RC_MAP_REAL_AUDIO_220_32_KEYS "rc-real-audio-220-32-keys" -- cgit v1.2.3-70-g09d2 From b378f43fe9466e7712a8b16be64795ffca3a937e Mon Sep 17 00:00:00 2001 From: Maxim Levitsky Date: Sat, 31 Jul 2010 11:59:21 -0300 Subject: V4L/DVB: IR: Allow not to compile keymaps in Currently, ir device registration fails if keymap requested by driver is not found. Fix that by always compiling in the empty keymap, and using it as a failback. Signed-off-by: Maxim Levitsky Acked-by: Jarod Wilson Signed-off-by: Mauro Carvalho Chehab --- drivers/media/IR/ir-core-priv.h | 3 ++- drivers/media/IR/ir-sysfs.c | 2 ++ drivers/media/IR/keymaps/Makefile | 1 - drivers/media/IR/keymaps/rc-empty.c | 44 ------------------------------------- drivers/media/IR/rc-map.c | 23 +++++++++++++++++++ include/media/ir-core.h | 8 +++++-- 6 files changed, 33 insertions(+), 48 deletions(-) delete mode 100644 drivers/media/IR/keymaps/rc-empty.c (limited to 'include') diff --git a/drivers/media/IR/ir-core-priv.h b/drivers/media/IR/ir-core-priv.h index 502d477b391..be6817261c8 100644 --- a/drivers/media/IR/ir-core-priv.h +++ b/drivers/media/IR/ir-core-priv.h @@ -126,7 +126,8 @@ int ir_raw_handler_register(struct ir_raw_handler *ir_raw_handler); void ir_raw_handler_unregister(struct ir_raw_handler *ir_raw_handler); void ir_raw_init(void); - +int ir_rcmap_init(void); +void ir_rcmap_cleanup(void); /* * Decoder initialization code * diff --git a/drivers/media/IR/ir-sysfs.c b/drivers/media/IR/ir-sysfs.c index 6273047e915..96dafc425c8 100644 --- a/drivers/media/IR/ir-sysfs.c +++ b/drivers/media/IR/ir-sysfs.c @@ -325,6 +325,7 @@ static int __init ir_core_init(void) /* Initialize/load the decoders/keymap code that will be used */ ir_raw_init(); + ir_rcmap_init(); return 0; } @@ -332,6 +333,7 @@ static int __init ir_core_init(void) static void __exit ir_core_exit(void) { class_unregister(&ir_input_class); + ir_rcmap_cleanup(); } module_init(ir_core_init); diff --git a/drivers/media/IR/keymaps/Makefile b/drivers/media/IR/keymaps/Makefile index c9fcc41944e..950e5d953c6 100644 --- a/drivers/media/IR/keymaps/Makefile +++ b/drivers/media/IR/keymaps/Makefile @@ -19,7 +19,6 @@ obj-$(CONFIG_RC_MAP) += rc-adstech-dvb-t-pci.o \ rc-dm1105-nec.o \ rc-dntv-live-dvb-t.o \ rc-dntv-live-dvbt-pro.o \ - rc-empty.o \ rc-em-terratec.o \ rc-encore-enltv2.o \ rc-encore-enltv.o \ diff --git a/drivers/media/IR/keymaps/rc-empty.c b/drivers/media/IR/keymaps/rc-empty.c deleted file mode 100644 index 3b338d84b47..00000000000 --- a/drivers/media/IR/keymaps/rc-empty.c +++ /dev/null @@ -1,44 +0,0 @@ -/* empty.h - Keytable for empty Remote Controller - * - * keymap imported from ir-keymaps.c - * - * Copyright (c) 2010 by Mauro Carvalho Chehab - * - * 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 - -/* empty keytable, can be used as placeholder for not-yet created keytables */ - -static struct ir_scancode empty[] = { - { 0x2a, KEY_COFFEE }, -}; - -static struct rc_keymap empty_map = { - .map = { - .scan = empty, - .size = ARRAY_SIZE(empty), - .ir_type = IR_TYPE_UNKNOWN, /* Legacy IR type */ - .name = RC_MAP_EMPTY, - } -}; - -static int __init init_rc_map_empty(void) -{ - return ir_register_map(&empty_map); -} - -static void __exit exit_rc_map_empty(void) -{ - ir_unregister_map(&empty_map); -} - -module_init(init_rc_map_empty) -module_exit(exit_rc_map_empty) - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Mauro Carvalho Chehab "); diff --git a/drivers/media/IR/rc-map.c b/drivers/media/IR/rc-map.c index 46a8f1524b5..689143f2fff 100644 --- a/drivers/media/IR/rc-map.c +++ b/drivers/media/IR/rc-map.c @@ -82,3 +82,26 @@ void ir_unregister_map(struct rc_keymap *map) } EXPORT_SYMBOL_GPL(ir_unregister_map); + +static struct ir_scancode empty[] = { + { 0x2a, KEY_COFFEE }, +}; + +static struct rc_keymap empty_map = { + .map = { + .scan = empty, + .size = ARRAY_SIZE(empty), + .ir_type = IR_TYPE_UNKNOWN, /* Legacy IR type */ + .name = RC_MAP_EMPTY, + } +}; + +int ir_rcmap_init(void) +{ + return ir_register_map(&empty_map); +} + +void ir_rcmap_cleanup(void) +{ + ir_unregister_map(&empty_map); +} diff --git a/include/media/ir-core.h b/include/media/ir-core.h index 513e60dd101..197d05aa83d 100644 --- a/include/media/ir-core.h +++ b/include/media/ir-core.h @@ -110,8 +110,12 @@ static inline int ir_input_register(struct input_dev *dev, return -EINVAL; ir_codes = get_rc_map(map_name); - if (!ir_codes) - return -EINVAL; + if (!ir_codes) { + ir_codes = get_rc_map(RC_MAP_EMPTY); + + if (!ir_codes) + return -EINVAL; + } rc = __ir_input_register(dev, ir_codes, props, driver_name); if (rc < 0) -- cgit v1.2.3-70-g09d2 From 4a702ebf61120906696f8366dd2be0653b1643e3 Mon Sep 17 00:00:00 2001 From: Maxim Levitsky Date: Sat, 31 Jul 2010 11:59:22 -0300 Subject: V4L/DVB: IR: add helper function for hardware with small o/b buffer Some ir input devices have small buffer, and interrupt the host each time it is full (or half full) Add a helper that automaticly handles timeouts, and also automaticly merges samples of same time (space-space) Such samples might be placed by hardware because size of sample in the buffer is small (a byte for example). Also remove constness from ir_dev_props, because it now contains timeout settings that driver might want to change Signed-off-by: Maxim Levitsky Acked-by: Jarod Wilson Signed-off-by: Mauro Carvalho Chehab --- drivers/media/IR/ir-core-priv.h | 1 + drivers/media/IR/ir-keytable.c | 2 +- drivers/media/IR/ir-raw-event.c | 84 +++++++++++++++++++++++++++++++++++++++++ include/media/ir-core.h | 23 +++++++++-- 4 files changed, 106 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/drivers/media/IR/ir-core-priv.h b/drivers/media/IR/ir-core-priv.h index be6817261c8..8053e3b8427 100644 --- a/drivers/media/IR/ir-core-priv.h +++ b/drivers/media/IR/ir-core-priv.h @@ -41,6 +41,7 @@ struct ir_raw_event_ctrl { /* raw decoder state follows */ struct ir_raw_event prev_ev; + struct ir_raw_event this_ev; struct nec_dec { int state; unsigned count; diff --git a/drivers/media/IR/ir-keytable.c b/drivers/media/IR/ir-keytable.c index 15a0f192d41..411976d10f8 100644 --- a/drivers/media/IR/ir-keytable.c +++ b/drivers/media/IR/ir-keytable.c @@ -428,7 +428,7 @@ static void ir_close(struct input_dev *input_dev) */ int __ir_input_register(struct input_dev *input_dev, const struct ir_scancode_table *rc_tab, - const struct ir_dev_props *props, + struct ir_dev_props *props, const char *driver_name) { struct ir_input_dev *ir_dev; diff --git a/drivers/media/IR/ir-raw-event.c b/drivers/media/IR/ir-raw-event.c index d0c18db4c0d..43094e7eccf 100644 --- a/drivers/media/IR/ir-raw-event.c +++ b/drivers/media/IR/ir-raw-event.c @@ -139,6 +139,90 @@ int ir_raw_event_store_edge(struct input_dev *input_dev, enum raw_event_type typ } EXPORT_SYMBOL_GPL(ir_raw_event_store_edge); +/** + * ir_raw_event_store_with_filter() - pass next pulse/space to decoders with some processing + * @input_dev: the struct input_dev device descriptor + * @type: the type of the event that has occurred + * + * This routine (which may be called from an interrupt context) works + * in similiar manner to ir_raw_event_store_edge. + * This routine is intended for devices with limited internal buffer + * It automerges samples of same type, and handles timeouts + */ +int ir_raw_event_store_with_filter(struct input_dev *input_dev, + struct ir_raw_event *ev) +{ + struct ir_input_dev *ir = input_get_drvdata(input_dev); + struct ir_raw_event_ctrl *raw = ir->raw; + + if (!raw || !ir->props) + return -EINVAL; + + /* Ignore spaces in idle mode */ + if (ir->idle && !ev->pulse) + return 0; + else if (ir->idle) + ir_raw_event_set_idle(input_dev, 0); + + if (!raw->this_ev.duration) { + raw->this_ev = *ev; + } else if (ev->pulse == raw->this_ev.pulse) { + raw->this_ev.duration += ev->duration; + } else { + ir_raw_event_store(input_dev, &raw->this_ev); + raw->this_ev = *ev; + } + + /* Enter idle mode if nessesary */ + if (!ev->pulse && ir->props->timeout && + raw->this_ev.duration >= ir->props->timeout) + ir_raw_event_set_idle(input_dev, 1); + return 0; +} +EXPORT_SYMBOL_GPL(ir_raw_event_store_with_filter); + +void ir_raw_event_set_idle(struct input_dev *input_dev, int idle) +{ + struct ir_input_dev *ir = input_get_drvdata(input_dev); + struct ir_raw_event_ctrl *raw = ir->raw; + ktime_t now; + u64 delta; + + if (!ir->props) + return; + + if (!ir->raw) + goto out; + + if (idle) { + IR_dprintk(2, "enter idle mode\n"); + raw->last_event = ktime_get(); + } else { + IR_dprintk(2, "exit idle mode\n"); + + now = ktime_get(); + delta = ktime_to_ns(ktime_sub(now, ir->raw->last_event)); + + WARN_ON(raw->this_ev.pulse); + + raw->this_ev.duration = + min(raw->this_ev.duration + delta, + (u64)IR_MAX_DURATION); + + ir_raw_event_store(input_dev, &raw->this_ev); + + if (raw->this_ev.duration == IR_MAX_DURATION) + ir_raw_event_reset(input_dev); + + raw->this_ev.duration = 0; + } +out: + if (ir->props->s_idle) + ir->props->s_idle(ir->props->priv, idle); + ir->idle = idle; +} +EXPORT_SYMBOL_GPL(ir_raw_event_set_idle); + /** * ir_raw_event_handle() - schedules the decoding of stored ir data * @input_dev: the struct input_dev device descriptor diff --git a/include/media/ir-core.h b/include/media/ir-core.h index 197d05aa83d..a781045aeed 100644 --- a/include/media/ir-core.h +++ b/include/media/ir-core.h @@ -41,6 +41,9 @@ enum rc_driver_type { * anything with it. Yet, as the same keycode table can be used with other * devices, a mask is provided to allow its usage. Drivers should generally * leave this field in blank + * @timeout: optional time after which device stops sending data + * @min_timeout: minimum timeout supported by device + * @max_timeout: maximum timeout supported by device * @priv: driver-specific data, to be used on the callbacks * @change_protocol: allow changing the protocol used on hardware decoders * @open: callback to allow drivers to enable polling/irq when IR input device @@ -50,11 +53,19 @@ enum rc_driver_type { * @s_tx_mask: set transmitter mask (for devices with multiple tx outputs) * @s_tx_carrier: set transmit carrier frequency * @tx_ir: transmit IR + * @s_idle: optional: enable/disable hardware idle mode, upon which, + * device doesn't interrupt host untill it sees IR data */ struct ir_dev_props { enum rc_driver_type driver_type; unsigned long allowed_protos; u32 scanmask; + + u32 timeout; + u32 min_timeout; + u32 max_timeout; + + void *priv; int (*change_protocol)(void *priv, u64 ir_type); int (*open)(void *priv); @@ -62,6 +73,7 @@ struct ir_dev_props { int (*s_tx_mask)(void *priv, u32 mask); int (*s_tx_carrier)(void *priv, u32 carrier); int (*tx_ir)(void *priv, int *txbuf, u32 n); + void (*s_idle)(void *priv, int enable); }; struct ir_input_dev { @@ -69,9 +81,10 @@ struct ir_input_dev { char *driver_name; /* Name of the driver module */ struct ir_scancode_table rc_tab; /* scan/key table */ unsigned long devno; /* device number */ - const struct ir_dev_props *props; /* Device properties */ + struct ir_dev_props *props; /* Device properties */ struct ir_raw_event_ctrl *raw; /* for raw pulse/space events */ struct input_dev *input_dev; /* the input device associated with this device */ + bool idle; /* key info - needed by IR keycode handlers */ spinlock_t keylock; /* protects the below members */ @@ -95,12 +108,12 @@ enum raw_event_type { /* From ir-keytable.c */ int __ir_input_register(struct input_dev *dev, const struct ir_scancode_table *ir_codes, - const struct ir_dev_props *props, + struct ir_dev_props *props, const char *driver_name); static inline int ir_input_register(struct input_dev *dev, const char *map_name, - const struct ir_dev_props *props, + struct ir_dev_props *props, const char *driver_name) { struct ir_scancode_table *ir_codes; struct ir_input_dev *ir_dev; @@ -148,6 +161,10 @@ struct ir_raw_event { void ir_raw_event_handle(struct input_dev *input_dev); int ir_raw_event_store(struct input_dev *input_dev, struct ir_raw_event *ev); int ir_raw_event_store_edge(struct input_dev *input_dev, enum raw_event_type type); +int ir_raw_event_store_with_filter(struct input_dev *input_dev, + struct ir_raw_event *ev); +void ir_raw_event_set_idle(struct input_dev *input_dev, int idle); + static inline void ir_raw_event_reset(struct input_dev *input_dev) { struct ir_raw_event ev = { .pulse = false, .duration = 0 }; -- cgit v1.2.3-70-g09d2 From e589333f346b58f8da5bb8152e1219c52d375ccc Mon Sep 17 00:00:00 2001 From: Maxim Levitsky Date: Sat, 31 Jul 2010 11:59:23 -0300 Subject: V4L/DVB: IR: extend interfaces to support more device settings LIRC: add new IOCTL that enables learning mode (wide band receiver) Still missing features: carrier report & timeout reports. Will need to pack these into ir_raw_event Signed-off-by: Maxim Levitsky Signed-off-by: Mauro Carvalho Chehab --- .../DocBook/v4l/lirc_device_interface.xml | 16 +++ drivers/media/IR/ir-core-priv.h | 1 + drivers/media/IR/ir-lirc-codec.c | 112 +++++++++++++++++---- include/media/ir-core.h | 12 ++- include/media/lirc.h | 5 +- 5 files changed, 125 insertions(+), 21 deletions(-) (limited to 'include') diff --git a/Documentation/DocBook/v4l/lirc_device_interface.xml b/Documentation/DocBook/v4l/lirc_device_interface.xml index 0413234023d..68134c0ab4d 100644 --- a/Documentation/DocBook/v4l/lirc_device_interface.xml +++ b/Documentation/DocBook/v4l/lirc_device_interface.xml @@ -229,6 +229,22 @@ on working with the default settings initially. and LIRC_SETUP_END. Drivers can also choose to ignore these ioctls. + + LIRC_SET_WIDEBAND_RECEIVER + + Some receivers are equipped with special wide band receiver which is intended + to be used to learn output of existing remote. + Calling that ioctl with (1) will enable it, and with (0) disable it. + This might be useful of receivers that have otherwise narrow band receiver + that prevents them to be used with some remotes. + Wide band receiver might also be more precise + On the other hand its disadvantage it usually reduced range of reception. + Note: wide band receiver might be implictly enabled if you enable + carrier reports. In that case it will be disabled as soon as you disable + carrier reports. Trying to disable wide band receiver while carrier + reports are active will do nothing. + + diff --git a/drivers/media/IR/ir-core-priv.h b/drivers/media/IR/ir-core-priv.h index 8053e3b8427..a85a8c7c905 100644 --- a/drivers/media/IR/ir-core-priv.h +++ b/drivers/media/IR/ir-core-priv.h @@ -79,6 +79,7 @@ struct ir_raw_event_ctrl { struct lirc_codec { struct ir_input_dev *ir_dev; struct lirc_driver *drv; + int carrier_low; } lirc; }; diff --git a/drivers/media/IR/ir-lirc-codec.c b/drivers/media/IR/ir-lirc-codec.c index 8ca01fd6713..77b5946413c 100644 --- a/drivers/media/IR/ir-lirc-codec.c +++ b/drivers/media/IR/ir-lirc-codec.c @@ -46,7 +46,6 @@ static int ir_lirc_decode(struct input_dev *input_dev, struct ir_raw_event ev) IR_dprintk(2, "LIRC data transfer started (%uus %s)\n", TO_US(ev.duration), TO_STR(ev.pulse)); - sample = ev.duration / 1000; if (ev.pulse) sample |= PULSE_BIT; @@ -96,13 +95,14 @@ out: return ret; } -static long ir_lirc_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) +static long ir_lirc_ioctl(struct file *filep, unsigned int cmd, + unsigned long __user arg) { struct lirc_codec *lirc; struct ir_input_dev *ir_dev; int ret = 0; void *drv_data; - unsigned long val; + unsigned long val = 0; lirc = lirc_get_pdata(filep); if (!lirc) @@ -114,47 +114,106 @@ static long ir_lirc_ioctl(struct file *filep, unsigned int cmd, unsigned long ar drv_data = ir_dev->props->priv; - switch (cmd) { - case LIRC_SET_TRANSMITTER_MASK: + if (_IOC_DIR(cmd) & _IOC_WRITE) { ret = get_user(val, (unsigned long *)arg); if (ret) return ret; + } + + switch (cmd) { + + /* legacy support */ + case LIRC_GET_SEND_MODE: + val = LIRC_CAN_SEND_PULSE & LIRC_CAN_SEND_MASK; + break; + + case LIRC_SET_SEND_MODE: + if (val != (LIRC_MODE_PULSE & LIRC_CAN_SEND_MASK)) + return -EINVAL; + break; - if (ir_dev->props && ir_dev->props->s_tx_mask) + /* TX settings */ + case LIRC_SET_TRANSMITTER_MASK: + if (ir_dev->props->s_tx_mask) ret = ir_dev->props->s_tx_mask(drv_data, (u32)val); else return -EINVAL; break; case LIRC_SET_SEND_CARRIER: - ret = get_user(val, (unsigned long *)arg); - if (ret) - return ret; - - if (ir_dev->props && ir_dev->props->s_tx_carrier) + if (ir_dev->props->s_tx_carrier) ir_dev->props->s_tx_carrier(drv_data, (u32)val); else return -EINVAL; break; - case LIRC_GET_SEND_MODE: - val = LIRC_CAN_SEND_PULSE & LIRC_CAN_SEND_MASK; - ret = put_user(val, (unsigned long *)arg); + case LIRC_SET_SEND_DUTY_CYCLE: + if (!ir_dev->props->s_tx_duty_cycle) + return -ENOSYS; + + if (val <= 0 || val >= 100) + return -EINVAL; + + ir_dev->props->s_tx_duty_cycle(ir_dev->props->priv, val); break; - case LIRC_SET_SEND_MODE: - ret = get_user(val, (unsigned long *)arg); - if (ret) - return ret; + /* RX settings */ + case LIRC_SET_REC_CARRIER: + if (ir_dev->props->s_rx_carrier_range) + ret = ir_dev->props->s_rx_carrier_range( + ir_dev->props->priv, + ir_dev->raw->lirc.carrier_low, val); + else + return -ENOSYS; - if (val != (LIRC_MODE_PULSE & LIRC_CAN_SEND_MASK)) + if (!ret) + ir_dev->raw->lirc.carrier_low = 0; + break; + + case LIRC_SET_REC_CARRIER_RANGE: + if (val >= 0) + ir_dev->raw->lirc.carrier_low = val; + break; + + + case LIRC_GET_REC_RESOLUTION: + val = ir_dev->props->rx_resolution; + break; + + case LIRC_SET_WIDEBAND_RECEIVER: + if (ir_dev->props->s_learning_mode) + return ir_dev->props->s_learning_mode( + ir_dev->props->priv, !!val); + else + return -ENOSYS; + + /* Generic timeout support */ + case LIRC_GET_MIN_TIMEOUT: + if (!ir_dev->props->max_timeout) + return -ENOSYS; + val = ir_dev->props->min_timeout / 1000; + break; + + case LIRC_GET_MAX_TIMEOUT: + if (!ir_dev->props->max_timeout) + return -ENOSYS; + val = ir_dev->props->max_timeout / 1000; + break; + + case LIRC_SET_REC_TIMEOUT: + if (val < ir_dev->props->min_timeout || + val > ir_dev->props->max_timeout) return -EINVAL; + ir_dev->props->timeout = val * 1000; break; default: return lirc_dev_fop_ioctl(filep, cmd, arg); } + if (_IOC_DIR(cmd) & _IOC_READ) + ret = put_user(val, (unsigned long *)arg); + return ret; } @@ -200,13 +259,28 @@ static int ir_lirc_register(struct input_dev *input_dev) features = LIRC_CAN_REC_MODE2; if (ir_dev->props->tx_ir) { + features |= LIRC_CAN_SEND_PULSE; if (ir_dev->props->s_tx_mask) features |= LIRC_CAN_SET_TRANSMITTER_MASK; if (ir_dev->props->s_tx_carrier) features |= LIRC_CAN_SET_SEND_CARRIER; + + if (ir_dev->props->s_tx_duty_cycle) + features |= LIRC_CAN_SET_REC_DUTY_CYCLE; } + if (ir_dev->props->s_rx_carrier_range) + features |= LIRC_CAN_SET_REC_CARRIER | + LIRC_CAN_SET_REC_CARRIER_RANGE; + + if (ir_dev->props->s_learning_mode) + features |= LIRC_CAN_USE_WIDEBAND_RECEIVER; + + if (ir_dev->props->max_timeout) + features |= LIRC_CAN_SET_REC_TIMEOUT; + + snprintf(drv->name, sizeof(drv->name), "ir-lirc-codec (%s)", ir_dev->driver_name); drv->minor = -1; diff --git a/include/media/ir-core.h b/include/media/ir-core.h index a781045aeed..eb7fddf8f60 100644 --- a/include/media/ir-core.h +++ b/include/media/ir-core.h @@ -44,6 +44,8 @@ enum rc_driver_type { * @timeout: optional time after which device stops sending data * @min_timeout: minimum timeout supported by device * @max_timeout: maximum timeout supported by device + * @rx_resolution : resolution (in ns) of input sampler + * @tx_resolution: resolution (in ns) of output sampler * @priv: driver-specific data, to be used on the callbacks * @change_protocol: allow changing the protocol used on hardware decoders * @open: callback to allow drivers to enable polling/irq when IR input device @@ -52,9 +54,12 @@ enum rc_driver_type { * is opened. * @s_tx_mask: set transmitter mask (for devices with multiple tx outputs) * @s_tx_carrier: set transmit carrier frequency + * @s_tx_duty_cycle: set transmit duty cycle (0% - 100%) + * @s_rx_carrier: inform driver about carrier it is expected to handle * @tx_ir: transmit IR * @s_idle: optional: enable/disable hardware idle mode, upon which, - * device doesn't interrupt host untill it sees IR data + device doesn't interrupt host until it sees IR pulses + * @s_learning_mode: enable wide band receiver used for learning */ struct ir_dev_props { enum rc_driver_type driver_type; @@ -65,6 +70,8 @@ struct ir_dev_props { u32 min_timeout; u32 max_timeout; + u32 rx_resolution; + u32 tx_resolution; void *priv; int (*change_protocol)(void *priv, u64 ir_type); @@ -72,8 +79,11 @@ struct ir_dev_props { void (*close)(void *priv); int (*s_tx_mask)(void *priv, u32 mask); int (*s_tx_carrier)(void *priv, u32 carrier); + int (*s_tx_duty_cycle)(void *priv, u32 duty_cycle); + int (*s_rx_carrier_range)(void *priv, u32 min, u32 max); int (*tx_ir)(void *priv, int *txbuf, u32 n); void (*s_idle)(void *priv, int enable); + int (*s_learning_mode)(void *priv, int enable); }; struct ir_input_dev { diff --git a/include/media/lirc.h b/include/media/lirc.h index 42c467c5051..6678a169fd9 100644 --- a/include/media/lirc.h +++ b/include/media/lirc.h @@ -77,6 +77,7 @@ #define LIRC_CAN_SET_REC_FILTER 0x08000000 #define LIRC_CAN_MEASURE_CARRIER 0x02000000 +#define LIRC_CAN_USE_WIDEBAND_RECEIVER 0x04000000 #define LIRC_CAN_SEND(x) ((x)&LIRC_CAN_SEND_MASK) #define LIRC_CAN_REC(x) ((x)&LIRC_CAN_REC_MASK) @@ -145,7 +146,7 @@ * if enabled from the next key press on the driver will send * LIRC_MODE2_FREQUENCY packets */ -#define LIRC_SET_MEASURE_CARRIER_MODE _IOW('i', 0x0000001d, __u32) +#define LIRC_SET_MEASURE_CARRIER_MODE _IOW('i', 0x0000001d, __u32) /* * to set a range use @@ -162,4 +163,6 @@ #define LIRC_SETUP_START _IO('i', 0x00000021) #define LIRC_SETUP_END _IO('i', 0x00000022) +#define LIRC_SET_WIDEBAND_RECEIVER _IOW('i', 0x00000023, __u32) + #endif -- cgit v1.2.3-70-g09d2 From 5fd8f7388c9a8601c2dbe0da458df602fe427e83 Mon Sep 17 00:00:00 2001 From: Sylwester Nawrocki Date: Tue, 3 Aug 2010 09:50:29 -0300 Subject: V4L/DVB: v4l: Add driver for Samsung S5P SoC video postprocessor This driver exports a video device node per each camera interface/ video postprocessor (FIMC) device contained in Samsung S5P SoC series. The driver is based on v4l2-mem2mem framework. Signed-off-by: Sylwester Nawrocki Signed-off-by: Kyungmin Park Signed-off-by: Pawel Osciak Signed-off-by: Mauro Carvalho Chehab --- Documentation/DocBook/v4l/pixfmt-packed-rgb.xml | 78 ++ drivers/media/video/Kconfig | 9 + drivers/media/video/Makefile | 1 + drivers/media/video/s5p-fimc/Makefile | 3 + drivers/media/video/s5p-fimc/fimc-core.c | 1570 +++++++++++++++++++++++ drivers/media/video/s5p-fimc/fimc-core.h | 465 +++++++ drivers/media/video/s5p-fimc/fimc-reg.c | 527 ++++++++ drivers/media/video/s5p-fimc/regs-fimc.h | 293 +++++ include/linux/videodev2.h | 1 + 9 files changed, 2947 insertions(+) create mode 100644 drivers/media/video/s5p-fimc/Makefile create mode 100644 drivers/media/video/s5p-fimc/fimc-core.c create mode 100644 drivers/media/video/s5p-fimc/fimc-core.h create mode 100644 drivers/media/video/s5p-fimc/fimc-reg.c create mode 100644 drivers/media/video/s5p-fimc/regs-fimc.h (limited to 'include') diff --git a/Documentation/DocBook/v4l/pixfmt-packed-rgb.xml b/Documentation/DocBook/v4l/pixfmt-packed-rgb.xml index d2dd697a81d..26e87923108 100644 --- a/Documentation/DocBook/v4l/pixfmt-packed-rgb.xml +++ b/Documentation/DocBook/v4l/pixfmt-packed-rgb.xml @@ -240,6 +240,45 @@ colorspace V4L2_COLORSPACE_SRGB. r1 r0 + + V4L2_PIX_FMT_BGR666 + 'BGRH' + + b5 + b4 + b3 + b2 + b1 + b0 + g5 + g4 + + g3 + g2 + g1 + g0 + r5 + r4 + r3 + r2 + + r1 + r0 + + + + + + + + + + + + + + + V4L2_PIX_FMT_BGR24 'BGR3' @@ -700,6 +739,45 @@ defined in error. Drivers may interpret them as in b1 b0 + + V4L2_PIX_FMT_BGR666 + 'BGRH' + + b5 + b4 + b3 + b2 + b1 + b0 + g5 + g4 + + g3 + g2 + g1 + g0 + r5 + r4 + r3 + r2 + + r1 + r0 + + + + + + + + + + + + + + + V4L2_PIX_FMT_BGR24 'BGR3' diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 2e15903b976..c70b67deb9b 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -1016,4 +1016,13 @@ config VIDEO_MEM2MEM_TESTDEV This is a virtual test device for the memory-to-memory driver framework. +config VIDEO_SAMSUNG_S5P_FIMC + tristate "Samsung S5P FIMC (video postprocessor) driver" + depends on VIDEO_DEV && VIDEO_V4L2 && PLAT_S5P + select VIDEOBUF_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + This is a v4l2 driver for the S5P camera interface + (video postprocessor) + endif # V4L_MEM2MEM_DRIVERS diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 1051ecc602e..c76fef39079 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -163,6 +163,7 @@ obj-$(CONFIG_VIDEO_MX3) += mx3_camera.o obj-$(CONFIG_VIDEO_PXA27x) += pxa_camera.o obj-$(CONFIG_VIDEO_SH_MOBILE_CSI2) += sh_mobile_csi2.o obj-$(CONFIG_VIDEO_SH_MOBILE_CEU) += sh_mobile_ceu_camera.o +obj-$(CONFIG_VIDEO_SAMSUNG_S5P_FIMC) += s5p-fimc/ obj-$(CONFIG_ARCH_DAVINCI) += davinci/ diff --git a/drivers/media/video/s5p-fimc/Makefile b/drivers/media/video/s5p-fimc/Makefile new file mode 100644 index 00000000000..0d9d54132ec --- /dev/null +++ b/drivers/media/video/s5p-fimc/Makefile @@ -0,0 +1,3 @@ + +obj-$(CONFIG_VIDEO_SAMSUNG_S5P_FIMC) := s5p-fimc.o +s5p-fimc-y := fimc-core.o fimc-reg.o diff --git a/drivers/media/video/s5p-fimc/fimc-core.c b/drivers/media/video/s5p-fimc/fimc-core.c new file mode 100644 index 00000000000..6558a2ea9ff --- /dev/null +++ b/drivers/media/video/s5p-fimc/fimc-core.c @@ -0,0 +1,1570 @@ +/* + * S5P camera interface (video postprocessor) driver + * + * Copyright (c) 2010 Samsung Electronics + * + * Sylwester Nawrocki, + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fimc-core.h" + +static char *fimc_clock_name[NUM_FIMC_CLOCKS] = { "sclk_fimc", "fimc" }; + +static struct fimc_fmt fimc_formats[] = { + { + .name = "RGB565", + .fourcc = V4L2_PIX_FMT_RGB565X, + .depth = 16, + .color = S5P_FIMC_RGB565, + .buff_cnt = 1, + .planes_cnt = 1 + }, { + .name = "BGR666", + .fourcc = V4L2_PIX_FMT_BGR666, + .depth = 32, + .color = S5P_FIMC_RGB666, + .buff_cnt = 1, + .planes_cnt = 1 + }, { + .name = "XRGB-8-8-8-8, 24 bpp", + .fourcc = V4L2_PIX_FMT_RGB24, + .depth = 32, + .color = S5P_FIMC_RGB888, + .buff_cnt = 1, + .planes_cnt = 1 + }, { + .name = "YUV 4:2:2 packed, YCbYCr", + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .color = S5P_FIMC_YCBYCR422, + .buff_cnt = 1, + .planes_cnt = 1 + }, { + .name = "YUV 4:2:2 packed, CbYCrY", + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + .color = S5P_FIMC_CBYCRY422, + .buff_cnt = 1, + .planes_cnt = 1 + }, { + .name = "YUV 4:2:2 packed, CrYCbY", + .fourcc = V4L2_PIX_FMT_VYUY, + .depth = 16, + .color = S5P_FIMC_CRYCBY422, + .buff_cnt = 1, + .planes_cnt = 1 + }, { + .name = "YUV 4:2:2 packed, YCrYCb", + .fourcc = V4L2_PIX_FMT_YVYU, + .depth = 16, + .color = S5P_FIMC_YCRYCB422, + .buff_cnt = 1, + .planes_cnt = 1 + }, { + .name = "YUV 4:2:2 planar, Y/Cb/Cr", + .fourcc = V4L2_PIX_FMT_YUV422P, + .depth = 12, + .color = S5P_FIMC_YCBCR422, + .buff_cnt = 1, + .planes_cnt = 3 + }, { + .name = "YUV 4:2:2 planar, Y/CbCr", + .fourcc = V4L2_PIX_FMT_NV16, + .depth = 16, + .color = S5P_FIMC_YCBCR422, + .buff_cnt = 1, + .planes_cnt = 2 + }, { + .name = "YUV 4:2:2 planar, Y/CrCb", + .fourcc = V4L2_PIX_FMT_NV61, + .depth = 16, + .color = S5P_FIMC_RGB565, + .buff_cnt = 1, + .planes_cnt = 2 + }, { + .name = "YUV 4:2:0 planar, YCbCr", + .fourcc = V4L2_PIX_FMT_YUV420, + .depth = 12, + .color = S5P_FIMC_YCBCR420, + .buff_cnt = 1, + .planes_cnt = 3 + }, { + .name = "YUV 4:2:0 planar, Y/CbCr", + .fourcc = V4L2_PIX_FMT_NV12, + .depth = 12, + .color = S5P_FIMC_YCBCR420, + .buff_cnt = 1, + .planes_cnt = 2 + } + }; + +static struct v4l2_queryctrl fimc_ctrls[] = { + { + .id = V4L2_CID_HFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Horizontal flip", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Vertical flip", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_ROTATE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Rotation (CCW)", + .minimum = 0, + .maximum = 270, + .step = 90, + .default_value = 0, + }, +}; + + +static struct v4l2_queryctrl *get_ctrl(int id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(fimc_ctrls); ++i) + if (id == fimc_ctrls[i].id) + return &fimc_ctrls[i]; + return NULL; +} + +static int fimc_check_scaler_ratio(struct v4l2_rect *r, struct fimc_frame *f) +{ + if (r->width > f->width) { + if (f->width > (r->width * SCALER_MAX_HRATIO)) + return -EINVAL; + } else { + if ((f->width * SCALER_MAX_HRATIO) < r->width) + return -EINVAL; + } + + if (r->height > f->height) { + if (f->height > (r->height * SCALER_MAX_VRATIO)) + return -EINVAL; + } else { + if ((f->height * SCALER_MAX_VRATIO) < r->height) + return -EINVAL; + } + + return 0; +} + +static int fimc_get_scaler_factor(u32 src, u32 tar, u32 *ratio, u32 *shift) +{ + if (src >= tar * 64) { + return -EINVAL; + } else if (src >= tar * 32) { + *ratio = 32; + *shift = 5; + } else if (src >= tar * 16) { + *ratio = 16; + *shift = 4; + } else if (src >= tar * 8) { + *ratio = 8; + *shift = 3; + } else if (src >= tar * 4) { + *ratio = 4; + *shift = 2; + } else if (src >= tar * 2) { + *ratio = 2; + *shift = 1; + } else { + *ratio = 1; + *shift = 0; + } + + return 0; +} + +static int fimc_set_scaler_info(struct fimc_ctx *ctx) +{ + struct fimc_scaler *sc = &ctx->scaler; + struct fimc_frame *s_frame = &ctx->s_frame; + struct fimc_frame *d_frame = &ctx->d_frame; + int tx, ty, sx, sy; + int ret; + + tx = d_frame->width; + ty = d_frame->height; + if (tx <= 0 || ty <= 0) { + v4l2_err(&ctx->fimc_dev->m2m.v4l2_dev, + "invalid target size: %d x %d", tx, ty); + return -EINVAL; + } + + sx = s_frame->width; + sy = s_frame->height; + if (sx <= 0 || sy <= 0) { + err("invalid source size: %d x %d", sx, sy); + return -EINVAL; + } + + sc->real_width = sx; + sc->real_height = sy; + dbg("sx= %d, sy= %d, tx= %d, ty= %d", sx, sy, tx, ty); + + ret = fimc_get_scaler_factor(sx, tx, &sc->pre_hratio, &sc->hfactor); + if (ret) + return ret; + + ret = fimc_get_scaler_factor(sy, ty, &sc->pre_vratio, &sc->vfactor); + if (ret) + return ret; + + sc->pre_dst_width = sx / sc->pre_hratio; + sc->pre_dst_height = sy / sc->pre_vratio; + + sc->main_hratio = (sx << 8) / (tx << sc->hfactor); + sc->main_vratio = (sy << 8) / (ty << sc->vfactor); + + sc->scaleup_h = (tx >= sx) ? 1 : 0; + sc->scaleup_v = (ty >= sy) ? 1 : 0; + + /* check to see if input and output size/format differ */ + if (s_frame->fmt->color == d_frame->fmt->color + && s_frame->width == d_frame->width + && s_frame->height == d_frame->height) + sc->copy_mode = 1; + else + sc->copy_mode = 0; + + return 0; +} + + +static irqreturn_t fimc_isr(int irq, void *priv) +{ + struct fimc_vid_buffer *src_buf, *dst_buf; + struct fimc_dev *fimc = (struct fimc_dev *)priv; + struct fimc_ctx *ctx; + + BUG_ON(!fimc); + fimc_hw_clear_irq(fimc); + + spin_lock(&fimc->slock); + + if (test_and_clear_bit(ST_M2M_PEND, &fimc->state)) { + ctx = v4l2_m2m_get_curr_priv(fimc->m2m.m2m_dev); + if (!ctx || !ctx->m2m_ctx) + goto isr_unlock; + src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); + dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); + if (src_buf && dst_buf) { + spin_lock(&fimc->irqlock); + src_buf->vb.state = dst_buf->vb.state = VIDEOBUF_DONE; + wake_up(&src_buf->vb.done); + wake_up(&dst_buf->vb.done); + spin_unlock(&fimc->irqlock); + v4l2_m2m_job_finish(fimc->m2m.m2m_dev, ctx->m2m_ctx); + } + } + +isr_unlock: + spin_unlock(&fimc->slock); + return IRQ_HANDLED; +} + +/* The color format (planes_cnt, buff_cnt) must be already configured. */ +static int fimc_prepare_addr(struct fimc_ctx *ctx, + struct fimc_vid_buffer *buf, enum v4l2_buf_type type) +{ + struct fimc_frame *frame; + struct fimc_addr *paddr; + u32 pix_size; + int ret = 0; + + ctx_m2m_get_frame(frame, ctx, type); + paddr = &frame->paddr; + + if (!buf) + return -EINVAL; + + pix_size = frame->width * frame->height; + + dbg("buff_cnt= %d, planes_cnt= %d, frame->size= %d, pix_size= %d", + frame->fmt->buff_cnt, frame->fmt->planes_cnt, + frame->size, pix_size); + + if (frame->fmt->buff_cnt == 1) { + paddr->y = videobuf_to_dma_contig(&buf->vb); + switch (frame->fmt->planes_cnt) { + case 1: + paddr->cb = 0; + paddr->cr = 0; + break; + case 2: + /* decompose Y into Y/Cb */ + paddr->cb = (u32)(paddr->y + pix_size); + paddr->cr = 0; + break; + case 3: + paddr->cb = (u32)(paddr->y + pix_size); + /* decompose Y into Y/Cb/Cr */ + if (S5P_FIMC_YCBCR420 == frame->fmt->color) + paddr->cr = (u32)(paddr->cb + + (pix_size >> 2)); + else /* 422 */ + paddr->cr = (u32)(paddr->cb + + (pix_size >> 1)); + break; + default: + return -EINVAL; + } + } + + dbg("PHYS_ADDR: type= %d, y= 0x%X cb= 0x%X cr= 0x%X ret= %d", + type, paddr->y, paddr->cb, paddr->cr, ret); + + return ret; +} + +/* Set order for 1 and 2 plane YCBCR 4:2:2 formats. */ +static void fimc_set_yuv_order(struct fimc_ctx *ctx) +{ + /* The one only mode supported in SoC. */ + ctx->in_order_2p = S5P_FIMC_LSB_CRCB; + ctx->out_order_2p = S5P_FIMC_LSB_CRCB; + + /* Set order for 1 plane input formats. */ + switch (ctx->s_frame.fmt->color) { + case S5P_FIMC_YCRYCB422: + ctx->in_order_1p = S5P_FIMC_IN_YCRYCB; + break; + case S5P_FIMC_CBYCRY422: + ctx->in_order_1p = S5P_FIMC_IN_CBYCRY; + break; + case S5P_FIMC_CRYCBY422: + ctx->in_order_1p = S5P_FIMC_IN_CRYCBY; + break; + case S5P_FIMC_YCBYCR422: + default: + ctx->in_order_1p = S5P_FIMC_IN_YCBYCR; + break; + } + dbg("ctx->in_order_1p= %d", ctx->in_order_1p); + + switch (ctx->d_frame.fmt->color) { + case S5P_FIMC_YCRYCB422: + ctx->out_order_1p = S5P_FIMC_OUT_YCRYCB; + break; + case S5P_FIMC_CBYCRY422: + ctx->out_order_1p = S5P_FIMC_OUT_CBYCRY; + break; + case S5P_FIMC_CRYCBY422: + ctx->out_order_1p = S5P_FIMC_OUT_CRYCBY; + break; + case S5P_FIMC_YCBYCR422: + default: + ctx->out_order_1p = S5P_FIMC_OUT_YCBYCR; + break; + } + dbg("ctx->out_order_1p= %d", ctx->out_order_1p); +} + +/** + * fimc_prepare_config - check dimensions, operation and color mode + * and pre-calculate offset and the scaling coefficients. + * + * @ctx: hardware context information + * @flags: flags indicating which parameters to check/update + * + * Return: 0 if dimensions are valid or non zero otherwise. + */ +static int fimc_prepare_config(struct fimc_ctx *ctx, u32 flags) +{ + struct fimc_frame *s_frame, *d_frame; + struct fimc_vid_buffer *buf = NULL; + struct samsung_fimc_variant *variant = ctx->fimc_dev->variant; + int ret = 0; + + s_frame = &ctx->s_frame; + d_frame = &ctx->d_frame; + + if (flags & FIMC_PARAMS) { + if ((ctx->out_path == FIMC_DMA) && + (ctx->rotation == 90 || ctx->rotation == 270)) { + swap(d_frame->f_width, d_frame->f_height); + swap(d_frame->width, d_frame->height); + } + + /* Prepare the output offset ratios for scaler. */ + d_frame->dma_offset.y_h = d_frame->offs_h; + if (!variant->pix_hoff) + d_frame->dma_offset.y_h *= (d_frame->fmt->depth >> 3); + + d_frame->dma_offset.y_v = d_frame->offs_v; + + d_frame->dma_offset.cb_h = d_frame->offs_h; + d_frame->dma_offset.cb_v = d_frame->offs_v; + + d_frame->dma_offset.cr_h = d_frame->offs_h; + d_frame->dma_offset.cr_v = d_frame->offs_v; + + if (!variant->pix_hoff && d_frame->fmt->planes_cnt == 3) { + d_frame->dma_offset.cb_h >>= 1; + d_frame->dma_offset.cb_v >>= 1; + d_frame->dma_offset.cr_h >>= 1; + d_frame->dma_offset.cr_v >>= 1; + } + + dbg("out offset: color= %d, y_h= %d, y_v= %d", + d_frame->fmt->color, + d_frame->dma_offset.y_h, d_frame->dma_offset.y_v); + + /* Prepare the input offset ratios for scaler. */ + s_frame->dma_offset.y_h = s_frame->offs_h; + if (!variant->pix_hoff) + s_frame->dma_offset.y_h *= (s_frame->fmt->depth >> 3); + s_frame->dma_offset.y_v = s_frame->offs_v; + + s_frame->dma_offset.cb_h = s_frame->offs_h; + s_frame->dma_offset.cb_v = s_frame->offs_v; + + s_frame->dma_offset.cr_h = s_frame->offs_h; + s_frame->dma_offset.cr_v = s_frame->offs_v; + + if (!variant->pix_hoff && s_frame->fmt->planes_cnt == 3) { + s_frame->dma_offset.cb_h >>= 1; + s_frame->dma_offset.cb_v >>= 1; + s_frame->dma_offset.cr_h >>= 1; + s_frame->dma_offset.cr_v >>= 1; + } + + dbg("in offset: color= %d, y_h= %d, y_v= %d", + s_frame->fmt->color, s_frame->dma_offset.y_h, + s_frame->dma_offset.y_v); + + fimc_set_yuv_order(ctx); + + /* Check against the scaler ratio. */ + if (s_frame->height > (SCALER_MAX_VRATIO * d_frame->height) || + s_frame->width > (SCALER_MAX_HRATIO * d_frame->width)) { + err("out of scaler range"); + return -EINVAL; + } + } + + /* Input DMA mode is not allowed when the scaler is disabled. */ + ctx->scaler.enabled = 1; + + if (flags & FIMC_SRC_ADDR) { + buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); + ret = fimc_prepare_addr(ctx, buf, + V4L2_BUF_TYPE_VIDEO_OUTPUT); + if (ret) + return ret; + } + + if (flags & FIMC_DST_ADDR) { + buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + ret = fimc_prepare_addr(ctx, buf, + V4L2_BUF_TYPE_VIDEO_CAPTURE); + } + + return ret; +} + +static void fimc_dma_run(void *priv) +{ + struct fimc_ctx *ctx = priv; + struct fimc_dev *fimc; + unsigned long flags; + u32 ret; + + if (WARN(!ctx, "null hardware context")) + return; + + fimc = ctx->fimc_dev; + + spin_lock_irqsave(&ctx->slock, flags); + set_bit(ST_M2M_PEND, &fimc->state); + + ctx->state |= (FIMC_SRC_ADDR | FIMC_DST_ADDR); + ret = fimc_prepare_config(ctx, ctx->state); + if (ret) { + err("general configuration error"); + goto dma_unlock; + } + + if (fimc->m2m.ctx != ctx) + ctx->state |= FIMC_PARAMS; + + fimc_hw_set_input_addr(fimc, &ctx->s_frame.paddr); + + if (ctx->state & FIMC_PARAMS) { + fimc_hw_set_input_path(ctx); + fimc_hw_set_in_dma(ctx); + if (fimc_set_scaler_info(ctx)) { + err("scaler configuration error"); + goto dma_unlock; + } + fimc_hw_set_prescaler(ctx); + fimc_hw_set_scaler(ctx); + fimc_hw_set_target_format(ctx); + fimc_hw_set_rotation(ctx); + fimc_hw_set_effect(ctx); + } + + fimc_hw_set_output_path(ctx); + if (ctx->state & (FIMC_DST_ADDR | FIMC_PARAMS)) + fimc_hw_set_output_addr(fimc, &ctx->d_frame.paddr); + + if (ctx->state & FIMC_PARAMS) + fimc_hw_set_out_dma(ctx); + + if (ctx->scaler.enabled) + fimc_hw_start_scaler(fimc); + fimc_hw_en_capture(ctx); + + ctx->state = 0; + fimc_hw_start_in_dma(fimc); + + fimc->m2m.ctx = ctx; + +dma_unlock: + spin_unlock_irqrestore(&ctx->slock, flags); +} + +/* Nothing done in job_abort. */ +static void fimc_job_abort(void *priv) {} + +static void fimc_buf_release(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + videobuf_dma_contig_free(vq, vb); + vb->state = VIDEOBUF_NEEDS_INIT; +} + +static int fimc_buf_setup(struct videobuf_queue *vq, unsigned int *count, + unsigned int *size) +{ + struct fimc_ctx *ctx = vq->priv_data; + struct fimc_frame *frame; + + ctx_m2m_get_frame(frame, ctx, vq->type); + + *size = (frame->width * frame->height * frame->fmt->depth) >> 3; + if (0 == *count) + *count = 1; + return 0; +} + +static int fimc_buf_prepare(struct videobuf_queue *vq, + struct videobuf_buffer *vb, enum v4l2_field field) +{ + struct fimc_ctx *ctx = vq->priv_data; + struct v4l2_device *v4l2_dev = &ctx->fimc_dev->m2m.v4l2_dev; + struct fimc_frame *frame; + int ret; + + ctx_m2m_get_frame(frame, ctx, vq->type); + + if (vb->baddr) { + if (vb->bsize < frame->size) { + v4l2_err(v4l2_dev, + "User-provided buffer too small (%d < %d)\n", + vb->bsize, frame->size); + WARN_ON(1); + return -EINVAL; + } + } else if (vb->state != VIDEOBUF_NEEDS_INIT + && vb->bsize < frame->size) { + return -EINVAL; + } + + vb->width = frame->width; + vb->height = frame->height; + vb->bytesperline = (frame->width * frame->fmt->depth) >> 3; + vb->size = frame->size; + vb->field = field; + + if (VIDEOBUF_NEEDS_INIT == vb->state) { + ret = videobuf_iolock(vq, vb, NULL); + if (ret) { + v4l2_err(v4l2_dev, "Iolock failed\n"); + fimc_buf_release(vq, vb); + return ret; + } + } + vb->state = VIDEOBUF_PREPARED; + + return 0; +} + +static void fimc_buf_queue(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct fimc_ctx *ctx = vq->priv_data; + v4l2_m2m_buf_queue(ctx->m2m_ctx, vq, vb); +} + +struct videobuf_queue_ops fimc_qops = { + .buf_setup = fimc_buf_setup, + .buf_prepare = fimc_buf_prepare, + .buf_queue = fimc_buf_queue, + .buf_release = fimc_buf_release, +}; + +static int fimc_m2m_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct fimc_ctx *ctx = file->private_data; + struct fimc_dev *fimc = ctx->fimc_dev; + + strncpy(cap->driver, fimc->pdev->name, sizeof(cap->driver) - 1); + strncpy(cap->card, fimc->pdev->name, sizeof(cap->card) - 1); + cap->bus_info[0] = 0; + cap->version = KERNEL_VERSION(1, 0, 0); + cap->capabilities = V4L2_CAP_STREAMING | + V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT; + + return 0; +} + +static int fimc_m2m_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct fimc_fmt *fmt; + + if (f->index >= ARRAY_SIZE(fimc_formats)) + return -EINVAL; + + fmt = &fimc_formats[f->index]; + strncpy(f->description, fmt->name, sizeof(f->description) - 1); + f->pixelformat = fmt->fourcc; + return 0; +} + +static int fimc_m2m_g_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct fimc_ctx *ctx = priv; + struct fimc_frame *frame; + + ctx_m2m_get_frame(frame, ctx, f->type); + + f->fmt.pix.width = frame->width; + f->fmt.pix.height = frame->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.pixelformat = frame->fmt->fourcc; + + return 0; +} + +static struct fimc_fmt *find_format(struct v4l2_format *f) +{ + struct fimc_fmt *fmt; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(fimc_formats); ++i) { + fmt = &fimc_formats[i]; + if (fmt->fourcc == f->fmt.pix.pixelformat) + break; + } + if (i == ARRAY_SIZE(fimc_formats)) + return NULL; + + return fmt; +} + +static int fimc_m2m_try_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct fimc_fmt *fmt; + u32 max_width, max_height, mod_x, mod_y; + struct fimc_ctx *ctx = priv; + struct fimc_dev *fimc = ctx->fimc_dev; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct samsung_fimc_variant *variant = fimc->variant; + + fmt = find_format(f); + if (!fmt) { + v4l2_err(&fimc->m2m.v4l2_dev, + "Fourcc format (0x%X) invalid.\n", pix->pixelformat); + return -EINVAL; + } + + if (pix->field == V4L2_FIELD_ANY) + pix->field = V4L2_FIELD_NONE; + else if (V4L2_FIELD_NONE != pix->field) + return -EINVAL; + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + max_width = variant->scaler_dis_w; + max_height = variant->scaler_dis_w; + mod_x = variant->min_inp_pixsize; + mod_y = variant->min_inp_pixsize; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + max_width = variant->out_rot_dis_w; + max_height = variant->out_rot_dis_w; + mod_x = variant->min_out_pixsize; + mod_y = variant->min_out_pixsize; + } else { + err("Wrong stream type (%d)", f->type); + return -EINVAL; + } + + dbg("max_w= %d, max_h= %d", max_width, max_height); + + if (pix->height > max_height) + pix->height = max_height; + if (pix->width > max_width) + pix->width = max_width; + + if (tiled_fmt(fmt)) { + mod_x = 64; /* 64x32 tile */ + mod_y = 32; + } + + dbg("mod_x= 0x%X, mod_y= 0x%X", mod_x, mod_y); + + pix->width = (pix->width == 0) ? mod_x : ALIGN(pix->width, mod_x); + pix->height = (pix->height == 0) ? mod_y : ALIGN(pix->height, mod_y); + + if (pix->bytesperline == 0 || + pix->bytesperline * 8 / fmt->depth > pix->width) + pix->bytesperline = (pix->width * fmt->depth) >> 3; + + if (pix->sizeimage == 0) + pix->sizeimage = pix->height * pix->bytesperline; + + dbg("pix->bytesperline= %d, fmt->depth= %d", + pix->bytesperline, fmt->depth); + + return 0; +} + + +static int fimc_m2m_s_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct fimc_ctx *ctx = priv; + struct v4l2_device *v4l2_dev = &ctx->fimc_dev->m2m.v4l2_dev; + struct videobuf_queue *src_vq, *dst_vq; + struct fimc_frame *frame; + struct v4l2_pix_format *pix; + unsigned long flags; + int ret = 0; + + BUG_ON(!ctx); + + ret = fimc_m2m_try_fmt(file, priv, f); + if (ret) + return ret; + + mutex_lock(&ctx->fimc_dev->lock); + + src_vq = v4l2_m2m_get_src_vq(ctx->m2m_ctx); + dst_vq = v4l2_m2m_get_dst_vq(ctx->m2m_ctx); + + mutex_lock(&src_vq->vb_lock); + mutex_lock(&dst_vq->vb_lock); + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + if (videobuf_queue_is_busy(src_vq)) { + v4l2_err(v4l2_dev, "%s queue busy\n", __func__); + ret = -EBUSY; + goto s_fmt_out; + } + frame = &ctx->s_frame; + spin_lock_irqsave(&ctx->slock, flags); + ctx->state |= FIMC_SRC_FMT; + spin_unlock_irqrestore(&ctx->slock, flags); + + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + if (videobuf_queue_is_busy(dst_vq)) { + v4l2_err(v4l2_dev, "%s queue busy\n", __func__); + ret = -EBUSY; + goto s_fmt_out; + } + frame = &ctx->d_frame; + spin_lock_irqsave(&ctx->slock, flags); + ctx->state |= FIMC_DST_FMT; + spin_unlock_irqrestore(&ctx->slock, flags); + } else { + v4l2_err(&ctx->fimc_dev->m2m.v4l2_dev, + "Wrong buffer/video queue type (%d)\n", f->type); + return -EINVAL; + } + + pix = &f->fmt.pix; + frame->fmt = find_format(f); + if (!frame->fmt) { + ret = -EINVAL; + goto s_fmt_out; + } + + frame->f_width = pix->bytesperline * 8 / frame->fmt->depth; + frame->f_height = pix->sizeimage/pix->bytesperline; + frame->width = pix->width; + frame->height = pix->height; + frame->o_width = pix->width; + frame->o_height = pix->height; + frame->offs_h = 0; + frame->offs_v = 0; + frame->size = (pix->width * pix->height * frame->fmt->depth) >> 3; + src_vq->field = dst_vq->field = pix->field; + spin_lock_irqsave(&ctx->slock, flags); + ctx->state |= FIMC_PARAMS; + spin_unlock_irqrestore(&ctx->slock, flags); + + dbg("f_width= %d, f_height= %d", frame->f_width, frame->f_height); + +s_fmt_out: + mutex_unlock(&dst_vq->vb_lock); + mutex_unlock(&src_vq->vb_lock); + mutex_unlock(&ctx->fimc_dev->lock); + return ret; +} + +static int fimc_m2m_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *reqbufs) +{ + struct fimc_ctx *ctx = priv; + return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs); +} + +static int fimc_m2m_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct fimc_ctx *ctx = priv; + return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf); +} + +static int fimc_m2m_qbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct fimc_ctx *ctx = priv; + + return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); +} + +static int fimc_m2m_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct fimc_ctx *ctx = priv; + return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); +} + +static int fimc_m2m_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct fimc_ctx *ctx = priv; + return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); +} + +static int fimc_m2m_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct fimc_ctx *ctx = priv; + return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); +} + +int fimc_m2m_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + struct v4l2_queryctrl *c; + c = get_ctrl(qc->id); + if (!c) + return -EINVAL; + *qc = *c; + return 0; +} + +int fimc_m2m_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct fimc_ctx *ctx = priv; + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + ctrl->value = (FLIP_X_AXIS & ctx->flip) ? 1 : 0; + break; + case V4L2_CID_VFLIP: + ctrl->value = (FLIP_Y_AXIS & ctx->flip) ? 1 : 0; + break; + case V4L2_CID_ROTATE: + ctrl->value = ctx->rotation; + break; + default: + v4l2_err(&ctx->fimc_dev->m2m.v4l2_dev, "Invalid control\n"); + return -EINVAL; + } + dbg("ctrl->value= %d", ctrl->value); + return 0; +} + +static int check_ctrl_val(struct fimc_ctx *ctx, + struct v4l2_control *ctrl) +{ + struct v4l2_queryctrl *c; + c = get_ctrl(ctrl->id); + if (!c) + return -EINVAL; + + if (ctrl->value < c->minimum || ctrl->value > c->maximum + || (c->step != 0 && ctrl->value % c->step != 0)) { + v4l2_err(&ctx->fimc_dev->m2m.v4l2_dev, + "Invalid control value\n"); + return -ERANGE; + } + + return 0; +} + +int fimc_m2m_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct fimc_ctx *ctx = priv; + struct samsung_fimc_variant *variant = ctx->fimc_dev->variant; + unsigned long flags; + int ret = 0; + + ret = check_ctrl_val(ctx, ctrl); + if (ret) + return ret; + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + if (ctx->rotation != 0) + return 0; + if (ctrl->value) + ctx->flip |= FLIP_X_AXIS; + else + ctx->flip &= ~FLIP_X_AXIS; + break; + + case V4L2_CID_VFLIP: + if (ctx->rotation != 0) + return 0; + if (ctrl->value) + ctx->flip |= FLIP_Y_AXIS; + else + ctx->flip &= ~FLIP_Y_AXIS; + break; + + case V4L2_CID_ROTATE: + if (ctrl->value == 90 || ctrl->value == 270) { + if (ctx->out_path == FIMC_LCDFIFO && + !variant->has_inp_rot) { + return -EINVAL; + } else if (ctx->in_path == FIMC_DMA && + !variant->has_out_rot) { + return -EINVAL; + } + } + ctx->rotation = ctrl->value; + if (ctrl->value == 180) + ctx->flip = FLIP_XY_AXIS; + break; + + default: + v4l2_err(&ctx->fimc_dev->m2m.v4l2_dev, "Invalid control\n"); + return -EINVAL; + } + spin_lock_irqsave(&ctx->slock, flags); + ctx->state |= FIMC_PARAMS; + spin_unlock_irqrestore(&ctx->slock, flags); + return 0; +} + + +static int fimc_m2m_cropcap(struct file *file, void *fh, + struct v4l2_cropcap *cr) +{ + struct fimc_frame *frame; + struct fimc_ctx *ctx = fh; + + ctx_m2m_get_frame(frame, ctx, cr->type); + + cr->bounds.left = 0; + cr->bounds.top = 0; + cr->bounds.width = frame->f_width; + cr->bounds.height = frame->f_height; + cr->defrect.left = frame->offs_h; + cr->defrect.top = frame->offs_v; + cr->defrect.width = frame->o_width; + cr->defrect.height = frame->o_height; + return 0; +} + +static int fimc_m2m_g_crop(struct file *file, void *fh, struct v4l2_crop *cr) +{ + struct fimc_frame *frame; + struct fimc_ctx *ctx = file->private_data; + + ctx_m2m_get_frame(frame, ctx, cr->type); + + cr->c.left = frame->offs_h; + cr->c.top = frame->offs_v; + cr->c.width = frame->width; + cr->c.height = frame->height; + + return 0; +} + +static int fimc_m2m_s_crop(struct file *file, void *fh, struct v4l2_crop *cr) +{ + struct fimc_ctx *ctx = file->private_data; + struct fimc_dev *fimc = ctx->fimc_dev; + unsigned long flags; + struct fimc_frame *f; + u32 min_size; + int ret = 0; + + if (cr->c.top < 0 || cr->c.left < 0) { + v4l2_err(&fimc->m2m.v4l2_dev, + "doesn't support negative values for top & left\n"); + return -EINVAL; + } + + if (cr->c.width <= 0 || cr->c.height <= 0) { + v4l2_err(&fimc->m2m.v4l2_dev, + "crop width and height must be greater than 0\n"); + return -EINVAL; + } + + ctx_m2m_get_frame(f, ctx, cr->type); + + /* Adjust to required pixel boundary. */ + min_size = (cr->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) ? + fimc->variant->min_inp_pixsize : fimc->variant->min_out_pixsize; + + cr->c.width = round_down(cr->c.width, min_size); + cr->c.height = round_down(cr->c.height, min_size); + cr->c.left = round_down(cr->c.left + 1, min_size); + cr->c.top = round_down(cr->c.top + 1, min_size); + + if ((cr->c.left + cr->c.width > f->o_width) + || (cr->c.top + cr->c.height > f->o_height)) { + v4l2_err(&fimc->m2m.v4l2_dev, "Error in S_CROP params\n"); + return -EINVAL; + } + + spin_lock_irqsave(&ctx->slock, flags); + if ((ctx->state & FIMC_SRC_FMT) && (ctx->state & FIMC_DST_FMT)) { + /* Check for the pixel scaling ratio when cropping input img. */ + if (cr->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + ret = fimc_check_scaler_ratio(&cr->c, &ctx->d_frame); + else if (cr->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + ret = fimc_check_scaler_ratio(&cr->c, &ctx->s_frame); + + if (ret) { + spin_unlock_irqrestore(&ctx->slock, flags); + v4l2_err(&fimc->m2m.v4l2_dev, "Out of scaler range"); + return -EINVAL; + } + } + ctx->state |= FIMC_PARAMS; + spin_unlock_irqrestore(&ctx->slock, flags); + + f->offs_h = cr->c.left; + f->offs_v = cr->c.top; + f->width = cr->c.width; + f->height = cr->c.height; + return 0; +} + +static const struct v4l2_ioctl_ops fimc_m2m_ioctl_ops = { + .vidioc_querycap = fimc_m2m_querycap, + + .vidioc_enum_fmt_vid_cap = fimc_m2m_enum_fmt, + .vidioc_enum_fmt_vid_out = fimc_m2m_enum_fmt, + + .vidioc_g_fmt_vid_cap = fimc_m2m_g_fmt, + .vidioc_g_fmt_vid_out = fimc_m2m_g_fmt, + + .vidioc_try_fmt_vid_cap = fimc_m2m_try_fmt, + .vidioc_try_fmt_vid_out = fimc_m2m_try_fmt, + + .vidioc_s_fmt_vid_cap = fimc_m2m_s_fmt, + .vidioc_s_fmt_vid_out = fimc_m2m_s_fmt, + + .vidioc_reqbufs = fimc_m2m_reqbufs, + .vidioc_querybuf = fimc_m2m_querybuf, + + .vidioc_qbuf = fimc_m2m_qbuf, + .vidioc_dqbuf = fimc_m2m_dqbuf, + + .vidioc_streamon = fimc_m2m_streamon, + .vidioc_streamoff = fimc_m2m_streamoff, + + .vidioc_queryctrl = fimc_m2m_queryctrl, + .vidioc_g_ctrl = fimc_m2m_g_ctrl, + .vidioc_s_ctrl = fimc_m2m_s_ctrl, + + .vidioc_g_crop = fimc_m2m_g_crop, + .vidioc_s_crop = fimc_m2m_s_crop, + .vidioc_cropcap = fimc_m2m_cropcap + +}; + +static void queue_init(void *priv, struct videobuf_queue *vq, + enum v4l2_buf_type type) +{ + struct fimc_ctx *ctx = priv; + struct fimc_dev *fimc = ctx->fimc_dev; + + videobuf_queue_dma_contig_init(vq, &fimc_qops, + fimc->m2m.v4l2_dev.dev, + &fimc->irqlock, type, V4L2_FIELD_NONE, + sizeof(struct fimc_vid_buffer), priv); +} + +static int fimc_m2m_open(struct file *file) +{ + struct fimc_dev *fimc = video_drvdata(file); + struct fimc_ctx *ctx = NULL; + int err = 0; + + mutex_lock(&fimc->lock); + fimc->m2m.refcnt++; + set_bit(ST_OUTDMA_RUN, &fimc->state); + mutex_unlock(&fimc->lock); + + + ctx = kzalloc(sizeof *ctx, GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + file->private_data = ctx; + ctx->fimc_dev = fimc; + /* default format */ + ctx->s_frame.fmt = &fimc_formats[0]; + ctx->d_frame.fmt = &fimc_formats[0]; + /* per user process device context initialization */ + ctx->state = 0; + ctx->flags = 0; + ctx->effect.type = S5P_FIMC_EFFECT_ORIGINAL; + ctx->in_path = FIMC_DMA; + ctx->out_path = FIMC_DMA; + spin_lock_init(&ctx->slock); + + ctx->m2m_ctx = v4l2_m2m_ctx_init(ctx, fimc->m2m.m2m_dev, queue_init); + if (IS_ERR(ctx->m2m_ctx)) { + err = PTR_ERR(ctx->m2m_ctx); + kfree(ctx); + } + return err; +} + +static int fimc_m2m_release(struct file *file) +{ + struct fimc_ctx *ctx = file->private_data; + struct fimc_dev *fimc = ctx->fimc_dev; + + v4l2_m2m_ctx_release(ctx->m2m_ctx); + kfree(ctx); + mutex_lock(&fimc->lock); + if (--fimc->m2m.refcnt <= 0) + clear_bit(ST_OUTDMA_RUN, &fimc->state); + mutex_unlock(&fimc->lock); + return 0; +} + +static unsigned int fimc_m2m_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct fimc_ctx *ctx = file->private_data; + return v4l2_m2m_poll(file, ctx->m2m_ctx, wait); +} + + +static int fimc_m2m_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct fimc_ctx *ctx = file->private_data; + return v4l2_m2m_mmap(file, ctx->m2m_ctx, vma); +} + +static const struct v4l2_file_operations fimc_m2m_fops = { + .owner = THIS_MODULE, + .open = fimc_m2m_open, + .release = fimc_m2m_release, + .poll = fimc_m2m_poll, + .ioctl = video_ioctl2, + .mmap = fimc_m2m_mmap, +}; + +static struct v4l2_m2m_ops m2m_ops = { + .device_run = fimc_dma_run, + .job_abort = fimc_job_abort, +}; + + +static int fimc_register_m2m_device(struct fimc_dev *fimc) +{ + struct video_device *vfd; + struct platform_device *pdev; + struct v4l2_device *v4l2_dev; + int ret = 0; + + if (!fimc) + return -ENODEV; + + pdev = fimc->pdev; + v4l2_dev = &fimc->m2m.v4l2_dev; + + /* set name if it is empty */ + if (!v4l2_dev->name[0]) + snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), + "%s.m2m", dev_name(&pdev->dev)); + + ret = v4l2_device_register(&pdev->dev, v4l2_dev); + if (ret) + return ret;; + + vfd = video_device_alloc(); + if (!vfd) { + v4l2_err(v4l2_dev, "Failed to allocate video device\n"); + goto err_m2m_r1; + } + + vfd->fops = &fimc_m2m_fops; + vfd->ioctl_ops = &fimc_m2m_ioctl_ops; + vfd->minor = -1; + vfd->release = video_device_release; + + snprintf(vfd->name, sizeof(vfd->name), "%s:m2m", dev_name(&pdev->dev)); + + video_set_drvdata(vfd, fimc); + platform_set_drvdata(pdev, fimc); + + fimc->m2m.vfd = vfd; + fimc->m2m.m2m_dev = v4l2_m2m_init(&m2m_ops); + if (IS_ERR(fimc->m2m.m2m_dev)) { + v4l2_err(v4l2_dev, "failed to initialize v4l2-m2m device\n"); + ret = PTR_ERR(fimc->m2m.m2m_dev); + goto err_m2m_r2; + } + + ret = video_register_device(vfd, VFL_TYPE_GRABBER, -1); + if (ret) { + v4l2_err(v4l2_dev, + "%s(): failed to register video device\n", __func__); + goto err_m2m_r3; + } + v4l2_info(v4l2_dev, + "FIMC m2m driver registered as /dev/video%d\n", vfd->num); + + return 0; + +err_m2m_r3: + v4l2_m2m_release(fimc->m2m.m2m_dev); +err_m2m_r2: + video_device_release(fimc->m2m.vfd); +err_m2m_r1: + v4l2_device_unregister(v4l2_dev); + + return ret; +} + +static void fimc_unregister_m2m_device(struct fimc_dev *fimc) +{ + if (fimc) { + v4l2_m2m_release(fimc->m2m.m2m_dev); + video_unregister_device(fimc->m2m.vfd); + video_device_release(fimc->m2m.vfd); + v4l2_device_unregister(&fimc->m2m.v4l2_dev); + } +} + +static void fimc_clk_release(struct fimc_dev *fimc) +{ + int i; + for (i = 0; i < NUM_FIMC_CLOCKS; i++) { + if (fimc->clock[i]) { + clk_disable(fimc->clock[i]); + clk_put(fimc->clock[i]); + } + } +} + +static int fimc_clk_get(struct fimc_dev *fimc) +{ + int i; + for (i = 0; i < NUM_FIMC_CLOCKS; i++) { + fimc->clock[i] = clk_get(&fimc->pdev->dev, fimc_clock_name[i]); + if (IS_ERR(fimc->clock[i])) { + dev_err(&fimc->pdev->dev, + "failed to get fimc clock: %s\n", + fimc_clock_name[i]); + return -ENXIO; + } + clk_enable(fimc->clock[i]); + } + return 0; +} + +static int fimc_probe(struct platform_device *pdev) +{ + struct fimc_dev *fimc; + struct resource *res; + struct samsung_fimc_driverdata *drv_data; + int ret = 0; + + dev_dbg(&pdev->dev, "%s():\n", __func__); + + drv_data = (struct samsung_fimc_driverdata *) + platform_get_device_id(pdev)->driver_data; + + if (pdev->id >= drv_data->devs_cnt) { + dev_err(&pdev->dev, "Invalid platform device id: %d\n", + pdev->id); + return -EINVAL; + } + + fimc = kzalloc(sizeof(struct fimc_dev), GFP_KERNEL); + if (!fimc) + return -ENOMEM; + + fimc->id = pdev->id; + fimc->variant = drv_data->variant[fimc->id]; + fimc->pdev = pdev; + fimc->state = ST_IDLE; + + spin_lock_init(&fimc->irqlock); + spin_lock_init(&fimc->slock); + + mutex_init(&fimc->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to find the registers\n"); + ret = -ENOENT; + goto err_info; + } + + fimc->regs_res = request_mem_region(res->start, resource_size(res), + dev_name(&pdev->dev)); + if (!fimc->regs_res) { + dev_err(&pdev->dev, "failed to obtain register region\n"); + ret = -ENOENT; + goto err_info; + } + + fimc->regs = ioremap(res->start, resource_size(res)); + if (!fimc->regs) { + dev_err(&pdev->dev, "failed to map registers\n"); + ret = -ENXIO; + goto err_req_region; + } + + ret = fimc_clk_get(fimc); + if (ret) + goto err_regs_unmap; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get IRQ resource\n"); + ret = -ENXIO; + goto err_clk; + } + fimc->irq = res->start; + + fimc_hw_reset(fimc); + + ret = request_irq(fimc->irq, fimc_isr, 0, pdev->name, fimc); + if (ret) { + dev_err(&pdev->dev, "failed to install irq (%d)\n", ret); + goto err_clk; + } + + fimc->work_queue = create_workqueue(dev_name(&fimc->pdev->dev)); + if (!fimc->work_queue) + goto err_irq; + + ret = fimc_register_m2m_device(fimc); + if (ret) + goto err_wq; + + fimc_hw_en_lastirq(fimc, true); + + dev_dbg(&pdev->dev, "%s(): fimc-%d registered successfully\n", + __func__, fimc->id); + + return 0; + +err_wq: + destroy_workqueue(fimc->work_queue); +err_irq: + free_irq(fimc->irq, fimc); +err_clk: + fimc_clk_release(fimc); +err_regs_unmap: + iounmap(fimc->regs); +err_req_region: + release_resource(fimc->regs_res); + kfree(fimc->regs_res); +err_info: + kfree(fimc); + dev_err(&pdev->dev, "failed to install\n"); + return ret; +} + +static int __devexit fimc_remove(struct platform_device *pdev) +{ + struct fimc_dev *fimc = + (struct fimc_dev *)platform_get_drvdata(pdev); + + v4l2_info(&fimc->m2m.v4l2_dev, "Removing %s\n", pdev->name); + + free_irq(fimc->irq, fimc); + + fimc_hw_reset(fimc); + + fimc_unregister_m2m_device(fimc); + fimc_clk_release(fimc); + iounmap(fimc->regs); + release_resource(fimc->regs_res); + kfree(fimc->regs_res); + kfree(fimc); + return 0; +} + +static struct samsung_fimc_variant fimc01_variant_s5p = { + .has_inp_rot = 1, + .has_out_rot = 1, + .min_inp_pixsize = 16, + .min_out_pixsize = 16, + + .scaler_en_w = 3264, + .scaler_dis_w = 8192, + .in_rot_en_h = 1920, + .in_rot_dis_w = 8192, + .out_rot_en_w = 1920, + .out_rot_dis_w = 4224, +}; + +static struct samsung_fimc_variant fimc2_variant_s5p = { + .min_inp_pixsize = 16, + .min_out_pixsize = 16, + + .scaler_en_w = 4224, + .scaler_dis_w = 8192, + .in_rot_en_h = 1920, + .in_rot_dis_w = 8192, + .out_rot_en_w = 1920, + .out_rot_dis_w = 4224, +}; + +static struct samsung_fimc_variant fimc01_variant_s5pv210 = { + .has_inp_rot = 1, + .has_out_rot = 1, + .min_inp_pixsize = 16, + .min_out_pixsize = 32, + + .scaler_en_w = 4224, + .scaler_dis_w = 8192, + .in_rot_en_h = 1920, + .in_rot_dis_w = 8192, + .out_rot_en_w = 1920, + .out_rot_dis_w = 4224, +}; + +static struct samsung_fimc_variant fimc2_variant_s5pv210 = { + .min_inp_pixsize = 16, + .min_out_pixsize = 32, + + .scaler_en_w = 1920, + .scaler_dis_w = 8192, + .in_rot_en_h = 1280, + .in_rot_dis_w = 8192, + .out_rot_en_w = 1280, + .out_rot_dis_w = 1920, +}; + +static struct samsung_fimc_driverdata fimc_drvdata_s5p = { + .variant = { + [0] = &fimc01_variant_s5p, + [1] = &fimc01_variant_s5p, + [2] = &fimc2_variant_s5p, + }, + .devs_cnt = 3 +}; + +static struct samsung_fimc_driverdata fimc_drvdata_s5pv210 = { + .variant = { + [0] = &fimc01_variant_s5pv210, + [1] = &fimc01_variant_s5pv210, + [2] = &fimc2_variant_s5pv210, + }, + .devs_cnt = 3 +}; + +static struct platform_device_id fimc_driver_ids[] = { + { + .name = "s5p-fimc", + .driver_data = (unsigned long)&fimc_drvdata_s5p, + }, { + .name = "s5pv210-fimc", + .driver_data = (unsigned long)&fimc_drvdata_s5pv210, + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, fimc_driver_ids); + +static struct platform_driver fimc_driver = { + .probe = fimc_probe, + .remove = __devexit_p(fimc_remove), + .id_table = fimc_driver_ids, + .driver = { + .name = MODULE_NAME, + .owner = THIS_MODULE, + } +}; + +static char banner[] __initdata = KERN_INFO + "S5PC Camera Interface V4L2 Driver, (c) 2010 Samsung Electronics\n"; + +static int __init fimc_init(void) +{ + u32 ret; + printk(banner); + + ret = platform_driver_register(&fimc_driver); + if (ret) { + printk(KERN_ERR "FIMC platform driver register failed\n"); + return -1; + } + return 0; +} + +static void __exit fimc_exit(void) +{ + platform_driver_unregister(&fimc_driver); +} + +module_init(fimc_init); +module_exit(fimc_exit); + +MODULE_AUTHOR("Sylwester Nawrocki, s.nawrocki@samsung.com"); +MODULE_DESCRIPTION("S3C/S5P FIMC (video postprocessor) driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/s5p-fimc/fimc-core.h b/drivers/media/video/s5p-fimc/fimc-core.h new file mode 100644 index 00000000000..f121b939626 --- /dev/null +++ b/drivers/media/video/s5p-fimc/fimc-core.h @@ -0,0 +1,465 @@ +/* + * Copyright (c) 2010 Samsung Electronics + * + * Sylwester Nawrocki, + * + * 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 FIMC_CORE_H_ +#define FIMC_CORE_H_ + +#include +#include +#include +#include +#include +#include "regs-fimc.h" + +#define err(fmt, args...) \ + printk(KERN_ERR "%s:%d: " fmt "\n", __func__, __LINE__, ##args) + +#ifdef DEBUG +#define dbg(fmt, args...) \ + printk(KERN_DEBUG "%s:%d: " fmt "\n", __func__, __LINE__, ##args) +#else +#define dbg(fmt, args...) +#endif + +#define ctx_m2m_get_frame(frame, ctx, type) do { \ + if (V4L2_BUF_TYPE_VIDEO_OUTPUT == (type)) { \ + frame = &(ctx)->s_frame; \ + } else if (V4L2_BUF_TYPE_VIDEO_CAPTURE == (type)) { \ + frame = &(ctx)->d_frame; \ + } else { \ + v4l2_err(&(ctx)->fimc_dev->m2m.v4l2_dev,\ + "Wrong buffer/video queue type (%d)\n", type); \ + return -EINVAL; \ + } \ +} while (0) + +#define NUM_FIMC_CLOCKS 2 +#define MODULE_NAME "s5p-fimc" +#define FIMC_MAX_DEVS 3 +#define FIMC_MAX_OUT_BUFS 4 +#define SCALER_MAX_HRATIO 64 +#define SCALER_MAX_VRATIO 64 + +enum { + ST_IDLE, + ST_OUTDMA_RUN, + ST_M2M_PEND, +}; + +#define fimc_m2m_active(dev) test_bit(ST_OUTDMA_RUN, &(dev)->state) +#define fimc_m2m_pending(dev) test_bit(ST_M2M_PEND, &(dev)->state) + +enum fimc_datapath { + FIMC_ITU_CAM_A, + FIMC_ITU_CAM_B, + FIMC_MIPI_CAM, + FIMC_DMA, + FIMC_LCDFIFO, + FIMC_WRITEBACK +}; + +enum fimc_color_fmt { + S5P_FIMC_RGB565, + S5P_FIMC_RGB666, + S5P_FIMC_RGB888, + S5P_FIMC_YCBCR420, + S5P_FIMC_YCBCR422, + S5P_FIMC_YCBYCR422, + S5P_FIMC_YCRYCB422, + S5P_FIMC_CBYCRY422, + S5P_FIMC_CRYCBY422, + S5P_FIMC_RGB30_LOCAL, + S5P_FIMC_YCBCR444_LOCAL, + S5P_FIMC_MAX_COLOR = S5P_FIMC_YCBCR444_LOCAL, + S5P_FIMC_COLOR_MASK = 0x0F, +}; + +/* Y/Cb/Cr components order at DMA output for 1 plane YCbCr 4:2:2 formats. */ +#define S5P_FIMC_OUT_CRYCBY S5P_CIOCTRL_ORDER422_CRYCBY +#define S5P_FIMC_OUT_CBYCRY S5P_CIOCTRL_ORDER422_YCRYCB +#define S5P_FIMC_OUT_YCRYCB S5P_CIOCTRL_ORDER422_CBYCRY +#define S5P_FIMC_OUT_YCBYCR S5P_CIOCTRL_ORDER422_YCBYCR + +/* Input Y/Cb/Cr components order for 1 plane YCbCr 4:2:2 color formats. */ +#define S5P_FIMC_IN_CRYCBY S5P_MSCTRL_ORDER422_CRYCBY +#define S5P_FIMC_IN_CBYCRY S5P_MSCTRL_ORDER422_YCRYCB +#define S5P_FIMC_IN_YCRYCB S5P_MSCTRL_ORDER422_CBYCRY +#define S5P_FIMC_IN_YCBYCR S5P_MSCTRL_ORDER422_YCBYCR + +/* Cb/Cr chrominance components order for 2 plane Y/CbCr 4:2:2 formats. */ +#define S5P_FIMC_LSB_CRCB S5P_CIOCTRL_ORDER422_2P_LSB_CRCB + +/* The embedded image effect selection */ +#define S5P_FIMC_EFFECT_ORIGINAL S5P_CIIMGEFF_FIN_BYPASS +#define S5P_FIMC_EFFECT_ARBITRARY S5P_CIIMGEFF_FIN_ARBITRARY +#define S5P_FIMC_EFFECT_NEGATIVE S5P_CIIMGEFF_FIN_NEGATIVE +#define S5P_FIMC_EFFECT_ARTFREEZE S5P_CIIMGEFF_FIN_ARTFREEZE +#define S5P_FIMC_EFFECT_EMBOSSING S5P_CIIMGEFF_FIN_EMBOSSING +#define S5P_FIMC_EFFECT_SIKHOUETTE S5P_CIIMGEFF_FIN_SILHOUETTE + +/* The hardware context state. */ +#define FIMC_PARAMS (1 << 0) +#define FIMC_SRC_ADDR (1 << 1) +#define FIMC_DST_ADDR (1 << 2) +#define FIMC_SRC_FMT (1 << 3) +#define FIMC_DST_FMT (1 << 4) + +/* Image conversion flags */ +#define FIMC_IN_DMA_ACCESS_TILED (1 << 0) +#define FIMC_IN_DMA_ACCESS_LINEAR (0 << 0) +#define FIMC_OUT_DMA_ACCESS_TILED (1 << 1) +#define FIMC_OUT_DMA_ACCESS_LINEAR (0 << 1) +#define FIMC_SCAN_MODE_PROGRESSIVE (0 << 2) +#define FIMC_SCAN_MODE_INTERLACED (1 << 2) +/* YCbCr data dynamic range for RGB-YUV color conversion. Y/Cb/Cr: (0 ~ 255) */ +#define FIMC_COLOR_RANGE_WIDE (0 << 3) +/* Y (16 ~ 235), Cb/Cr (16 ~ 240) */ +#define FIMC_COLOR_RANGE_NARROW (1 << 3) + +#define FLIP_NONE 0 +#define FLIP_X_AXIS 1 +#define FLIP_Y_AXIS 2 +#define FLIP_XY_AXIS (FLIP_X_AXIS | FLIP_Y_AXIS) + +/** + * struct fimc_fmt - the driver's internal color format data + * @name: format description + * @fourcc: the fourcc code for this format + * @color: the corresponding fimc_color_fmt + * @depth: number of bits per pixel + * @buff_cnt: number of physically non-contiguous data planes + * @planes_cnt: number of physically contiguous data planes + */ +struct fimc_fmt { + char *name; + u32 fourcc; + u32 color; + u32 depth; + u16 buff_cnt; + u16 planes_cnt; +}; + +/** + * struct fimc_dma_offset - pixel offset information for DMA + * @y_h: y value horizontal offset + * @y_v: y value vertical offset + * @cb_h: cb value horizontal offset + * @cb_v: cb value vertical offset + * @cr_h: cr value horizontal offset + * @cr_v: cr value vertical offset + */ +struct fimc_dma_offset { + int y_h; + int y_v; + int cb_h; + int cb_v; + int cr_h; + int cr_v; +}; + +/** + * struct fimc_effect - the configuration data for the "Arbitrary" image effect + * @type: effect type + * @pat_cb: cr value when type is "arbitrary" + * @pat_cr: cr value when type is "arbitrary" + */ +struct fimc_effect { + u32 type; + u8 pat_cb; + u8 pat_cr; +}; + +/** + * struct fimc_scaler - the configuration data for FIMC inetrnal scaler + * + * @enabled: the flag set when the scaler is used + * @hfactor: horizontal shift factor + * @vfactor: vertical shift factor + * @pre_hratio: horizontal ratio of the prescaler + * @pre_vratio: vertical ratio of the prescaler + * @pre_dst_width: the prescaler's destination width + * @pre_dst_height: the prescaler's destination height + * @scaleup_h: flag indicating scaling up horizontally + * @scaleup_v: flag indicating scaling up vertically + * @main_hratio: the main scaler's horizontal ratio + * @main_vratio: the main scaler's vertical ratio + * @real_width: source width - offset + * @real_height: source height - offset + * @copy_mode: flag set if one-to-one mode is used, i.e. no scaling + * and color format conversion + */ +struct fimc_scaler { + u32 enabled; + u32 hfactor; + u32 vfactor; + u32 pre_hratio; + u32 pre_vratio; + u32 pre_dst_width; + u32 pre_dst_height; + u32 scaleup_h; + u32 scaleup_v; + u32 main_hratio; + u32 main_vratio; + u32 real_width; + u32 real_height; + u32 copy_mode; +}; + +/** + * struct fimc_addr - the FIMC physical address set for DMA + * + * @y: luminance plane physical address + * @cb: Cb plane physical address + * @cr: Cr plane physical address + */ +struct fimc_addr { + u32 y; + u32 cb; + u32 cr; +}; + +/** + * struct fimc_vid_buffer - the driver's video buffer + * @vb: v4l videobuf buffer + */ +struct fimc_vid_buffer { + struct videobuf_buffer vb; +}; + +/** + * struct fimc_frame - input/output frame format properties + * + * @f_width: image full width (virtual screen size) + * @f_height: image full height (virtual screen size) + * @o_width: original image width as set by S_FMT + * @o_height: original image height as set by S_FMT + * @offs_h: image horizontal pixel offset + * @offs_v: image vertical pixel offset + * @width: image pixel width + * @height: image pixel weight + * @paddr: image frame buffer physical addresses + * @buf_cnt: number of buffers depending on a color format + * @size: image size in bytes + * @color: color format + * @dma_offset: DMA offset in bytes + */ +struct fimc_frame { + u32 f_width; + u32 f_height; + u32 o_width; + u32 o_height; + u32 offs_h; + u32 offs_v; + u32 width; + u32 height; + u32 size; + struct fimc_addr paddr; + struct fimc_dma_offset dma_offset; + struct fimc_fmt *fmt; +}; + +/** + * struct fimc_m2m_device - v4l2 memory-to-memory device data + * @vfd: the video device node for v4l2 m2m mode + * @v4l2_dev: v4l2 device for m2m mode + * @m2m_dev: v4l2 memory-to-memory device data + * @ctx: hardware context data + * @refcnt: the reference counter + */ +struct fimc_m2m_device { + struct video_device *vfd; + struct v4l2_device v4l2_dev; + struct v4l2_m2m_dev *m2m_dev; + struct fimc_ctx *ctx; + int refcnt; +}; + +/** + * struct samsung_fimc_variant - camera interface variant information + * + * @pix_hoff: indicate whether horizontal offset is in pixels or in bytes + * @has_inp_rot: set if has input rotator + * @has_out_rot: set if has output rotator + * @min_inp_pixsize: minimum input pixel size + * @min_out_pixsize: minimum output pixel size + * @scaler_en_w: maximum input pixel width when the scaler is enabled + * @scaler_dis_w: maximum input pixel width when the scaler is disabled + * @in_rot_en_h: maximum input width when the input rotator is used + * @in_rot_dis_w: maximum input width when the input rotator is used + * @out_rot_en_w: maximum output width for the output rotator enabled + * @out_rot_dis_w: maximum output width for the output rotator enabled + */ +struct samsung_fimc_variant { + unsigned int pix_hoff:1; + unsigned int has_inp_rot:1; + unsigned int has_out_rot:1; + + u16 min_inp_pixsize; + u16 min_out_pixsize; + u16 scaler_en_w; + u16 scaler_dis_w; + u16 in_rot_en_h; + u16 in_rot_dis_w; + u16 out_rot_en_w; + u16 out_rot_dis_w; +}; + +/** + * struct samsung_fimc_driverdata - per-device type driver data for init time. + * + * @variant: the variant information for this driver. + * @dev_cnt: number of fimc sub-devices available in SoC + */ +struct samsung_fimc_driverdata { + struct samsung_fimc_variant *variant[FIMC_MAX_DEVS]; + int devs_cnt; +}; + +struct fimc_ctx; + +/** + * struct fimc_subdev - abstraction for a FIMC entity + * + * @slock: the spinlock protecting this data structure + * @lock: the mutex protecting this data structure + * @pdev: pointer to the FIMC platform device + * @id: FIMC device index (0..2) + * @clock[]: the clocks required for FIMC operation + * @regs: the mapped hardware registers + * @regs_res: the resource claimed for IO registers + * @irq: interrupt number of the FIMC subdevice + * @irqlock: spinlock protecting videbuffer queue + * @m2m: memory-to-memory V4L2 device information + * @state: the FIMC device state flags + */ +struct fimc_dev { + spinlock_t slock; + struct mutex lock; + struct platform_device *pdev; + struct samsung_fimc_variant *variant; + int id; + struct clk *clock[NUM_FIMC_CLOCKS]; + void __iomem *regs; + struct resource *regs_res; + int irq; + spinlock_t irqlock; + struct workqueue_struct *work_queue; + struct fimc_m2m_device m2m; + unsigned long state; +}; + +/** + * fimc_ctx - the device context data + * + * @lock: mutex protecting this data structure + * @s_frame: source frame properties + * @d_frame: destination frame properties + * @out_order_1p: output 1-plane YCBCR order + * @out_order_2p: output 2-plane YCBCR order + * @in_order_1p input 1-plane YCBCR order + * @in_order_2p: input 2-plane YCBCR order + * @in_path: input mode (DMA or camera) + * @out_path: output mode (DMA or FIFO) + * @scaler: image scaler properties + * @effect: image effect + * @rotation: image clockwise rotation in degrees + * @flip: image flip mode + * @flags: an 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 + */ +struct fimc_ctx { + spinlock_t slock; + struct fimc_frame s_frame; + struct fimc_frame d_frame; + u32 out_order_1p; + u32 out_order_2p; + u32 in_order_1p; + u32 in_order_2p; + enum fimc_datapath in_path; + enum fimc_datapath out_path; + struct fimc_scaler scaler; + struct fimc_effect effect; + int rotation; + u32 flip; + u32 flags; + u32 state; + struct fimc_dev *fimc_dev; + struct v4l2_m2m_ctx *m2m_ctx; +}; + + +static inline int tiled_fmt(struct fimc_fmt *fmt) +{ + return 0; +} + +static inline void fimc_hw_clear_irq(struct fimc_dev *dev) +{ + u32 cfg = readl(dev->regs + S5P_CIGCTRL); + cfg |= S5P_CIGCTRL_IRQ_CLR; + writel(cfg, dev->regs + S5P_CIGCTRL); +} + +static inline void fimc_hw_start_scaler(struct fimc_dev *dev) +{ + u32 cfg = readl(dev->regs + S5P_CISCCTRL); + cfg |= S5P_CISCCTRL_SCALERSTART; + writel(cfg, dev->regs + S5P_CISCCTRL); +} + +static inline void fimc_hw_stop_scaler(struct fimc_dev *dev) +{ + u32 cfg = readl(dev->regs + S5P_CISCCTRL); + cfg &= ~S5P_CISCCTRL_SCALERSTART; + writel(cfg, dev->regs + S5P_CISCCTRL); +} + +static inline void fimc_hw_dis_capture(struct fimc_dev *dev) +{ + u32 cfg = readl(dev->regs + S5P_CIIMGCPT); + cfg &= ~(S5P_CIIMGCPT_IMGCPTEN | S5P_CIIMGCPT_IMGCPTEN_SC); + writel(cfg, dev->regs + S5P_CIIMGCPT); +} + +static inline void fimc_hw_start_in_dma(struct fimc_dev *dev) +{ + u32 cfg = readl(dev->regs + S5P_MSCTRL); + cfg |= S5P_MSCTRL_ENVID; + writel(cfg, dev->regs + S5P_MSCTRL); +} + +static inline void fimc_hw_stop_in_dma(struct fimc_dev *dev) +{ + u32 cfg = readl(dev->regs + S5P_MSCTRL); + cfg &= ~S5P_MSCTRL_ENVID; + writel(cfg, dev->regs + S5P_MSCTRL); +} + +/* -----------------------------------------------------*/ +/* fimc-reg.c */ +void fimc_hw_reset(struct fimc_dev *dev); +void fimc_hw_set_rotation(struct fimc_ctx *ctx); +void fimc_hw_set_target_format(struct fimc_ctx *ctx); +void fimc_hw_set_out_dma(struct fimc_ctx *ctx); +void fimc_hw_en_lastirq(struct fimc_dev *dev, int enable); +void fimc_hw_en_irq(struct fimc_dev *dev, int enable); +void fimc_hw_set_prescaler(struct fimc_ctx *ctx); +void fimc_hw_set_scaler(struct fimc_ctx *ctx); +void fimc_hw_en_capture(struct fimc_ctx *ctx); +void fimc_hw_set_effect(struct fimc_ctx *ctx); +void fimc_hw_set_in_dma(struct fimc_ctx *ctx); +void fimc_hw_set_input_path(struct fimc_ctx *ctx); +void fimc_hw_set_output_path(struct fimc_ctx *ctx); +void fimc_hw_set_input_addr(struct fimc_dev *dev, struct fimc_addr *paddr); +void fimc_hw_set_output_addr(struct fimc_dev *dev, struct fimc_addr *paddr); + +#endif /* FIMC_CORE_H_ */ diff --git a/drivers/media/video/s5p-fimc/fimc-reg.c b/drivers/media/video/s5p-fimc/fimc-reg.c new file mode 100644 index 00000000000..5570f1ce0c9 --- /dev/null +++ b/drivers/media/video/s5p-fimc/fimc-reg.c @@ -0,0 +1,527 @@ +/* + * Register interface file for Samsung Camera Interface (FIMC) driver + * + * Copyright (c) 2010 Samsung Electronics + * + * Sylwester Nawrocki, s.nawrocki@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. +*/ + +#include +#include +#include + +#include "fimc-core.h" + + +void fimc_hw_reset(struct fimc_dev *dev) +{ + u32 cfg; + + cfg = readl(dev->regs + S5P_CISRCFMT); + cfg |= S5P_CISRCFMT_ITU601_8BIT; + writel(cfg, dev->regs + S5P_CISRCFMT); + + /* Software reset. */ + cfg = readl(dev->regs + S5P_CIGCTRL); + cfg |= (S5P_CIGCTRL_SWRST | S5P_CIGCTRL_IRQ_LEVEL); + writel(cfg, dev->regs + S5P_CIGCTRL); + msleep(1); + + cfg = readl(dev->regs + S5P_CIGCTRL); + cfg &= ~S5P_CIGCTRL_SWRST; + writel(cfg, dev->regs + S5P_CIGCTRL); + +} + +void fimc_hw_set_rotation(struct fimc_ctx *ctx) +{ + u32 cfg, flip; + struct fimc_dev *dev = ctx->fimc_dev; + + cfg = readl(dev->regs + S5P_CITRGFMT); + cfg &= ~(S5P_CITRGFMT_INROT90 | S5P_CITRGFMT_OUTROT90); + + flip = readl(dev->regs + S5P_MSCTRL); + flip &= ~S5P_MSCTRL_FLIP_MASK; + + /* + * The input and output rotator cannot work simultaneously. + * Use the output rotator in output DMA mode or the input rotator + * in direct fifo output mode. + */ + if (ctx->rotation == 90 || ctx->rotation == 270) { + if (ctx->out_path == FIMC_LCDFIFO) { + cfg |= S5P_CITRGFMT_INROT90; + if (ctx->rotation == 270) + flip |= S5P_MSCTRL_FLIP_180; + } else { + cfg |= S5P_CITRGFMT_OUTROT90; + if (ctx->rotation == 270) + cfg |= S5P_CITRGFMT_FLIP_180; + } + } else if (ctx->rotation == 180) { + if (ctx->out_path == FIMC_LCDFIFO) + flip |= S5P_MSCTRL_FLIP_180; + else + cfg |= S5P_CITRGFMT_FLIP_180; + } + if (ctx->rotation == 180 || ctx->rotation == 270) + writel(flip, dev->regs + S5P_MSCTRL); + writel(cfg, dev->regs + S5P_CITRGFMT); +} + +static u32 fimc_hw_get_in_flip(u32 ctx_flip) +{ + u32 flip = S5P_MSCTRL_FLIP_NORMAL; + + switch (ctx_flip) { + case FLIP_X_AXIS: + flip = S5P_MSCTRL_FLIP_X_MIRROR; + break; + case FLIP_Y_AXIS: + flip = S5P_MSCTRL_FLIP_Y_MIRROR; + break; + case FLIP_XY_AXIS: + flip = S5P_MSCTRL_FLIP_180; + break; + } + + return flip; +} + +static u32 fimc_hw_get_target_flip(u32 ctx_flip) +{ + u32 flip = S5P_CITRGFMT_FLIP_NORMAL; + + switch (ctx_flip) { + case FLIP_X_AXIS: + flip = S5P_CITRGFMT_FLIP_X_MIRROR; + break; + case FLIP_Y_AXIS: + flip = S5P_CITRGFMT_FLIP_Y_MIRROR; + break; + case FLIP_XY_AXIS: + flip = S5P_CITRGFMT_FLIP_180; + break; + case FLIP_NONE: + break; + + } + return flip; +} + +void fimc_hw_set_target_format(struct fimc_ctx *ctx) +{ + u32 cfg; + struct fimc_dev *dev = ctx->fimc_dev; + struct fimc_frame *frame = &ctx->d_frame; + + dbg("w= %d, h= %d color: %d", frame->width, + frame->height, frame->fmt->color); + + cfg = readl(dev->regs + S5P_CITRGFMT); + cfg &= ~(S5P_CITRGFMT_FMT_MASK | S5P_CITRGFMT_HSIZE_MASK | + S5P_CITRGFMT_VSIZE_MASK); + + switch (frame->fmt->color) { + case S5P_FIMC_RGB565: + case S5P_FIMC_RGB666: + case S5P_FIMC_RGB888: + cfg |= S5P_CITRGFMT_RGB; + break; + case S5P_FIMC_YCBCR420: + cfg |= S5P_CITRGFMT_YCBCR420; + break; + case S5P_FIMC_YCBYCR422: + case S5P_FIMC_YCRYCB422: + case S5P_FIMC_CBYCRY422: + case S5P_FIMC_CRYCBY422: + if (frame->fmt->planes_cnt == 1) + cfg |= S5P_CITRGFMT_YCBCR422_1P; + else + cfg |= S5P_CITRGFMT_YCBCR422; + break; + default: + break; + } + + cfg |= S5P_CITRGFMT_HSIZE(frame->width); + cfg |= S5P_CITRGFMT_VSIZE(frame->height); + + if (ctx->rotation == 0) { + cfg &= ~S5P_CITRGFMT_FLIP_MASK; + cfg |= fimc_hw_get_target_flip(ctx->flip); + } + writel(cfg, dev->regs + S5P_CITRGFMT); + + cfg = readl(dev->regs + S5P_CITAREA) & ~S5P_CITAREA_MASK; + cfg |= (frame->width * frame->height); + writel(cfg, dev->regs + S5P_CITAREA); +} + +static void fimc_hw_set_out_dma_size(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + struct fimc_frame *frame = &ctx->d_frame; + u32 cfg = 0; + + if (ctx->rotation == 90 || ctx->rotation == 270) { + cfg |= S5P_ORIG_SIZE_HOR(frame->f_height); + cfg |= S5P_ORIG_SIZE_VER(frame->f_width); + } else { + cfg |= S5P_ORIG_SIZE_HOR(frame->f_width); + cfg |= S5P_ORIG_SIZE_VER(frame->f_height); + } + writel(cfg, dev->regs + S5P_ORGOSIZE); +} + +void fimc_hw_set_out_dma(struct fimc_ctx *ctx) +{ + u32 cfg; + struct fimc_dev *dev = ctx->fimc_dev; + struct fimc_frame *frame = &ctx->d_frame; + struct fimc_dma_offset *offset = &frame->dma_offset; + + /* Set the input dma offsets. */ + cfg = 0; + cfg |= S5P_CIO_OFFS_HOR(offset->y_h); + cfg |= S5P_CIO_OFFS_VER(offset->y_v); + writel(cfg, dev->regs + S5P_CIOYOFF); + + cfg = 0; + cfg |= S5P_CIO_OFFS_HOR(offset->cb_h); + cfg |= S5P_CIO_OFFS_VER(offset->cb_v); + writel(cfg, dev->regs + S5P_CIOCBOFF); + + cfg = 0; + cfg |= S5P_CIO_OFFS_HOR(offset->cr_h); + cfg |= S5P_CIO_OFFS_VER(offset->cr_v); + writel(cfg, dev->regs + S5P_CIOCROFF); + + fimc_hw_set_out_dma_size(ctx); + + /* Configure chroma components order. */ + cfg = readl(dev->regs + S5P_CIOCTRL); + + cfg &= ~(S5P_CIOCTRL_ORDER2P_MASK | S5P_CIOCTRL_ORDER422_MASK | + S5P_CIOCTRL_YCBCR_PLANE_MASK); + + if (frame->fmt->planes_cnt == 1) + cfg |= ctx->out_order_1p; + else if (frame->fmt->planes_cnt == 2) + cfg |= ctx->out_order_2p | S5P_CIOCTRL_YCBCR_2PLANE; + else if (frame->fmt->planes_cnt == 3) + cfg |= S5P_CIOCTRL_YCBCR_3PLANE; + + writel(cfg, dev->regs + S5P_CIOCTRL); +} + +static void fimc_hw_en_autoload(struct fimc_dev *dev, int enable) +{ + u32 cfg = readl(dev->regs + S5P_ORGISIZE); + if (enable) + cfg |= S5P_CIREAL_ISIZE_AUTOLOAD_EN; + else + cfg &= ~S5P_CIREAL_ISIZE_AUTOLOAD_EN; + writel(cfg, dev->regs + S5P_ORGISIZE); +} + +void fimc_hw_en_lastirq(struct fimc_dev *dev, int enable) +{ + unsigned long flags; + u32 cfg; + + spin_lock_irqsave(&dev->slock, flags); + + cfg = readl(dev->regs + S5P_CIOCTRL); + if (enable) + cfg |= S5P_CIOCTRL_LASTIRQ_ENABLE; + else + cfg &= ~S5P_CIOCTRL_LASTIRQ_ENABLE; + writel(cfg, dev->regs + S5P_CIOCTRL); + + spin_unlock_irqrestore(&dev->slock, flags); +} + +void fimc_hw_set_prescaler(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + struct fimc_scaler *sc = &ctx->scaler; + u32 cfg = 0, shfactor; + + shfactor = 10 - (sc->hfactor + sc->vfactor); + + cfg |= S5P_CISCPRERATIO_SHFACTOR(shfactor); + cfg |= S5P_CISCPRERATIO_HOR(sc->pre_hratio); + cfg |= S5P_CISCPRERATIO_VER(sc->pre_vratio); + writel(cfg, dev->regs + S5P_CISCPRERATIO); + + cfg = 0; + cfg |= S5P_CISCPREDST_WIDTH(sc->pre_dst_width); + cfg |= S5P_CISCPREDST_HEIGHT(sc->pre_dst_height); + writel(cfg, dev->regs + S5P_CISCPREDST); +} + +void fimc_hw_set_scaler(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + struct fimc_scaler *sc = &ctx->scaler; + struct fimc_frame *src_frame = &ctx->s_frame; + struct fimc_frame *dst_frame = &ctx->d_frame; + u32 cfg = 0; + + if (!(ctx->flags & FIMC_COLOR_RANGE_NARROW)) + cfg |= (S5P_CISCCTRL_CSCR2Y_WIDE | S5P_CISCCTRL_CSCY2R_WIDE); + + if (!sc->enabled) + cfg |= S5P_CISCCTRL_SCALERBYPASS; + + if (sc->scaleup_h) + cfg |= S5P_CISCCTRL_SCALEUP_H; + + if (sc->scaleup_v) + cfg |= S5P_CISCCTRL_SCALEUP_V; + + if (sc->copy_mode) + cfg |= S5P_CISCCTRL_ONE2ONE; + + + if (ctx->in_path == FIMC_DMA) { + if (src_frame->fmt->color == S5P_FIMC_RGB565) + cfg |= S5P_CISCCTRL_INRGB_FMT_RGB565; + else if (src_frame->fmt->color == S5P_FIMC_RGB666) + cfg |= S5P_CISCCTRL_INRGB_FMT_RGB666; + else if (src_frame->fmt->color == S5P_FIMC_RGB888) + cfg |= S5P_CISCCTRL_INRGB_FMT_RGB888; + } + + if (ctx->out_path == FIMC_DMA) { + if (dst_frame->fmt->color == S5P_FIMC_RGB565) + cfg |= S5P_CISCCTRL_OUTRGB_FMT_RGB565; + else if (dst_frame->fmt->color == S5P_FIMC_RGB666) + cfg |= S5P_CISCCTRL_OUTRGB_FMT_RGB666; + else if (dst_frame->fmt->color == S5P_FIMC_RGB888) + cfg |= S5P_CISCCTRL_OUTRGB_FMT_RGB888; + } else { + cfg |= S5P_CISCCTRL_OUTRGB_FMT_RGB888; + + if (ctx->flags & FIMC_SCAN_MODE_INTERLACED) + cfg |= S5P_CISCCTRL_INTERLACE; + } + + dbg("main_hratio= 0x%X main_vratio= 0x%X", + sc->main_hratio, sc->main_vratio); + + cfg |= S5P_CISCCTRL_SC_HORRATIO(sc->main_hratio); + cfg |= S5P_CISCCTRL_SC_VERRATIO(sc->main_vratio); + + writel(cfg, dev->regs + S5P_CISCCTRL); +} + +void fimc_hw_en_capture(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + u32 cfg; + + cfg = readl(dev->regs + S5P_CIIMGCPT); + /* One shot mode for output DMA or freerun for FIFO. */ + if (ctx->out_path == FIMC_DMA) + cfg |= S5P_CIIMGCPT_CPT_FREN_ENABLE; + else + cfg &= ~S5P_CIIMGCPT_CPT_FREN_ENABLE; + + if (ctx->scaler.enabled) + cfg |= S5P_CIIMGCPT_IMGCPTEN_SC; + + writel(cfg | S5P_CIIMGCPT_IMGCPTEN, dev->regs + S5P_CIIMGCPT); +} + +void fimc_hw_set_effect(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + struct fimc_effect *effect = &ctx->effect; + u32 cfg = (S5P_CIIMGEFF_IE_ENABLE | S5P_CIIMGEFF_IE_SC_AFTER); + + cfg |= effect->type; + + if (effect->type == S5P_FIMC_EFFECT_ARBITRARY) { + cfg |= S5P_CIIMGEFF_PAT_CB(effect->pat_cb); + cfg |= S5P_CIIMGEFF_PAT_CR(effect->pat_cr); + } + + writel(cfg, dev->regs + S5P_CIIMGEFF); +} + +static void fimc_hw_set_in_dma_size(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + struct fimc_frame *frame = &ctx->s_frame; + u32 cfg_o = 0; + u32 cfg_r = 0; + + if (FIMC_LCDFIFO == ctx->out_path) + cfg_r |= S5P_CIREAL_ISIZE_AUTOLOAD_EN; + + cfg_o |= S5P_ORIG_SIZE_HOR(frame->f_width); + cfg_o |= S5P_ORIG_SIZE_VER(frame->f_height); + cfg_r |= S5P_CIREAL_ISIZE_WIDTH(frame->width); + cfg_r |= S5P_CIREAL_ISIZE_HEIGHT(frame->height); + + writel(cfg_o, dev->regs + S5P_ORGISIZE); + writel(cfg_r, dev->regs + S5P_CIREAL_ISIZE); +} + +void fimc_hw_set_in_dma(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + struct fimc_frame *frame = &ctx->s_frame; + struct fimc_dma_offset *offset = &frame->dma_offset; + u32 cfg = 0; + + /* Set the pixel offsets. */ + cfg |= S5P_CIO_OFFS_HOR(offset->y_h); + cfg |= S5P_CIO_OFFS_VER(offset->y_v); + writel(cfg, dev->regs + S5P_CIIYOFF); + + cfg = 0; + cfg |= S5P_CIO_OFFS_HOR(offset->cb_h); + cfg |= S5P_CIO_OFFS_VER(offset->cb_v); + writel(cfg, dev->regs + S5P_CIICBOFF); + + cfg = 0; + cfg |= S5P_CIO_OFFS_HOR(offset->cr_h); + cfg |= S5P_CIO_OFFS_VER(offset->cr_v); + writel(cfg, dev->regs + S5P_CIICROFF); + + /* Input original and real size. */ + fimc_hw_set_in_dma_size(ctx); + + /* Autoload is used currently only in FIFO mode. */ + fimc_hw_en_autoload(dev, ctx->out_path == FIMC_LCDFIFO); + + /* Set the input DMA to process single frame only. */ + cfg = readl(dev->regs + S5P_MSCTRL); + cfg &= ~(S5P_MSCTRL_FLIP_MASK + | S5P_MSCTRL_INFORMAT_MASK + | S5P_MSCTRL_IN_BURST_COUNT_MASK + | S5P_MSCTRL_INPUT_MASK + | S5P_MSCTRL_C_INT_IN_MASK + | S5P_MSCTRL_2P_IN_ORDER_MASK); + + cfg |= (S5P_MSCTRL_FRAME_COUNT(1) | S5P_MSCTRL_INPUT_MEMORY); + + switch (frame->fmt->color) { + case S5P_FIMC_RGB565: + case S5P_FIMC_RGB666: + case S5P_FIMC_RGB888: + cfg |= S5P_MSCTRL_INFORMAT_RGB; + break; + case S5P_FIMC_YCBCR420: + cfg |= S5P_MSCTRL_INFORMAT_YCBCR420; + + if (frame->fmt->planes_cnt == 2) + cfg |= ctx->in_order_2p | S5P_MSCTRL_C_INT_IN_2PLANE; + else + cfg |= S5P_MSCTRL_C_INT_IN_3PLANE; + + break; + case S5P_FIMC_YCBYCR422: + case S5P_FIMC_YCRYCB422: + case S5P_FIMC_CBYCRY422: + case S5P_FIMC_CRYCBY422: + if (frame->fmt->planes_cnt == 1) { + cfg |= ctx->in_order_1p + | S5P_MSCTRL_INFORMAT_YCBCR422_1P; + } else { + cfg |= S5P_MSCTRL_INFORMAT_YCBCR422; + + if (frame->fmt->planes_cnt == 2) + cfg |= ctx->in_order_2p + | S5P_MSCTRL_C_INT_IN_2PLANE; + else + cfg |= S5P_MSCTRL_C_INT_IN_3PLANE; + } + break; + default: + break; + } + + /* + * Input DMA flip mode (and rotation). + * Do not allow simultaneous rotation and flipping. + */ + if (!ctx->rotation && ctx->out_path == FIMC_LCDFIFO) + cfg |= fimc_hw_get_in_flip(ctx->flip); + + writel(cfg, dev->regs + S5P_MSCTRL); + + /* Input/output DMA linear/tiled mode. */ + cfg = readl(dev->regs + S5P_CIDMAPARAM); + cfg &= ~S5P_CIDMAPARAM_TILE_MASK; + + if (tiled_fmt(ctx->s_frame.fmt)) + cfg |= S5P_CIDMAPARAM_R_64X32; + + if (tiled_fmt(ctx->d_frame.fmt)) + cfg |= S5P_CIDMAPARAM_W_64X32; + + writel(cfg, dev->regs + S5P_CIDMAPARAM); +} + + +void fimc_hw_set_input_path(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + + u32 cfg = readl(dev->regs + S5P_MSCTRL); + cfg &= ~S5P_MSCTRL_INPUT_MASK; + + if (ctx->in_path == FIMC_DMA) + cfg |= S5P_MSCTRL_INPUT_MEMORY; + else + cfg |= S5P_MSCTRL_INPUT_EXTCAM; + + writel(cfg, dev->regs + S5P_MSCTRL); +} + +void fimc_hw_set_output_path(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + + u32 cfg = readl(dev->regs + S5P_CISCCTRL); + cfg &= ~S5P_CISCCTRL_LCDPATHEN_FIFO; + if (ctx->out_path == FIMC_LCDFIFO) + cfg |= S5P_CISCCTRL_LCDPATHEN_FIFO; + writel(cfg, dev->regs + S5P_CISCCTRL); +} + +void fimc_hw_set_input_addr(struct fimc_dev *dev, struct fimc_addr *paddr) +{ + u32 cfg = 0; + + cfg = readl(dev->regs + S5P_CIREAL_ISIZE); + cfg |= S5P_CIREAL_ISIZE_ADDR_CH_DIS; + writel(cfg, dev->regs + S5P_CIREAL_ISIZE); + + writel(paddr->y, dev->regs + S5P_CIIYSA0); + writel(paddr->cb, dev->regs + S5P_CIICBSA0); + writel(paddr->cr, dev->regs + S5P_CIICRSA0); + + cfg &= ~S5P_CIREAL_ISIZE_ADDR_CH_DIS; + writel(cfg, dev->regs + S5P_CIREAL_ISIZE); +} + +void fimc_hw_set_output_addr(struct fimc_dev *dev, struct fimc_addr *paddr) +{ + int i; + /* Set all the output register sets to point to single video buffer. */ + for (i = 0; i < FIMC_MAX_OUT_BUFS; i++) { + writel(paddr->y, dev->regs + S5P_CIOYSA(i)); + writel(paddr->cb, dev->regs + S5P_CIOCBSA(i)); + writel(paddr->cr, dev->regs + S5P_CIOCRSA(i)); + } +} diff --git a/drivers/media/video/s5p-fimc/regs-fimc.h b/drivers/media/video/s5p-fimc/regs-fimc.h new file mode 100644 index 00000000000..a3cfe824db0 --- /dev/null +++ b/drivers/media/video/s5p-fimc/regs-fimc.h @@ -0,0 +1,293 @@ +/* + * Register definition file for Samsung Camera Interface (FIMC) driver + * + * Copyright (c) 2010 Samsung Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef REGS_FIMC_H_ +#define REGS_FIMC_H_ + +#define S5P_CIOYSA(__x) (0x18 + (__x) * 4) +#define S5P_CIOCBSA(__x) (0x28 + (__x) * 4) +#define S5P_CIOCRSA(__x) (0x38 + (__x) * 4) + +/* Input source format */ +#define S5P_CISRCFMT 0x00 +#define S5P_CISRCFMT_ITU601_8BIT (1 << 31) +#define S5P_CISRCFMT_ITU601_16BIT (1 << 29) +#define S5P_CISRCFMT_ORDER422_YCBYCR (0 << 14) +#define S5P_CISRCFMT_ORDER422_YCRYCB (1 << 14) +#define S5P_CISRCFMT_ORDER422_CBYCRY (2 << 14) +#define S5P_CISRCFMT_ORDER422_CRYCBY (3 << 14) +#define S5P_CISRCFMT_HSIZE(x) ((x) << 16) +#define S5P_CISRCFMT_VSIZE(x) ((x) << 0) + +/* Window offset */ +#define S5P_CIWDOFST 0x04 +#define S5P_CIWDOFST_WINOFSEN (1 << 31) +#define S5P_CIWDOFST_CLROVFIY (1 << 30) +#define S5P_CIWDOFST_CLROVRLB (1 << 29) +#define S5P_CIWDOFST_WINHOROFST_MASK (0x7ff << 16) +#define S5P_CIWDOFST_CLROVFICB (1 << 15) +#define S5P_CIWDOFST_CLROVFICR (1 << 14) +#define S5P_CIWDOFST_WINHOROFST(x) ((x) << 16) +#define S5P_CIWDOFST_WINVEROFST(x) ((x) << 0) +#define S5P_CIWDOFST_WINVEROFST_MASK (0xfff << 0) + +/* Global control */ +#define S5P_CIGCTRL 0x08 +#define S5P_CIGCTRL_SWRST (1 << 31) +#define S5P_CIGCTRL_CAMRST_A (1 << 30) +#define S5P_CIGCTRL_SELCAM_ITU_A (1 << 29) +#define S5P_CIGCTRL_SELCAM_ITU_MASK (1 << 29) +#define S5P_CIGCTRL_TESTPAT_NORMAL (0 << 27) +#define S5P_CIGCTRL_TESTPAT_COLOR_BAR (1 << 27) +#define S5P_CIGCTRL_TESTPAT_HOR_INC (2 << 27) +#define S5P_CIGCTRL_TESTPAT_VER_INC (3 << 27) +#define S5P_CIGCTRL_TESTPAT_MASK (3 << 27) +#define S5P_CIGCTRL_TESTPAT_SHIFT (27) +#define S5P_CIGCTRL_INVPOLPCLK (1 << 26) +#define S5P_CIGCTRL_INVPOLVSYNC (1 << 25) +#define S5P_CIGCTRL_INVPOLHREF (1 << 24) +#define S5P_CIGCTRL_IRQ_OVFEN (1 << 22) +#define S5P_CIGCTRL_HREF_MASK (1 << 21) +#define S5P_CIGCTRL_IRQ_LEVEL (1 << 20) +#define S5P_CIGCTRL_IRQ_CLR (1 << 19) +#define S5P_CIGCTRL_IRQ_ENABLE (1 << 16) +#define S5P_CIGCTRL_SHDW_DISABLE (1 << 12) +#define S5P_CIGCTRL_SELCAM_MIPI_A (1 << 7) +#define S5P_CIGCTRL_CAMIF_SELWB (1 << 6) +#define S5P_CIGCTRL_INVPOLHSYNC (1 << 4) +#define S5P_CIGCTRL_SELCAM_MIPI (1 << 3) +#define S5P_CIGCTRL_INTERLACE (1 << 0) + +/* Window offset 2 */ +#define S5P_CIWDOFST2 0x14 +#define S5P_CIWDOFST2_HOROFF_MASK (0xfff << 16) +#define S5P_CIWDOFST2_VEROFF_MASK (0xfff << 0) +#define S5P_CIWDOFST2_HOROFF(x) ((x) << 16) +#define S5P_CIWDOFST2_VEROFF(x) ((x) << 0) + +/* Output DMA Y plane start address */ +#define S5P_CIOYSA1 0x18 +#define S5P_CIOYSA2 0x1c +#define S5P_CIOYSA3 0x20 +#define S5P_CIOYSA4 0x24 + +/* Output DMA Cb plane start address */ +#define S5P_CIOCBSA1 0x28 +#define S5P_CIOCBSA2 0x2c +#define S5P_CIOCBSA3 0x30 +#define S5P_CIOCBSA4 0x34 + +/* Output DMA Cr plane start address */ +#define S5P_CIOCRSA1 0x38 +#define S5P_CIOCRSA2 0x3c +#define S5P_CIOCRSA3 0x40 +#define S5P_CIOCRSA4 0x44 + +/* Target image format */ +#define S5P_CITRGFMT 0x48 +#define S5P_CITRGFMT_INROT90 (1 << 31) +#define S5P_CITRGFMT_YCBCR420 (0 << 29) +#define S5P_CITRGFMT_YCBCR422 (1 << 29) +#define S5P_CITRGFMT_YCBCR422_1P (2 << 29) +#define S5P_CITRGFMT_RGB (3 << 29) +#define S5P_CITRGFMT_FMT_MASK (3 << 29) +#define S5P_CITRGFMT_HSIZE_MASK (0xfff << 16) +#define S5P_CITRGFMT_FLIP_SHIFT (14) +#define S5P_CITRGFMT_FLIP_NORMAL (0 << 14) +#define S5P_CITRGFMT_FLIP_X_MIRROR (1 << 14) +#define S5P_CITRGFMT_FLIP_Y_MIRROR (2 << 14) +#define S5P_CITRGFMT_FLIP_180 (3 << 14) +#define S5P_CITRGFMT_FLIP_MASK (3 << 14) +#define S5P_CITRGFMT_OUTROT90 (1 << 13) +#define S5P_CITRGFMT_VSIZE_MASK (0xfff << 0) +#define S5P_CITRGFMT_HSIZE(x) ((x) << 16) +#define S5P_CITRGFMT_VSIZE(x) ((x) << 0) + +/* Output DMA control */ +#define S5P_CIOCTRL 0x4c +#define S5P_CIOCTRL_ORDER422_MASK (3 << 0) +#define S5P_CIOCTRL_ORDER422_CRYCBY (0 << 0) +#define S5P_CIOCTRL_ORDER422_YCRYCB (1 << 0) +#define S5P_CIOCTRL_ORDER422_CBYCRY (2 << 0) +#define S5P_CIOCTRL_ORDER422_YCBYCR (3 << 0) +#define S5P_CIOCTRL_LASTIRQ_ENABLE (1 << 2) +#define S5P_CIOCTRL_YCBCR_3PLANE (0 << 3) +#define S5P_CIOCTRL_YCBCR_2PLANE (1 << 3) +#define S5P_CIOCTRL_YCBCR_PLANE_MASK (1 << 3) +#define S5P_CIOCTRL_ORDER2P_SHIFT (24) +#define S5P_CIOCTRL_ORDER2P_MASK (3 << 24) +#define S5P_CIOCTRL_ORDER422_2P_LSB_CRCB (0 << 24) + +/* Pre-scaler control 1 */ +#define S5P_CISCPRERATIO 0x50 +#define S5P_CISCPRERATIO_SHFACTOR(x) ((x) << 28) +#define S5P_CISCPRERATIO_HOR(x) ((x) << 16) +#define S5P_CISCPRERATIO_VER(x) ((x) << 0) + +#define S5P_CISCPREDST 0x54 +#define S5P_CISCPREDST_WIDTH(x) ((x) << 16) +#define S5P_CISCPREDST_HEIGHT(x) ((x) << 0) + +/* Main scaler control */ +#define S5P_CISCCTRL 0x58 +#define S5P_CISCCTRL_SCALERBYPASS (1 << 31) +#define S5P_CISCCTRL_SCALEUP_H (1 << 30) +#define S5P_CISCCTRL_SCALEUP_V (1 << 29) +#define S5P_CISCCTRL_CSCR2Y_WIDE (1 << 28) +#define S5P_CISCCTRL_CSCY2R_WIDE (1 << 27) +#define S5P_CISCCTRL_LCDPATHEN_FIFO (1 << 26) +#define S5P_CISCCTRL_INTERLACE (1 << 25) +#define S5P_CISCCTRL_SCALERSTART (1 << 15) +#define S5P_CISCCTRL_INRGB_FMT_RGB565 (0 << 13) +#define S5P_CISCCTRL_INRGB_FMT_RGB666 (1 << 13) +#define S5P_CISCCTRL_INRGB_FMT_RGB888 (2 << 13) +#define S5P_CISCCTRL_INRGB_FMT_MASK (3 << 13) +#define S5P_CISCCTRL_OUTRGB_FMT_RGB565 (0 << 11) +#define S5P_CISCCTRL_OUTRGB_FMT_RGB666 (1 << 11) +#define S5P_CISCCTRL_OUTRGB_FMT_RGB888 (2 << 11) +#define S5P_CISCCTRL_OUTRGB_FMT_MASK (3 << 11) +#define S5P_CISCCTRL_RGB_EXT (1 << 10) +#define S5P_CISCCTRL_ONE2ONE (1 << 9) +#define S5P_CISCCTRL_SC_HORRATIO(x) ((x) << 16) +#define S5P_CISCCTRL_SC_VERRATIO(x) ((x) << 0) + +/* Target area */ +#define S5P_CITAREA 0x5c +#define S5P_CITAREA_MASK 0x0fffffff + +/* General status */ +#define S5P_CISTATUS 0x64 +#define S5P_CISTATUS_OVFIY (1 << 31) +#define S5P_CISTATUS_OVFICB (1 << 30) +#define S5P_CISTATUS_OVFICR (1 << 29) +#define S5P_CISTATUS_VSYNC (1 << 28) +#define S5P_CISTATUS_WINOFF_EN (1 << 25) +#define S5P_CISTATUS_IMGCPT_EN (1 << 22) +#define S5P_CISTATUS_IMGCPT_SCEN (1 << 21) +#define S5P_CISTATUS_VSYNC_A (1 << 20) +#define S5P_CISTATUS_VSYNC_B (1 << 19) +#define S5P_CISTATUS_OVRLB (1 << 18) +#define S5P_CISTATUS_FRAME_END (1 << 17) +#define S5P_CISTATUS_LASTCAPT_END (1 << 16) +#define S5P_CISTATUS_VVALID_A (1 << 15) +#define S5P_CISTATUS_VVALID_B (1 << 14) + +/* Image capture control */ +#define S5P_CIIMGCPT 0xc0 +#define S5P_CIIMGCPT_IMGCPTEN (1 << 31) +#define S5P_CIIMGCPT_IMGCPTEN_SC (1 << 30) +#define S5P_CIIMGCPT_CPT_FREN_ENABLE (1 << 25) +#define S5P_CIIMGCPT_CPT_FRMOD_CNT (1 << 18) + +/* Frame capture sequence */ +#define S5P_CICPTSEQ 0xc4 + +/* Image effect */ +#define S5P_CIIMGEFF 0xd0 +#define S5P_CIIMGEFF_IE_DISABLE (0 << 30) +#define S5P_CIIMGEFF_IE_ENABLE (1 << 30) +#define S5P_CIIMGEFF_IE_SC_BEFORE (0 << 29) +#define S5P_CIIMGEFF_IE_SC_AFTER (1 << 29) +#define S5P_CIIMGEFF_FIN_BYPASS (0 << 26) +#define S5P_CIIMGEFF_FIN_ARBITRARY (1 << 26) +#define S5P_CIIMGEFF_FIN_NEGATIVE (2 << 26) +#define S5P_CIIMGEFF_FIN_ARTFREEZE (3 << 26) +#define S5P_CIIMGEFF_FIN_EMBOSSING (4 << 26) +#define S5P_CIIMGEFF_FIN_SILHOUETTE (5 << 26) +#define S5P_CIIMGEFF_FIN_MASK (7 << 26) +#define S5P_CIIMGEFF_PAT_CBCR_MASK ((0xff < 13) | (0xff < 0)) +#define S5P_CIIMGEFF_PAT_CB(x) ((x) << 13) +#define S5P_CIIMGEFF_PAT_CR(x) ((x) << 0) + +/* Input DMA Y/Cb/Cr plane start address 0 */ +#define S5P_CIIYSA0 0xd4 +#define S5P_CIICBSA0 0xd8 +#define S5P_CIICRSA0 0xdc + +/* Real input DMA image size */ +#define S5P_CIREAL_ISIZE 0xf8 +#define S5P_CIREAL_ISIZE_AUTOLOAD_EN (1 << 31) +#define S5P_CIREAL_ISIZE_ADDR_CH_DIS (1 << 30) +#define S5P_CIREAL_ISIZE_HEIGHT(x) ((x) << 16) +#define S5P_CIREAL_ISIZE_WIDTH(x) ((x) << 0) + + +/* Input DMA control */ +#define S5P_MSCTRL 0xfc +#define S5P_MSCTRL_IN_BURST_COUNT_MASK (3 << 24) +#define S5P_MSCTRL_2P_IN_ORDER_MASK (3 << 16) +#define S5P_MSCTRL_2P_IN_ORDER_SHIFT 16 +#define S5P_MSCTRL_C_INT_IN_3PLANE (0 << 15) +#define S5P_MSCTRL_C_INT_IN_2PLANE (1 << 15) +#define S5P_MSCTRL_C_INT_IN_MASK (1 << 15) +#define S5P_MSCTRL_FLIP_SHIFT 13 +#define S5P_MSCTRL_FLIP_MASK (3 << 13) +#define S5P_MSCTRL_FLIP_NORMAL (0 << 13) +#define S5P_MSCTRL_FLIP_X_MIRROR (1 << 13) +#define S5P_MSCTRL_FLIP_Y_MIRROR (2 << 13) +#define S5P_MSCTRL_FLIP_180 (3 << 13) +#define S5P_MSCTRL_ORDER422_SHIFT 4 +#define S5P_MSCTRL_ORDER422_CRYCBY (0 << 4) +#define S5P_MSCTRL_ORDER422_YCRYCB (1 << 4) +#define S5P_MSCTRL_ORDER422_CBYCRY (2 << 4) +#define S5P_MSCTRL_ORDER422_YCBYCR (3 << 4) +#define S5P_MSCTRL_ORDER422_MASK (3 << 4) +#define S5P_MSCTRL_INPUT_EXTCAM (0 << 3) +#define S5P_MSCTRL_INPUT_MEMORY (1 << 3) +#define S5P_MSCTRL_INPUT_MASK (1 << 3) +#define S5P_MSCTRL_INFORMAT_YCBCR420 (0 << 1) +#define S5P_MSCTRL_INFORMAT_YCBCR422 (1 << 1) +#define S5P_MSCTRL_INFORMAT_YCBCR422_1P (2 << 1) +#define S5P_MSCTRL_INFORMAT_RGB (3 << 1) +#define S5P_MSCTRL_INFORMAT_MASK (3 << 1) +#define S5P_MSCTRL_ENVID (1 << 0) +#define S5P_MSCTRL_FRAME_COUNT(x) ((x) << 24) + +/* Input DMA Y/Cb/Cr plane start address 1 */ +#define S5P_CIIYSA1 0x144 +#define S5P_CIICBSA1 0x148 +#define S5P_CIICRSA1 0x14c + +/* Output DMA Y/Cb/Cr offset */ +#define S5P_CIOYOFF 0x168 +#define S5P_CIOCBOFF 0x16c +#define S5P_CIOCROFF 0x170 + +/* Input DMA Y/Cb/Cr offset */ +#define S5P_CIIYOFF 0x174 +#define S5P_CIICBOFF 0x178 +#define S5P_CIICROFF 0x17c + +#define S5P_CIO_OFFS_VER(x) ((x) << 16) +#define S5P_CIO_OFFS_HOR(x) ((x) << 0) + +/* Input DMA original image size */ +#define S5P_ORGISIZE 0x180 + +/* Output DMA original image size */ +#define S5P_ORGOSIZE 0x184 + +#define S5P_ORIG_SIZE_VER(x) ((x) << 16) +#define S5P_ORIG_SIZE_HOR(x) ((x) << 0) + +/* Real output DMA image size (extension register) */ +#define S5P_CIEXTEN 0x188 + +#define S5P_CIDMAPARAM 0x18c +#define S5P_CIDMAPARAM_R_LINEAR (0 << 29) +#define S5P_CIDMAPARAM_R_64X32 (3 << 29) +#define S5P_CIDMAPARAM_W_LINEAR (0 << 13) +#define S5P_CIDMAPARAM_W_64X32 (3 << 13) +#define S5P_CIDMAPARAM_TILE_MASK ((3 << 29) | (3 << 13)) + +/* MIPI CSI image format */ +#define S5P_CSIIMGFMT 0x194 + +#endif /* REGS_FIMC_H_ */ diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h index 047f7e6edb8..61490c6dcdb 100644 --- a/include/linux/videodev2.h +++ b/include/linux/videodev2.h @@ -277,6 +277,7 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_RGB565 v4l2_fourcc('R', 'G', 'B', 'P') /* 16 RGB-5-6-5 */ #define V4L2_PIX_FMT_RGB555X v4l2_fourcc('R', 'G', 'B', 'Q') /* 16 RGB-5-5-5 BE */ #define V4L2_PIX_FMT_RGB565X v4l2_fourcc('R', 'G', 'B', 'R') /* 16 RGB-5-6-5 BE */ +#define V4L2_PIX_FMT_BGR666 v4l2_fourcc('B', 'G', 'R', 'H') /* 18 BGR-6-6-6 */ #define V4L2_PIX_FMT_BGR24 v4l2_fourcc('B', 'G', 'R', '3') /* 24 BGR-8-8-8 */ #define V4L2_PIX_FMT_RGB24 v4l2_fourcc('R', 'G', 'B', '3') /* 24 RGB-8-8-8 */ #define V4L2_PIX_FMT_BGR32 v4l2_fourcc('B', 'G', 'R', '4') /* 32 BGR-8-8-8-8 */ -- cgit v1.2.3-70-g09d2 From 0996517cf8eaded69b8502c8f5abeb8cec62b6d4 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Sun, 1 Aug 2010 14:32:42 -0300 Subject: V4L/DVB: v4l2: Add new control handling framework Add a new framework to handle controls which makes life for driver developers much easier. Note that this patch moves some of the control support that used to be in v4l2-common.c to v4l2-ctrls.c. The tables were copied unchanged. The body of v4l2_ctrl_query_fill() was copied to a new v4l2_ctrl_fill() function in v4l2-ctrls.c. This new function doesn't use the v4l2_queryctrl struct anymore, which makes it more general. The remainder of v4l2-ctrls.c is all new. Highlights include: - No need to implement VIDIOC_QUERYCTRL, QUERYMENU, S_CTRL, G_CTRL, S_EXT_CTRLS, G_EXT_CTRLS or TRY_EXT_CTRLS in either bridge drivers or subdevs. New wrapper functions are provided that can just be plugged in. Once everything has been converted these wrapper functions can be removed as well. - When subdevices are added their controls can be automatically merged with the bridge driver's controls. - Most drivers just need to implement s_ctrl to set the controls. The framework handles the locking and tries to be as 'atomic' as possible. - Ready for the subdev device nodes: the same mechanism applies to subdevs and their device nodes as well. Sub-device drivers can make controls local, preventing them from being merged with bridge drivers. - Takes care of backwards compatibility handling of VIDIOC_S_CTRL and VIDIOC_G_CTRL. Handling of V4L2_CID_PRIVATE_BASE is fully transparent. CTRL_CLASS controls are automatically added. Signed-off-by: Hans Verkuil Reviewed-by: Laurent Pinchart Signed-off-by: Mauro Carvalho Chehab --- drivers/media/video/Makefile | 2 +- drivers/media/video/v4l2-common.c | 479 +--------- drivers/media/video/v4l2-ctrls.c | 1847 +++++++++++++++++++++++++++++++++++++ include/media/v4l2-ctrls.h | 460 +++++++++ include/media/v4l2-dev.h | 4 + include/media/v4l2-device.h | 4 + include/media/v4l2-subdev.h | 3 + 7 files changed, 2324 insertions(+), 475 deletions(-) create mode 100644 drivers/media/video/v4l2-ctrls.c create mode 100644 include/media/v4l2-ctrls.h (limited to 'include') diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index c76fef39079..40f98fba5f8 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -11,7 +11,7 @@ stkwebcam-objs := stk-webcam.o stk-sensor.o omap2cam-objs := omap24xxcam.o omap24xxcam-dma.o videodev-objs := v4l2-dev.o v4l2-ioctl.o v4l2-device.o v4l2-fh.o \ - v4l2-event.o + v4l2-event.o v4l2-ctrls.o # V4L2 core modules diff --git a/drivers/media/video/v4l2-common.c b/drivers/media/video/v4l2-common.c index 4e53b0b3339..3ce7c64e578 100644 --- a/drivers/media/video/v4l2-common.c +++ b/drivers/media/video/v4l2-common.c @@ -62,6 +62,7 @@ #define __OLD_VIDIOC_ /* To allow fixing old calls*/ #include #include +#include #include #include @@ -172,487 +173,17 @@ int v4l2_ctrl_check(struct v4l2_ext_control *ctrl, struct v4l2_queryctrl *qctrl, } EXPORT_SYMBOL(v4l2_ctrl_check); -/* Returns NULL or a character pointer array containing the menu for - the given control ID. The pointer array ends with a NULL pointer. - An empty string signifies a menu entry that is invalid. This allows - drivers to disable certain options if it is not supported. */ -const char **v4l2_ctrl_get_menu(u32 id) -{ - static const char *mpeg_audio_sampling_freq[] = { - "44.1 kHz", - "48 kHz", - "32 kHz", - NULL - }; - static const char *mpeg_audio_encoding[] = { - "MPEG-1/2 Layer I", - "MPEG-1/2 Layer II", - "MPEG-1/2 Layer III", - "MPEG-2/4 AAC", - "AC-3", - NULL - }; - static const char *mpeg_audio_l1_bitrate[] = { - "32 kbps", - "64 kbps", - "96 kbps", - "128 kbps", - "160 kbps", - "192 kbps", - "224 kbps", - "256 kbps", - "288 kbps", - "320 kbps", - "352 kbps", - "384 kbps", - "416 kbps", - "448 kbps", - NULL - }; - static const char *mpeg_audio_l2_bitrate[] = { - "32 kbps", - "48 kbps", - "56 kbps", - "64 kbps", - "80 kbps", - "96 kbps", - "112 kbps", - "128 kbps", - "160 kbps", - "192 kbps", - "224 kbps", - "256 kbps", - "320 kbps", - "384 kbps", - NULL - }; - static const char *mpeg_audio_l3_bitrate[] = { - "32 kbps", - "40 kbps", - "48 kbps", - "56 kbps", - "64 kbps", - "80 kbps", - "96 kbps", - "112 kbps", - "128 kbps", - "160 kbps", - "192 kbps", - "224 kbps", - "256 kbps", - "320 kbps", - NULL - }; - static const char *mpeg_audio_ac3_bitrate[] = { - "32 kbps", - "40 kbps", - "48 kbps", - "56 kbps", - "64 kbps", - "80 kbps", - "96 kbps", - "112 kbps", - "128 kbps", - "160 kbps", - "192 kbps", - "224 kbps", - "256 kbps", - "320 kbps", - "384 kbps", - "448 kbps", - "512 kbps", - "576 kbps", - "640 kbps", - NULL - }; - static const char *mpeg_audio_mode[] = { - "Stereo", - "Joint Stereo", - "Dual", - "Mono", - NULL - }; - static const char *mpeg_audio_mode_extension[] = { - "Bound 4", - "Bound 8", - "Bound 12", - "Bound 16", - NULL - }; - static const char *mpeg_audio_emphasis[] = { - "No Emphasis", - "50/15 us", - "CCITT J17", - NULL - }; - static const char *mpeg_audio_crc[] = { - "No CRC", - "16-bit CRC", - NULL - }; - static const char *mpeg_video_encoding[] = { - "MPEG-1", - "MPEG-2", - "MPEG-4 AVC", - NULL - }; - static const char *mpeg_video_aspect[] = { - "1x1", - "4x3", - "16x9", - "2.21x1", - NULL - }; - static const char *mpeg_video_bitrate_mode[] = { - "Variable Bitrate", - "Constant Bitrate", - NULL - }; - static const char *mpeg_stream_type[] = { - "MPEG-2 Program Stream", - "MPEG-2 Transport Stream", - "MPEG-1 System Stream", - "MPEG-2 DVD-compatible Stream", - "MPEG-1 VCD-compatible Stream", - "MPEG-2 SVCD-compatible Stream", - NULL - }; - static const char *mpeg_stream_vbi_fmt[] = { - "No VBI", - "Private packet, IVTV format", - NULL - }; - static const char *camera_power_line_frequency[] = { - "Disabled", - "50 Hz", - "60 Hz", - NULL - }; - static const char *camera_exposure_auto[] = { - "Auto Mode", - "Manual Mode", - "Shutter Priority Mode", - "Aperture Priority Mode", - NULL - }; - static const char *colorfx[] = { - "None", - "Black & White", - "Sepia", - "Negative", - "Emboss", - "Sketch", - "Sky blue", - "Grass green", - "Skin whiten", - "Vivid", - NULL - }; - static const char *tune_preemphasis[] = { - "No preemphasis", - "50 useconds", - "75 useconds", - NULL, - }; - - switch (id) { - case V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ: - return mpeg_audio_sampling_freq; - case V4L2_CID_MPEG_AUDIO_ENCODING: - return mpeg_audio_encoding; - case V4L2_CID_MPEG_AUDIO_L1_BITRATE: - return mpeg_audio_l1_bitrate; - case V4L2_CID_MPEG_AUDIO_L2_BITRATE: - return mpeg_audio_l2_bitrate; - case V4L2_CID_MPEG_AUDIO_L3_BITRATE: - return mpeg_audio_l3_bitrate; - case V4L2_CID_MPEG_AUDIO_AC3_BITRATE: - return mpeg_audio_ac3_bitrate; - case V4L2_CID_MPEG_AUDIO_MODE: - return mpeg_audio_mode; - case V4L2_CID_MPEG_AUDIO_MODE_EXTENSION: - return mpeg_audio_mode_extension; - case V4L2_CID_MPEG_AUDIO_EMPHASIS: - return mpeg_audio_emphasis; - case V4L2_CID_MPEG_AUDIO_CRC: - return mpeg_audio_crc; - case V4L2_CID_MPEG_VIDEO_ENCODING: - return mpeg_video_encoding; - case V4L2_CID_MPEG_VIDEO_ASPECT: - return mpeg_video_aspect; - case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: - return mpeg_video_bitrate_mode; - case V4L2_CID_MPEG_STREAM_TYPE: - return mpeg_stream_type; - case V4L2_CID_MPEG_STREAM_VBI_FMT: - return mpeg_stream_vbi_fmt; - case V4L2_CID_POWER_LINE_FREQUENCY: - return camera_power_line_frequency; - case V4L2_CID_EXPOSURE_AUTO: - return camera_exposure_auto; - case V4L2_CID_COLORFX: - return colorfx; - case V4L2_CID_TUNE_PREEMPHASIS: - return tune_preemphasis; - default: - return NULL; - } -} -EXPORT_SYMBOL(v4l2_ctrl_get_menu); - -/* Return the control name. */ -const char *v4l2_ctrl_get_name(u32 id) -{ - switch (id) { - /* USER controls */ - case V4L2_CID_USER_CLASS: return "User Controls"; - case V4L2_CID_BRIGHTNESS: return "Brightness"; - case V4L2_CID_CONTRAST: return "Contrast"; - case V4L2_CID_SATURATION: return "Saturation"; - case V4L2_CID_HUE: return "Hue"; - case V4L2_CID_AUDIO_VOLUME: return "Volume"; - case V4L2_CID_AUDIO_BALANCE: return "Balance"; - case V4L2_CID_AUDIO_BASS: return "Bass"; - case V4L2_CID_AUDIO_TREBLE: return "Treble"; - case V4L2_CID_AUDIO_MUTE: return "Mute"; - case V4L2_CID_AUDIO_LOUDNESS: return "Loudness"; - case V4L2_CID_BLACK_LEVEL: return "Black Level"; - case V4L2_CID_AUTO_WHITE_BALANCE: return "White Balance, Automatic"; - case V4L2_CID_DO_WHITE_BALANCE: return "Do White Balance"; - case V4L2_CID_RED_BALANCE: return "Red Balance"; - case V4L2_CID_BLUE_BALANCE: return "Blue Balance"; - case V4L2_CID_GAMMA: return "Gamma"; - case V4L2_CID_EXPOSURE: return "Exposure"; - case V4L2_CID_AUTOGAIN: return "Gain, Automatic"; - case V4L2_CID_GAIN: return "Gain"; - case V4L2_CID_HFLIP: return "Horizontal Flip"; - case V4L2_CID_VFLIP: return "Vertical Flip"; - case V4L2_CID_HCENTER: return "Horizontal Center"; - case V4L2_CID_VCENTER: return "Vertical Center"; - case V4L2_CID_POWER_LINE_FREQUENCY: return "Power Line Frequency"; - case V4L2_CID_HUE_AUTO: return "Hue, Automatic"; - case V4L2_CID_WHITE_BALANCE_TEMPERATURE: return "White Balance Temperature"; - case V4L2_CID_SHARPNESS: return "Sharpness"; - case V4L2_CID_BACKLIGHT_COMPENSATION: return "Backlight Compensation"; - case V4L2_CID_CHROMA_AGC: return "Chroma AGC"; - case V4L2_CID_CHROMA_GAIN: return "Chroma Gain"; - case V4L2_CID_COLOR_KILLER: return "Color Killer"; - case V4L2_CID_COLORFX: return "Color Effects"; - case V4L2_CID_AUTOBRIGHTNESS: return "Brightness, Automatic"; - case V4L2_CID_BAND_STOP_FILTER: return "Band-Stop Filter"; - case V4L2_CID_ROTATE: return "Rotate"; - case V4L2_CID_BG_COLOR: return "Background Color"; - - /* MPEG controls */ - case V4L2_CID_MPEG_CLASS: return "MPEG Encoder Controls"; - case V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ: return "Audio Sampling Frequency"; - case V4L2_CID_MPEG_AUDIO_ENCODING: return "Audio Encoding"; - case V4L2_CID_MPEG_AUDIO_L1_BITRATE: return "Audio Layer I Bitrate"; - case V4L2_CID_MPEG_AUDIO_L2_BITRATE: return "Audio Layer II Bitrate"; - case V4L2_CID_MPEG_AUDIO_L3_BITRATE: return "Audio Layer III Bitrate"; - case V4L2_CID_MPEG_AUDIO_AAC_BITRATE: return "Audio AAC Bitrate"; - case V4L2_CID_MPEG_AUDIO_AC3_BITRATE: return "Audio AC-3 Bitrate"; - case V4L2_CID_MPEG_AUDIO_MODE: return "Audio Stereo Mode"; - case V4L2_CID_MPEG_AUDIO_MODE_EXTENSION: return "Audio Stereo Mode Extension"; - case V4L2_CID_MPEG_AUDIO_EMPHASIS: return "Audio Emphasis"; - case V4L2_CID_MPEG_AUDIO_CRC: return "Audio CRC"; - case V4L2_CID_MPEG_AUDIO_MUTE: return "Audio Mute"; - case V4L2_CID_MPEG_VIDEO_ENCODING: return "Video Encoding"; - case V4L2_CID_MPEG_VIDEO_ASPECT: return "Video Aspect"; - case V4L2_CID_MPEG_VIDEO_B_FRAMES: return "Video B Frames"; - case V4L2_CID_MPEG_VIDEO_GOP_SIZE: return "Video GOP Size"; - case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE: return "Video GOP Closure"; - case V4L2_CID_MPEG_VIDEO_PULLDOWN: return "Video Pulldown"; - case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: return "Video Bitrate Mode"; - case V4L2_CID_MPEG_VIDEO_BITRATE: return "Video Bitrate"; - case V4L2_CID_MPEG_VIDEO_BITRATE_PEAK: return "Video Peak Bitrate"; - case V4L2_CID_MPEG_VIDEO_TEMPORAL_DECIMATION: return "Video Temporal Decimation"; - case V4L2_CID_MPEG_VIDEO_MUTE: return "Video Mute"; - case V4L2_CID_MPEG_VIDEO_MUTE_YUV: return "Video Mute YUV"; - case V4L2_CID_MPEG_STREAM_TYPE: return "Stream Type"; - case V4L2_CID_MPEG_STREAM_PID_PMT: return "Stream PMT Program ID"; - case V4L2_CID_MPEG_STREAM_PID_AUDIO: return "Stream Audio Program ID"; - case V4L2_CID_MPEG_STREAM_PID_VIDEO: return "Stream Video Program ID"; - case V4L2_CID_MPEG_STREAM_PID_PCR: return "Stream PCR Program ID"; - case V4L2_CID_MPEG_STREAM_PES_ID_AUDIO: return "Stream PES Audio ID"; - case V4L2_CID_MPEG_STREAM_PES_ID_VIDEO: return "Stream PES Video ID"; - case V4L2_CID_MPEG_STREAM_VBI_FMT: return "Stream VBI Format"; - - /* CAMERA controls */ - case V4L2_CID_CAMERA_CLASS: return "Camera Controls"; - case V4L2_CID_EXPOSURE_AUTO: return "Auto Exposure"; - case V4L2_CID_EXPOSURE_ABSOLUTE: return "Exposure Time, Absolute"; - case V4L2_CID_EXPOSURE_AUTO_PRIORITY: return "Exposure, Dynamic Framerate"; - case V4L2_CID_PAN_RELATIVE: return "Pan, Relative"; - case V4L2_CID_TILT_RELATIVE: return "Tilt, Relative"; - case V4L2_CID_PAN_RESET: return "Pan, Reset"; - case V4L2_CID_TILT_RESET: return "Tilt, Reset"; - case V4L2_CID_PAN_ABSOLUTE: return "Pan, Absolute"; - case V4L2_CID_TILT_ABSOLUTE: return "Tilt, Absolute"; - case V4L2_CID_FOCUS_ABSOLUTE: return "Focus, Absolute"; - case V4L2_CID_FOCUS_RELATIVE: return "Focus, Relative"; - case V4L2_CID_FOCUS_AUTO: return "Focus, Automatic"; - case V4L2_CID_IRIS_ABSOLUTE: return "Iris, Absolute"; - case V4L2_CID_IRIS_RELATIVE: return "Iris, Relative"; - case V4L2_CID_ZOOM_ABSOLUTE: return "Zoom, Absolute"; - case V4L2_CID_ZOOM_RELATIVE: return "Zoom, Relative"; - case V4L2_CID_ZOOM_CONTINUOUS: return "Zoom, Continuous"; - case V4L2_CID_PRIVACY: return "Privacy"; - - /* FM Radio Modulator control */ - case V4L2_CID_FM_TX_CLASS: return "FM Radio Modulator Controls"; - case V4L2_CID_RDS_TX_DEVIATION: return "RDS Signal Deviation"; - case V4L2_CID_RDS_TX_PI: return "RDS Program ID"; - case V4L2_CID_RDS_TX_PTY: return "RDS Program Type"; - case V4L2_CID_RDS_TX_PS_NAME: return "RDS PS Name"; - case V4L2_CID_RDS_TX_RADIO_TEXT: return "RDS Radio Text"; - case V4L2_CID_AUDIO_LIMITER_ENABLED: return "Audio Limiter Feature Enabled"; - case V4L2_CID_AUDIO_LIMITER_RELEASE_TIME: return "Audio Limiter Release Time"; - case V4L2_CID_AUDIO_LIMITER_DEVIATION: return "Audio Limiter Deviation"; - case V4L2_CID_AUDIO_COMPRESSION_ENABLED: return "Audio Compression Feature Enabled"; - case V4L2_CID_AUDIO_COMPRESSION_GAIN: return "Audio Compression Gain"; - case V4L2_CID_AUDIO_COMPRESSION_THRESHOLD: return "Audio Compression Threshold"; - case V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME: return "Audio Compression Attack Time"; - case V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME: return "Audio Compression Release Time"; - case V4L2_CID_PILOT_TONE_ENABLED: return "Pilot Tone Feature Enabled"; - case V4L2_CID_PILOT_TONE_DEVIATION: return "Pilot Tone Deviation"; - case V4L2_CID_PILOT_TONE_FREQUENCY: return "Pilot Tone Frequency"; - case V4L2_CID_TUNE_PREEMPHASIS: return "Pre-emphasis settings"; - case V4L2_CID_TUNE_POWER_LEVEL: return "Tune Power Level"; - case V4L2_CID_TUNE_ANTENNA_CAPACITOR: return "Tune Antenna Capacitor"; - - default: - return NULL; - } -} -EXPORT_SYMBOL(v4l2_ctrl_get_name); - /* Fill in a struct v4l2_queryctrl */ int v4l2_ctrl_query_fill(struct v4l2_queryctrl *qctrl, s32 min, s32 max, s32 step, s32 def) { - const char *name = v4l2_ctrl_get_name(qctrl->id); + const char *name; + + v4l2_ctrl_fill(qctrl->id, &name, &qctrl->type, + &min, &max, &step, &def, &qctrl->flags); - qctrl->flags = 0; if (name == NULL) return -EINVAL; - switch (qctrl->id) { - case V4L2_CID_AUDIO_MUTE: - case V4L2_CID_AUDIO_LOUDNESS: - case V4L2_CID_AUTO_WHITE_BALANCE: - case V4L2_CID_AUTOGAIN: - case V4L2_CID_HFLIP: - case V4L2_CID_VFLIP: - case V4L2_CID_HUE_AUTO: - case V4L2_CID_CHROMA_AGC: - case V4L2_CID_COLOR_KILLER: - case V4L2_CID_MPEG_AUDIO_MUTE: - case V4L2_CID_MPEG_VIDEO_MUTE: - case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE: - case V4L2_CID_MPEG_VIDEO_PULLDOWN: - case V4L2_CID_EXPOSURE_AUTO_PRIORITY: - case V4L2_CID_FOCUS_AUTO: - case V4L2_CID_PRIVACY: - case V4L2_CID_AUDIO_LIMITER_ENABLED: - case V4L2_CID_AUDIO_COMPRESSION_ENABLED: - case V4L2_CID_PILOT_TONE_ENABLED: - qctrl->type = V4L2_CTRL_TYPE_BOOLEAN; - min = 0; - max = step = 1; - break; - case V4L2_CID_PAN_RESET: - case V4L2_CID_TILT_RESET: - qctrl->type = V4L2_CTRL_TYPE_BUTTON; - qctrl->flags |= V4L2_CTRL_FLAG_WRITE_ONLY; - min = max = step = def = 0; - break; - case V4L2_CID_POWER_LINE_FREQUENCY: - case V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ: - case V4L2_CID_MPEG_AUDIO_ENCODING: - case V4L2_CID_MPEG_AUDIO_L1_BITRATE: - case V4L2_CID_MPEG_AUDIO_L2_BITRATE: - case V4L2_CID_MPEG_AUDIO_L3_BITRATE: - case V4L2_CID_MPEG_AUDIO_AC3_BITRATE: - case V4L2_CID_MPEG_AUDIO_MODE: - case V4L2_CID_MPEG_AUDIO_MODE_EXTENSION: - case V4L2_CID_MPEG_AUDIO_EMPHASIS: - case V4L2_CID_MPEG_AUDIO_CRC: - case V4L2_CID_MPEG_VIDEO_ENCODING: - case V4L2_CID_MPEG_VIDEO_ASPECT: - case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: - case V4L2_CID_MPEG_STREAM_TYPE: - case V4L2_CID_MPEG_STREAM_VBI_FMT: - case V4L2_CID_EXPOSURE_AUTO: - case V4L2_CID_COLORFX: - case V4L2_CID_TUNE_PREEMPHASIS: - qctrl->type = V4L2_CTRL_TYPE_MENU; - step = 1; - break; - case V4L2_CID_RDS_TX_PS_NAME: - case V4L2_CID_RDS_TX_RADIO_TEXT: - qctrl->type = V4L2_CTRL_TYPE_STRING; - break; - case V4L2_CID_USER_CLASS: - case V4L2_CID_CAMERA_CLASS: - case V4L2_CID_MPEG_CLASS: - case V4L2_CID_FM_TX_CLASS: - qctrl->type = V4L2_CTRL_TYPE_CTRL_CLASS; - qctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; - min = max = step = def = 0; - break; - case V4L2_CID_BG_COLOR: - qctrl->type = V4L2_CTRL_TYPE_INTEGER; - step = 1; - min = 0; - /* Max is calculated as RGB888 that is 2^24 */ - max = 0xFFFFFF; - break; - default: - qctrl->type = V4L2_CTRL_TYPE_INTEGER; - break; - } - switch (qctrl->id) { - case V4L2_CID_MPEG_AUDIO_ENCODING: - case V4L2_CID_MPEG_AUDIO_MODE: - case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: - case V4L2_CID_MPEG_VIDEO_B_FRAMES: - case V4L2_CID_MPEG_STREAM_TYPE: - qctrl->flags |= V4L2_CTRL_FLAG_UPDATE; - break; - case V4L2_CID_AUDIO_VOLUME: - case V4L2_CID_AUDIO_BALANCE: - case V4L2_CID_AUDIO_BASS: - case V4L2_CID_AUDIO_TREBLE: - case V4L2_CID_BRIGHTNESS: - case V4L2_CID_CONTRAST: - case V4L2_CID_SATURATION: - case V4L2_CID_HUE: - case V4L2_CID_RED_BALANCE: - case V4L2_CID_BLUE_BALANCE: - case V4L2_CID_GAMMA: - case V4L2_CID_SHARPNESS: - case V4L2_CID_CHROMA_GAIN: - case V4L2_CID_RDS_TX_DEVIATION: - case V4L2_CID_AUDIO_LIMITER_RELEASE_TIME: - case V4L2_CID_AUDIO_LIMITER_DEVIATION: - case V4L2_CID_AUDIO_COMPRESSION_GAIN: - case V4L2_CID_AUDIO_COMPRESSION_THRESHOLD: - case V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME: - case V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME: - case V4L2_CID_PILOT_TONE_DEVIATION: - case V4L2_CID_PILOT_TONE_FREQUENCY: - case V4L2_CID_TUNE_POWER_LEVEL: - case V4L2_CID_TUNE_ANTENNA_CAPACITOR: - qctrl->flags |= V4L2_CTRL_FLAG_SLIDER; - break; - case V4L2_CID_PAN_RELATIVE: - case V4L2_CID_TILT_RELATIVE: - case V4L2_CID_FOCUS_RELATIVE: - case V4L2_CID_IRIS_RELATIVE: - case V4L2_CID_ZOOM_RELATIVE: - qctrl->flags |= V4L2_CTRL_FLAG_WRITE_ONLY; - break; - } qctrl->minimum = min; qctrl->maximum = max; qctrl->step = step; diff --git a/drivers/media/video/v4l2-ctrls.c b/drivers/media/video/v4l2-ctrls.c new file mode 100644 index 00000000000..8489894ee42 --- /dev/null +++ b/drivers/media/video/v4l2-ctrls.c @@ -0,0 +1,1847 @@ +/* + V4L2 controls framework implementation. + + Copyright (C) 2010 Hans Verkuil + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include + +/* Internal temporary helper struct, one for each v4l2_ext_control */ +struct ctrl_helper { + /* The control corresponding to the v4l2_ext_control ID field. */ + struct v4l2_ctrl *ctrl; + /* Used internally to mark whether this control was already + processed. */ + bool handled; +}; + +/* Returns NULL or a character pointer array containing the menu for + the given control ID. The pointer array ends with a NULL pointer. + An empty string signifies a menu entry that is invalid. This allows + drivers to disable certain options if it is not supported. */ +const char **v4l2_ctrl_get_menu(u32 id) +{ + static const char *mpeg_audio_sampling_freq[] = { + "44.1 kHz", + "48 kHz", + "32 kHz", + NULL + }; + static const char *mpeg_audio_encoding[] = { + "MPEG-1/2 Layer I", + "MPEG-1/2 Layer II", + "MPEG-1/2 Layer III", + "MPEG-2/4 AAC", + "AC-3", + NULL + }; + static const char *mpeg_audio_l1_bitrate[] = { + "32 kbps", + "64 kbps", + "96 kbps", + "128 kbps", + "160 kbps", + "192 kbps", + "224 kbps", + "256 kbps", + "288 kbps", + "320 kbps", + "352 kbps", + "384 kbps", + "416 kbps", + "448 kbps", + NULL + }; + static const char *mpeg_audio_l2_bitrate[] = { + "32 kbps", + "48 kbps", + "56 kbps", + "64 kbps", + "80 kbps", + "96 kbps", + "112 kbps", + "128 kbps", + "160 kbps", + "192 kbps", + "224 kbps", + "256 kbps", + "320 kbps", + "384 kbps", + NULL + }; + static const char *mpeg_audio_l3_bitrate[] = { + "32 kbps", + "40 kbps", + "48 kbps", + "56 kbps", + "64 kbps", + "80 kbps", + "96 kbps", + "112 kbps", + "128 kbps", + "160 kbps", + "192 kbps", + "224 kbps", + "256 kbps", + "320 kbps", + NULL + }; + static const char *mpeg_audio_ac3_bitrate[] = { + "32 kbps", + "40 kbps", + "48 kbps", + "56 kbps", + "64 kbps", + "80 kbps", + "96 kbps", + "112 kbps", + "128 kbps", + "160 kbps", + "192 kbps", + "224 kbps", + "256 kbps", + "320 kbps", + "384 kbps", + "448 kbps", + "512 kbps", + "576 kbps", + "640 kbps", + NULL + }; + static const char *mpeg_audio_mode[] = { + "Stereo", + "Joint Stereo", + "Dual", + "Mono", + NULL + }; + static const char *mpeg_audio_mode_extension[] = { + "Bound 4", + "Bound 8", + "Bound 12", + "Bound 16", + NULL + }; + static const char *mpeg_audio_emphasis[] = { + "No Emphasis", + "50/15 us", + "CCITT J17", + NULL + }; + static const char *mpeg_audio_crc[] = { + "No CRC", + "16-bit CRC", + NULL + }; + static const char *mpeg_video_encoding[] = { + "MPEG-1", + "MPEG-2", + "MPEG-4 AVC", + NULL + }; + static const char *mpeg_video_aspect[] = { + "1x1", + "4x3", + "16x9", + "2.21x1", + NULL + }; + static const char *mpeg_video_bitrate_mode[] = { + "Variable Bitrate", + "Constant Bitrate", + NULL + }; + static const char *mpeg_stream_type[] = { + "MPEG-2 Program Stream", + "MPEG-2 Transport Stream", + "MPEG-1 System Stream", + "MPEG-2 DVD-compatible Stream", + "MPEG-1 VCD-compatible Stream", + "MPEG-2 SVCD-compatible Stream", + NULL + }; + static const char *mpeg_stream_vbi_fmt[] = { + "No VBI", + "Private packet, IVTV format", + NULL + }; + static const char *camera_power_line_frequency[] = { + "Disabled", + "50 Hz", + "60 Hz", + NULL + }; + static const char *camera_exposure_auto[] = { + "Auto Mode", + "Manual Mode", + "Shutter Priority Mode", + "Aperture Priority Mode", + NULL + }; + static const char *colorfx[] = { + "None", + "Black & White", + "Sepia", + "Negative", + "Emboss", + "Sketch", + "Sky blue", + "Grass green", + "Skin whiten", + "Vivid", + NULL + }; + static const char *tune_preemphasis[] = { + "No preemphasis", + "50 useconds", + "75 useconds", + NULL, + }; + + switch (id) { + case V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ: + return mpeg_audio_sampling_freq; + case V4L2_CID_MPEG_AUDIO_ENCODING: + return mpeg_audio_encoding; + case V4L2_CID_MPEG_AUDIO_L1_BITRATE: + return mpeg_audio_l1_bitrate; + case V4L2_CID_MPEG_AUDIO_L2_BITRATE: + return mpeg_audio_l2_bitrate; + case V4L2_CID_MPEG_AUDIO_L3_BITRATE: + return mpeg_audio_l3_bitrate; + case V4L2_CID_MPEG_AUDIO_AC3_BITRATE: + return mpeg_audio_ac3_bitrate; + case V4L2_CID_MPEG_AUDIO_MODE: + return mpeg_audio_mode; + case V4L2_CID_MPEG_AUDIO_MODE_EXTENSION: + return mpeg_audio_mode_extension; + case V4L2_CID_MPEG_AUDIO_EMPHASIS: + return mpeg_audio_emphasis; + case V4L2_CID_MPEG_AUDIO_CRC: + return mpeg_audio_crc; + case V4L2_CID_MPEG_VIDEO_ENCODING: + return mpeg_video_encoding; + case V4L2_CID_MPEG_VIDEO_ASPECT: + return mpeg_video_aspect; + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + return mpeg_video_bitrate_mode; + case V4L2_CID_MPEG_STREAM_TYPE: + return mpeg_stream_type; + case V4L2_CID_MPEG_STREAM_VBI_FMT: + return mpeg_stream_vbi_fmt; + case V4L2_CID_POWER_LINE_FREQUENCY: + return camera_power_line_frequency; + case V4L2_CID_EXPOSURE_AUTO: + return camera_exposure_auto; + case V4L2_CID_COLORFX: + return colorfx; + case V4L2_CID_TUNE_PREEMPHASIS: + return tune_preemphasis; + default: + return NULL; + } +} +EXPORT_SYMBOL(v4l2_ctrl_get_menu); + +/* Return the control name. */ +const char *v4l2_ctrl_get_name(u32 id) +{ + switch (id) { + /* USER controls */ + case V4L2_CID_USER_CLASS: return "User Controls"; + case V4L2_CID_BRIGHTNESS: return "Brightness"; + case V4L2_CID_CONTRAST: return "Contrast"; + case V4L2_CID_SATURATION: return "Saturation"; + case V4L2_CID_HUE: return "Hue"; + case V4L2_CID_AUDIO_VOLUME: return "Volume"; + case V4L2_CID_AUDIO_BALANCE: return "Balance"; + case V4L2_CID_AUDIO_BASS: return "Bass"; + case V4L2_CID_AUDIO_TREBLE: return "Treble"; + case V4L2_CID_AUDIO_MUTE: return "Mute"; + case V4L2_CID_AUDIO_LOUDNESS: return "Loudness"; + case V4L2_CID_BLACK_LEVEL: return "Black Level"; + case V4L2_CID_AUTO_WHITE_BALANCE: return "White Balance, Automatic"; + case V4L2_CID_DO_WHITE_BALANCE: return "Do White Balance"; + case V4L2_CID_RED_BALANCE: return "Red Balance"; + case V4L2_CID_BLUE_BALANCE: return "Blue Balance"; + case V4L2_CID_GAMMA: return "Gamma"; + case V4L2_CID_EXPOSURE: return "Exposure"; + case V4L2_CID_AUTOGAIN: return "Gain, Automatic"; + case V4L2_CID_GAIN: return "Gain"; + case V4L2_CID_HFLIP: return "Horizontal Flip"; + case V4L2_CID_VFLIP: return "Vertical Flip"; + case V4L2_CID_HCENTER: return "Horizontal Center"; + case V4L2_CID_VCENTER: return "Vertical Center"; + case V4L2_CID_POWER_LINE_FREQUENCY: return "Power Line Frequency"; + case V4L2_CID_HUE_AUTO: return "Hue, Automatic"; + case V4L2_CID_WHITE_BALANCE_TEMPERATURE: return "White Balance Temperature"; + case V4L2_CID_SHARPNESS: return "Sharpness"; + case V4L2_CID_BACKLIGHT_COMPENSATION: return "Backlight Compensation"; + case V4L2_CID_CHROMA_AGC: return "Chroma AGC"; + case V4L2_CID_CHROMA_GAIN: return "Chroma Gain"; + case V4L2_CID_COLOR_KILLER: return "Color Killer"; + case V4L2_CID_COLORFX: return "Color Effects"; + case V4L2_CID_AUTOBRIGHTNESS: return "Brightness, Automatic"; + case V4L2_CID_BAND_STOP_FILTER: return "Band-Stop Filter"; + case V4L2_CID_ROTATE: return "Rotate"; + case V4L2_CID_BG_COLOR: return "Background Color"; + + /* MPEG controls */ + case V4L2_CID_MPEG_CLASS: return "MPEG Encoder Controls"; + case V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ: return "Audio Sampling Frequency"; + case V4L2_CID_MPEG_AUDIO_ENCODING: return "Audio Encoding"; + case V4L2_CID_MPEG_AUDIO_L1_BITRATE: return "Audio Layer I Bitrate"; + case V4L2_CID_MPEG_AUDIO_L2_BITRATE: return "Audio Layer II Bitrate"; + case V4L2_CID_MPEG_AUDIO_L3_BITRATE: return "Audio Layer III Bitrate"; + case V4L2_CID_MPEG_AUDIO_AAC_BITRATE: return "Audio AAC Bitrate"; + case V4L2_CID_MPEG_AUDIO_AC3_BITRATE: return "Audio AC-3 Bitrate"; + case V4L2_CID_MPEG_AUDIO_MODE: return "Audio Stereo Mode"; + case V4L2_CID_MPEG_AUDIO_MODE_EXTENSION: return "Audio Stereo Mode Extension"; + case V4L2_CID_MPEG_AUDIO_EMPHASIS: return "Audio Emphasis"; + case V4L2_CID_MPEG_AUDIO_CRC: return "Audio CRC"; + case V4L2_CID_MPEG_AUDIO_MUTE: return "Audio Mute"; + case V4L2_CID_MPEG_VIDEO_ENCODING: return "Video Encoding"; + case V4L2_CID_MPEG_VIDEO_ASPECT: return "Video Aspect"; + case V4L2_CID_MPEG_VIDEO_B_FRAMES: return "Video B Frames"; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: return "Video GOP Size"; + case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE: return "Video GOP Closure"; + case V4L2_CID_MPEG_VIDEO_PULLDOWN: return "Video Pulldown"; + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: return "Video Bitrate Mode"; + case V4L2_CID_MPEG_VIDEO_BITRATE: return "Video Bitrate"; + case V4L2_CID_MPEG_VIDEO_BITRATE_PEAK: return "Video Peak Bitrate"; + case V4L2_CID_MPEG_VIDEO_TEMPORAL_DECIMATION: return "Video Temporal Decimation"; + case V4L2_CID_MPEG_VIDEO_MUTE: return "Video Mute"; + case V4L2_CID_MPEG_VIDEO_MUTE_YUV: return "Video Mute YUV"; + case V4L2_CID_MPEG_STREAM_TYPE: return "Stream Type"; + case V4L2_CID_MPEG_STREAM_PID_PMT: return "Stream PMT Program ID"; + case V4L2_CID_MPEG_STREAM_PID_AUDIO: return "Stream Audio Program ID"; + case V4L2_CID_MPEG_STREAM_PID_VIDEO: return "Stream Video Program ID"; + case V4L2_CID_MPEG_STREAM_PID_PCR: return "Stream PCR Program ID"; + case V4L2_CID_MPEG_STREAM_PES_ID_AUDIO: return "Stream PES Audio ID"; + case V4L2_CID_MPEG_STREAM_PES_ID_VIDEO: return "Stream PES Video ID"; + case V4L2_CID_MPEG_STREAM_VBI_FMT: return "Stream VBI Format"; + + /* CAMERA controls */ + case V4L2_CID_CAMERA_CLASS: return "Camera Controls"; + case V4L2_CID_EXPOSURE_AUTO: return "Auto Exposure"; + case V4L2_CID_EXPOSURE_ABSOLUTE: return "Exposure Time, Absolute"; + case V4L2_CID_EXPOSURE_AUTO_PRIORITY: return "Exposure, Dynamic Framerate"; + case V4L2_CID_PAN_RELATIVE: return "Pan, Relative"; + case V4L2_CID_TILT_RELATIVE: return "Tilt, Relative"; + case V4L2_CID_PAN_RESET: return "Pan, Reset"; + case V4L2_CID_TILT_RESET: return "Tilt, Reset"; + case V4L2_CID_PAN_ABSOLUTE: return "Pan, Absolute"; + case V4L2_CID_TILT_ABSOLUTE: return "Tilt, Absolute"; + case V4L2_CID_FOCUS_ABSOLUTE: return "Focus, Absolute"; + case V4L2_CID_FOCUS_RELATIVE: return "Focus, Relative"; + case V4L2_CID_FOCUS_AUTO: return "Focus, Automatic"; + case V4L2_CID_IRIS_ABSOLUTE: return "Iris, Absolute"; + case V4L2_CID_IRIS_RELATIVE: return "Iris, Relative"; + case V4L2_CID_ZOOM_ABSOLUTE: return "Zoom, Absolute"; + case V4L2_CID_ZOOM_RELATIVE: return "Zoom, Relative"; + case V4L2_CID_ZOOM_CONTINUOUS: return "Zoom, Continuous"; + case V4L2_CID_PRIVACY: return "Privacy"; + + /* FM Radio Modulator control */ + case V4L2_CID_FM_TX_CLASS: return "FM Radio Modulator Controls"; + case V4L2_CID_RDS_TX_DEVIATION: return "RDS Signal Deviation"; + case V4L2_CID_RDS_TX_PI: return "RDS Program ID"; + case V4L2_CID_RDS_TX_PTY: return "RDS Program Type"; + case V4L2_CID_RDS_TX_PS_NAME: return "RDS PS Name"; + case V4L2_CID_RDS_TX_RADIO_TEXT: return "RDS Radio Text"; + case V4L2_CID_AUDIO_LIMITER_ENABLED: return "Audio Limiter Feature Enabled"; + case V4L2_CID_AUDIO_LIMITER_RELEASE_TIME: return "Audio Limiter Release Time"; + case V4L2_CID_AUDIO_LIMITER_DEVIATION: return "Audio Limiter Deviation"; + case V4L2_CID_AUDIO_COMPRESSION_ENABLED: return "Audio Compression Feature Enabled"; + case V4L2_CID_AUDIO_COMPRESSION_GAIN: return "Audio Compression Gain"; + case V4L2_CID_AUDIO_COMPRESSION_THRESHOLD: return "Audio Compression Threshold"; + case V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME: return "Audio Compression Attack Time"; + case V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME: return "Audio Compression Release Time"; + case V4L2_CID_PILOT_TONE_ENABLED: return "Pilot Tone Feature Enabled"; + case V4L2_CID_PILOT_TONE_DEVIATION: return "Pilot Tone Deviation"; + case V4L2_CID_PILOT_TONE_FREQUENCY: return "Pilot Tone Frequency"; + case V4L2_CID_TUNE_PREEMPHASIS: return "Pre-emphasis settings"; + case V4L2_CID_TUNE_POWER_LEVEL: return "Tune Power Level"; + case V4L2_CID_TUNE_ANTENNA_CAPACITOR: return "Tune Antenna Capacitor"; + + default: + return NULL; + } +} +EXPORT_SYMBOL(v4l2_ctrl_get_name); + +void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, + s32 *min, s32 *max, s32 *step, s32 *def, u32 *flags) +{ + *name = v4l2_ctrl_get_name(id); + *flags = 0; + + switch (id) { + case V4L2_CID_AUDIO_MUTE: + case V4L2_CID_AUDIO_LOUDNESS: + case V4L2_CID_AUTO_WHITE_BALANCE: + case V4L2_CID_AUTOGAIN: + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + case V4L2_CID_HUE_AUTO: + case V4L2_CID_CHROMA_AGC: + case V4L2_CID_COLOR_KILLER: + case V4L2_CID_MPEG_AUDIO_MUTE: + case V4L2_CID_MPEG_VIDEO_MUTE: + case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE: + case V4L2_CID_MPEG_VIDEO_PULLDOWN: + case V4L2_CID_EXPOSURE_AUTO_PRIORITY: + case V4L2_CID_FOCUS_AUTO: + case V4L2_CID_PRIVACY: + case V4L2_CID_AUDIO_LIMITER_ENABLED: + case V4L2_CID_AUDIO_COMPRESSION_ENABLED: + case V4L2_CID_PILOT_TONE_ENABLED: + *type = V4L2_CTRL_TYPE_BOOLEAN; + *min = 0; + *max = *step = 1; + break; + case V4L2_CID_PAN_RESET: + case V4L2_CID_TILT_RESET: + *type = V4L2_CTRL_TYPE_BUTTON; + *flags |= V4L2_CTRL_FLAG_WRITE_ONLY; + *min = *max = *step = *def = 0; + break; + case V4L2_CID_POWER_LINE_FREQUENCY: + case V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ: + case V4L2_CID_MPEG_AUDIO_ENCODING: + case V4L2_CID_MPEG_AUDIO_L1_BITRATE: + case V4L2_CID_MPEG_AUDIO_L2_BITRATE: + case V4L2_CID_MPEG_AUDIO_L3_BITRATE: + case V4L2_CID_MPEG_AUDIO_AC3_BITRATE: + case V4L2_CID_MPEG_AUDIO_MODE: + case V4L2_CID_MPEG_AUDIO_MODE_EXTENSION: + case V4L2_CID_MPEG_AUDIO_EMPHASIS: + case V4L2_CID_MPEG_AUDIO_CRC: + case V4L2_CID_MPEG_VIDEO_ENCODING: + case V4L2_CID_MPEG_VIDEO_ASPECT: + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + case V4L2_CID_MPEG_STREAM_TYPE: + case V4L2_CID_MPEG_STREAM_VBI_FMT: + case V4L2_CID_EXPOSURE_AUTO: + case V4L2_CID_COLORFX: + case V4L2_CID_TUNE_PREEMPHASIS: + *type = V4L2_CTRL_TYPE_MENU; + break; + case V4L2_CID_RDS_TX_PS_NAME: + case V4L2_CID_RDS_TX_RADIO_TEXT: + *type = V4L2_CTRL_TYPE_STRING; + break; + case V4L2_CID_USER_CLASS: + case V4L2_CID_CAMERA_CLASS: + case V4L2_CID_MPEG_CLASS: + case V4L2_CID_FM_TX_CLASS: + *type = V4L2_CTRL_TYPE_CTRL_CLASS; + /* You can neither read not write these */ + *flags |= V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_WRITE_ONLY; + *min = *max = *step = *def = 0; + break; + case V4L2_CID_BG_COLOR: + *type = V4L2_CTRL_TYPE_INTEGER; + *step = 1; + *min = 0; + /* Max is calculated as RGB888 that is 2^24 */ + *max = 0xFFFFFF; + break; + default: + *type = V4L2_CTRL_TYPE_INTEGER; + break; + } + switch (id) { + case V4L2_CID_MPEG_AUDIO_ENCODING: + case V4L2_CID_MPEG_AUDIO_MODE: + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + case V4L2_CID_MPEG_VIDEO_B_FRAMES: + case V4L2_CID_MPEG_STREAM_TYPE: + *flags |= V4L2_CTRL_FLAG_UPDATE; + break; + case V4L2_CID_AUDIO_VOLUME: + case V4L2_CID_AUDIO_BALANCE: + case V4L2_CID_AUDIO_BASS: + case V4L2_CID_AUDIO_TREBLE: + case V4L2_CID_BRIGHTNESS: + case V4L2_CID_CONTRAST: + case V4L2_CID_SATURATION: + case V4L2_CID_HUE: + case V4L2_CID_RED_BALANCE: + case V4L2_CID_BLUE_BALANCE: + case V4L2_CID_GAMMA: + case V4L2_CID_SHARPNESS: + case V4L2_CID_CHROMA_GAIN: + case V4L2_CID_RDS_TX_DEVIATION: + case V4L2_CID_AUDIO_LIMITER_RELEASE_TIME: + case V4L2_CID_AUDIO_LIMITER_DEVIATION: + case V4L2_CID_AUDIO_COMPRESSION_GAIN: + case V4L2_CID_AUDIO_COMPRESSION_THRESHOLD: + case V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME: + case V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME: + case V4L2_CID_PILOT_TONE_DEVIATION: + case V4L2_CID_PILOT_TONE_FREQUENCY: + case V4L2_CID_TUNE_POWER_LEVEL: + case V4L2_CID_TUNE_ANTENNA_CAPACITOR: + *flags |= V4L2_CTRL_FLAG_SLIDER; + break; + case V4L2_CID_PAN_RELATIVE: + case V4L2_CID_TILT_RELATIVE: + case V4L2_CID_FOCUS_RELATIVE: + case V4L2_CID_IRIS_RELATIVE: + case V4L2_CID_ZOOM_RELATIVE: + *flags |= V4L2_CTRL_FLAG_WRITE_ONLY; + break; + } +} +EXPORT_SYMBOL(v4l2_ctrl_fill); + +/* Helper function to determine whether the control type is compatible with + VIDIOC_G/S_CTRL. */ +static bool type_is_int(const struct v4l2_ctrl *ctrl) +{ + switch (ctrl->type) { + case V4L2_CTRL_TYPE_INTEGER64: + case V4L2_CTRL_TYPE_STRING: + /* Nope, these need v4l2_ext_control */ + return false; + default: + return true; + } +} + +/* Helper function: copy the current control value back to the caller */ +static int cur_to_user(struct v4l2_ext_control *c, + struct v4l2_ctrl *ctrl) +{ + u32 len; + + switch (ctrl->type) { + case V4L2_CTRL_TYPE_STRING: + len = strlen(ctrl->cur.string); + if (c->size < len + 1) { + c->size = len + 1; + return -ENOSPC; + } + return copy_to_user(c->string, ctrl->cur.string, + len + 1) ? -EFAULT : 0; + case V4L2_CTRL_TYPE_INTEGER64: + c->value64 = ctrl->cur.val64; + break; + default: + c->value = ctrl->cur.val; + break; + } + return 0; +} + +/* Helper function: copy the caller-provider value as the new control value */ +static int user_to_new(struct v4l2_ext_control *c, + struct v4l2_ctrl *ctrl) +{ + int ret; + u32 size; + + ctrl->has_new = 1; + switch (ctrl->type) { + case V4L2_CTRL_TYPE_INTEGER64: + ctrl->val64 = c->value64; + break; + case V4L2_CTRL_TYPE_STRING: + size = c->size; + if (size == 0) + return -ERANGE; + if (size > ctrl->maximum + 1) + size = ctrl->maximum + 1; + ret = copy_from_user(ctrl->string, c->string, size); + if (!ret) { + char last = ctrl->string[size - 1]; + + ctrl->string[size - 1] = 0; + /* If the string was longer than ctrl->maximum, + then return an error. */ + if (strlen(ctrl->string) == ctrl->maximum && last) + return -ERANGE; + } + return ret ? -EFAULT : 0; + default: + ctrl->val = c->value; + break; + } + return 0; +} + +/* Helper function: copy the new control value back to the caller */ +static int new_to_user(struct v4l2_ext_control *c, + struct v4l2_ctrl *ctrl) +{ + u32 len; + + switch (ctrl->type) { + case V4L2_CTRL_TYPE_STRING: + len = strlen(ctrl->string); + if (c->size < len + 1) { + c->size = ctrl->maximum + 1; + return -ENOSPC; + } + return copy_to_user(c->string, ctrl->string, + len + 1) ? -EFAULT : 0; + case V4L2_CTRL_TYPE_INTEGER64: + c->value64 = ctrl->val64; + break; + default: + c->value = ctrl->val; + break; + } + return 0; +} + +/* Copy the new value to the current value. */ +static void new_to_cur(struct v4l2_ctrl *ctrl) +{ + if (ctrl == NULL) + return; + switch (ctrl->type) { + case V4L2_CTRL_TYPE_STRING: + /* strings are always 0-terminated */ + strcpy(ctrl->cur.string, ctrl->string); + break; + case V4L2_CTRL_TYPE_INTEGER64: + ctrl->cur.val64 = ctrl->val64; + break; + default: + ctrl->cur.val = ctrl->val; + break; + } +} + +/* Copy the current value to the new value */ +static void cur_to_new(struct v4l2_ctrl *ctrl) +{ + if (ctrl == NULL) + return; + switch (ctrl->type) { + case V4L2_CTRL_TYPE_STRING: + /* strings are always 0-terminated */ + strcpy(ctrl->string, ctrl->cur.string); + break; + case V4L2_CTRL_TYPE_INTEGER64: + ctrl->val64 = ctrl->cur.val64; + break; + default: + ctrl->val = ctrl->cur.val; + break; + } +} + +/* Return non-zero if one or more of the controls in the cluster has a new + value that differs from the current value. */ +static int cluster_changed(struct v4l2_ctrl *master) +{ + int diff = 0; + int i; + + for (i = 0; !diff && i < master->ncontrols; i++) { + struct v4l2_ctrl *ctrl = master->cluster[i]; + + if (ctrl == NULL) + continue; + switch (ctrl->type) { + case V4L2_CTRL_TYPE_BUTTON: + /* Button controls are always 'different' */ + return 1; + case V4L2_CTRL_TYPE_STRING: + /* strings are always 0-terminated */ + diff = strcmp(ctrl->string, ctrl->cur.string); + break; + case V4L2_CTRL_TYPE_INTEGER64: + diff = ctrl->val64 != ctrl->cur.val64; + break; + default: + diff = ctrl->val != ctrl->cur.val; + break; + } + } + return diff; +} + +/* Validate a new control */ +static int validate_new(struct v4l2_ctrl *ctrl) +{ + s32 val = ctrl->val; + char *s = ctrl->string; + u32 offset; + size_t len; + + switch (ctrl->type) { + case V4L2_CTRL_TYPE_INTEGER: + /* Round towards the closest legal value */ + val += ctrl->step / 2; + if (val < ctrl->minimum) + val = ctrl->minimum; + if (val > ctrl->maximum) + val = ctrl->maximum; + offset = val - ctrl->minimum; + offset = ctrl->step * (offset / ctrl->step); + val = ctrl->minimum + offset; + ctrl->val = val; + return 0; + + case V4L2_CTRL_TYPE_BOOLEAN: + ctrl->val = !!ctrl->val; + return 0; + + case V4L2_CTRL_TYPE_MENU: + if (val < ctrl->minimum || val > ctrl->maximum) + return -ERANGE; + if (ctrl->qmenu[val][0] == '\0' || + (ctrl->menu_skip_mask & (1 << val))) + return -EINVAL; + return 0; + + case V4L2_CTRL_TYPE_BUTTON: + case V4L2_CTRL_TYPE_CTRL_CLASS: + ctrl->val64 = 0; + return 0; + + case V4L2_CTRL_TYPE_INTEGER64: + return 0; + + case V4L2_CTRL_TYPE_STRING: + len = strlen(s); + if (len < ctrl->minimum) + return -ERANGE; + if ((len - ctrl->minimum) % ctrl->step) + return -ERANGE; + return 0; + + default: + return -EINVAL; + } +} + +static inline u32 node2id(struct list_head *node) +{ + return list_entry(node, struct v4l2_ctrl_ref, node)->ctrl->id; +} + +/* Set the handler's error code if it wasn't set earlier already */ +static inline int handler_set_err(struct v4l2_ctrl_handler *hdl, int err) +{ + if (hdl->error == 0) + hdl->error = err; + return err; +} + +/* Initialize the handler */ +int v4l2_ctrl_handler_init(struct v4l2_ctrl_handler *hdl, + unsigned nr_of_controls_hint) +{ + mutex_init(&hdl->lock); + INIT_LIST_HEAD(&hdl->ctrls); + INIT_LIST_HEAD(&hdl->ctrl_refs); + hdl->nr_of_buckets = 1 + nr_of_controls_hint / 8; + hdl->buckets = kzalloc(sizeof(hdl->buckets[0]) * hdl->nr_of_buckets, + GFP_KERNEL); + hdl->error = hdl->buckets ? 0 : -ENOMEM; + return hdl->error; +} +EXPORT_SYMBOL(v4l2_ctrl_handler_init); + +/* Free all controls and control refs */ +void v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl) +{ + struct v4l2_ctrl_ref *ref, *next_ref; + struct v4l2_ctrl *ctrl, *next_ctrl; + + if (hdl == NULL || hdl->buckets == NULL) + return; + + mutex_lock(&hdl->lock); + /* Free all nodes */ + list_for_each_entry_safe(ref, next_ref, &hdl->ctrl_refs, node) { + list_del(&ref->node); + kfree(ref); + } + /* Free all controls owned by the handler */ + list_for_each_entry_safe(ctrl, next_ctrl, &hdl->ctrls, node) { + list_del(&ctrl->node); + kfree(ctrl); + } + kfree(hdl->buckets); + hdl->buckets = NULL; + hdl->cached = NULL; + hdl->error = 0; + mutex_unlock(&hdl->lock); +} +EXPORT_SYMBOL(v4l2_ctrl_handler_free); + +/* For backwards compatibility: V4L2_CID_PRIVATE_BASE should no longer + be used except in G_CTRL, S_CTRL, QUERYCTRL and QUERYMENU when dealing + with applications that do not use the NEXT_CTRL flag. + + We just find the n-th private user control. It's O(N), but that should not + be an issue in this particular case. */ +static struct v4l2_ctrl_ref *find_private_ref( + struct v4l2_ctrl_handler *hdl, u32 id) +{ + struct v4l2_ctrl_ref *ref; + + id -= V4L2_CID_PRIVATE_BASE; + list_for_each_entry(ref, &hdl->ctrl_refs, node) { + /* Search for private user controls that are compatible with + VIDIOC_G/S_CTRL. */ + if (V4L2_CTRL_ID2CLASS(ref->ctrl->id) == V4L2_CTRL_CLASS_USER && + V4L2_CTRL_DRIVER_PRIV(ref->ctrl->id)) { + if (!type_is_int(ref->ctrl)) + continue; + if (id == 0) + return ref; + id--; + } + } + return NULL; +} + +/* Find a control with the given ID. */ +static struct v4l2_ctrl_ref *find_ref(struct v4l2_ctrl_handler *hdl, u32 id) +{ + struct v4l2_ctrl_ref *ref; + int bucket; + + id &= V4L2_CTRL_ID_MASK; + + /* Old-style private controls need special handling */ + if (id >= V4L2_CID_PRIVATE_BASE) + return find_private_ref(hdl, id); + bucket = id % hdl->nr_of_buckets; + + /* Simple optimization: cache the last control found */ + if (hdl->cached && hdl->cached->ctrl->id == id) + return hdl->cached; + + /* Not in cache, search the hash */ + ref = hdl->buckets ? hdl->buckets[bucket] : NULL; + while (ref && ref->ctrl->id != id) + ref = ref->next; + + if (ref) + hdl->cached = ref; /* cache it! */ + return ref; +} + +/* Find a control with the given ID. Take the handler's lock first. */ +static struct v4l2_ctrl_ref *find_ref_lock( + struct v4l2_ctrl_handler *hdl, u32 id) +{ + struct v4l2_ctrl_ref *ref = NULL; + + if (hdl) { + mutex_lock(&hdl->lock); + ref = find_ref(hdl, id); + mutex_unlock(&hdl->lock); + } + return ref; +} + +/* Find a control with the given ID. */ +struct v4l2_ctrl *v4l2_ctrl_find(struct v4l2_ctrl_handler *hdl, u32 id) +{ + struct v4l2_ctrl_ref *ref = find_ref_lock(hdl, id); + + return ref ? ref->ctrl : NULL; +} +EXPORT_SYMBOL(v4l2_ctrl_find); + +/* Allocate a new v4l2_ctrl_ref and hook it into the handler. */ +static int handler_new_ref(struct v4l2_ctrl_handler *hdl, + struct v4l2_ctrl *ctrl) +{ + struct v4l2_ctrl_ref *ref; + struct v4l2_ctrl_ref *new_ref; + u32 id = ctrl->id; + u32 class_ctrl = V4L2_CTRL_ID2CLASS(id) | 1; + int bucket = id % hdl->nr_of_buckets; /* which bucket to use */ + + /* Automatically add the control class if it is not yet present. */ + if (id != class_ctrl && find_ref_lock(hdl, class_ctrl) == NULL) + if (!v4l2_ctrl_new_std(hdl, NULL, class_ctrl, 0, 0, 0, 0)) + return hdl->error; + + if (hdl->error) + return hdl->error; + + new_ref = kzalloc(sizeof(*new_ref), GFP_KERNEL); + if (!new_ref) + return handler_set_err(hdl, -ENOMEM); + new_ref->ctrl = ctrl; + if (ctrl->handler == hdl) { + /* By default each control starts in a cluster of its own. + new_ref->ctrl is basically a cluster array with one + element, so that's perfect to use as the cluster pointer. + But only do this for the handler that owns the control. */ + ctrl->cluster = &new_ref->ctrl; + ctrl->ncontrols = 1; + } + + INIT_LIST_HEAD(&new_ref->node); + + mutex_lock(&hdl->lock); + + /* Add immediately at the end of the list if the list is empty, or if + the last element in the list has a lower ID. + This ensures that when elements are added in ascending order the + insertion is an O(1) operation. */ + if (list_empty(&hdl->ctrl_refs) || id > node2id(hdl->ctrl_refs.prev)) { + list_add_tail(&new_ref->node, &hdl->ctrl_refs); + goto insert_in_hash; + } + + /* Find insert position in sorted list */ + list_for_each_entry(ref, &hdl->ctrl_refs, node) { + if (ref->ctrl->id < id) + continue; + /* Don't add duplicates */ + if (ref->ctrl->id == id) { + kfree(new_ref); + goto unlock; + } + list_add(&new_ref->node, ref->node.prev); + break; + } + +insert_in_hash: + /* Insert the control node in the hash */ + new_ref->next = hdl->buckets[bucket]; + hdl->buckets[bucket] = new_ref; + +unlock: + mutex_unlock(&hdl->lock); + return 0; +} + +/* Add a new control */ +static struct v4l2_ctrl *v4l2_ctrl_new(struct v4l2_ctrl_handler *hdl, + const struct v4l2_ctrl_ops *ops, + u32 id, const char *name, enum v4l2_ctrl_type type, + s32 min, s32 max, u32 step, s32 def, + u32 flags, const char **qmenu, void *priv) +{ + struct v4l2_ctrl *ctrl; + unsigned sz_extra = 0; + + if (hdl->error) + return NULL; + + /* Sanity checks */ + if (id == 0 || name == NULL || id >= V4L2_CID_PRIVATE_BASE || + def < min || def > max || max < min || + (type == V4L2_CTRL_TYPE_INTEGER && step == 0) || + (type == V4L2_CTRL_TYPE_MENU && qmenu == NULL) || + (type == V4L2_CTRL_TYPE_STRING && max == 0)) { + handler_set_err(hdl, -ERANGE); + return NULL; + } + + if (type == V4L2_CTRL_TYPE_BUTTON) + flags |= V4L2_CTRL_FLAG_WRITE_ONLY; + else if (type == V4L2_CTRL_TYPE_CTRL_CLASS) + flags |= V4L2_CTRL_FLAG_READ_ONLY; + else if (type == V4L2_CTRL_TYPE_STRING) + sz_extra += 2 * (max + 1); + + ctrl = kzalloc(sizeof(*ctrl) + sz_extra, GFP_KERNEL); + if (ctrl == NULL) { + handler_set_err(hdl, -ENOMEM); + return NULL; + } + + INIT_LIST_HEAD(&ctrl->node); + ctrl->handler = hdl; + ctrl->ops = ops; + ctrl->id = id; + ctrl->name = name; + ctrl->type = type; + ctrl->flags = flags; + ctrl->minimum = min; + ctrl->maximum = max; + ctrl->step = step; + ctrl->qmenu = qmenu; + ctrl->priv = priv; + ctrl->cur.val = ctrl->val = ctrl->default_value = def; + + if (ctrl->type == V4L2_CTRL_TYPE_STRING) { + ctrl->cur.string = (char *)&ctrl[1] + sz_extra - (max + 1); + ctrl->string = (char *)&ctrl[1] + sz_extra - 2 * (max + 1); + if (ctrl->minimum) + memset(ctrl->cur.string, ' ', ctrl->minimum); + } + if (handler_new_ref(hdl, ctrl)) { + kfree(ctrl); + return NULL; + } + mutex_lock(&hdl->lock); + list_add_tail(&ctrl->node, &hdl->ctrls); + mutex_unlock(&hdl->lock); + return ctrl; +} + +struct v4l2_ctrl *v4l2_ctrl_new_custom(struct v4l2_ctrl_handler *hdl, + const struct v4l2_ctrl_config *cfg, void *priv) +{ + bool is_menu; + struct v4l2_ctrl *ctrl; + const char *name = cfg->name; + const char **qmenu = cfg->qmenu; + enum v4l2_ctrl_type type = cfg->type; + u32 flags = cfg->flags; + s32 min = cfg->min; + s32 max = cfg->max; + u32 step = cfg->step; + s32 def = cfg->def; + + if (name == NULL) + v4l2_ctrl_fill(cfg->id, &name, &type, &min, &max, &step, + &def, &flags); + + is_menu = (cfg->type == V4L2_CTRL_TYPE_MENU); + if (is_menu) + WARN_ON(step); + else + WARN_ON(cfg->menu_skip_mask); + if (is_menu && qmenu == NULL) + qmenu = v4l2_ctrl_get_menu(cfg->id); + + ctrl = v4l2_ctrl_new(hdl, cfg->ops, cfg->id, name, + type, min, max, + is_menu ? cfg->menu_skip_mask : step, + def, flags, qmenu, priv); + if (ctrl) { + ctrl->is_private = cfg->is_private; + ctrl->is_volatile = cfg->is_volatile; + } + return ctrl; +} +EXPORT_SYMBOL(v4l2_ctrl_new_custom); + +/* Helper function for standard non-menu controls */ +struct v4l2_ctrl *v4l2_ctrl_new_std(struct v4l2_ctrl_handler *hdl, + const struct v4l2_ctrl_ops *ops, + u32 id, s32 min, s32 max, u32 step, s32 def) +{ + const char *name; + enum v4l2_ctrl_type type; + u32 flags; + + v4l2_ctrl_fill(id, &name, &type, &min, &max, &step, &def, &flags); + if (type == V4L2_CTRL_TYPE_MENU) { + handler_set_err(hdl, -EINVAL); + return NULL; + } + return v4l2_ctrl_new(hdl, ops, id, name, type, + min, max, step, def, flags, NULL, NULL); +} +EXPORT_SYMBOL(v4l2_ctrl_new_std); + +/* Helper function for standard menu controls */ +struct v4l2_ctrl *v4l2_ctrl_new_std_menu(struct v4l2_ctrl_handler *hdl, + const struct v4l2_ctrl_ops *ops, + u32 id, s32 max, s32 mask, s32 def) +{ + const char **qmenu = v4l2_ctrl_get_menu(id); + const char *name; + enum v4l2_ctrl_type type; + s32 min; + s32 step; + u32 flags; + + v4l2_ctrl_fill(id, &name, &type, &min, &max, &step, &def, &flags); + if (type != V4L2_CTRL_TYPE_MENU) { + handler_set_err(hdl, -EINVAL); + return NULL; + } + return v4l2_ctrl_new(hdl, ops, id, name, type, + 0, max, mask, def, flags, qmenu, NULL); +} +EXPORT_SYMBOL(v4l2_ctrl_new_std_menu); + +/* Add a control from another handler to this handler */ +struct v4l2_ctrl *v4l2_ctrl_add_ctrl(struct v4l2_ctrl_handler *hdl, + struct v4l2_ctrl *ctrl) +{ + if (hdl == NULL || hdl->error) + return NULL; + if (ctrl == NULL) { + handler_set_err(hdl, -EINVAL); + return NULL; + } + if (ctrl->handler == hdl) + return ctrl; + return handler_new_ref(hdl, ctrl) ? NULL : ctrl; +} +EXPORT_SYMBOL(v4l2_ctrl_add_ctrl); + +/* Add the controls from another handler to our own. */ +int v4l2_ctrl_add_handler(struct v4l2_ctrl_handler *hdl, + struct v4l2_ctrl_handler *add) +{ + struct v4l2_ctrl *ctrl; + int ret = 0; + + /* Do nothing if either handler is NULL or if they are the same */ + if (!hdl || !add || hdl == add) + return 0; + if (hdl->error) + return hdl->error; + mutex_lock(&add->lock); + list_for_each_entry(ctrl, &add->ctrls, node) { + /* Skip handler-private controls. */ + if (ctrl->is_private) + continue; + ret = handler_new_ref(hdl, ctrl); + if (ret) + break; + } + mutex_unlock(&add->lock); + return ret; +} +EXPORT_SYMBOL(v4l2_ctrl_add_handler); + +/* Cluster controls */ +void v4l2_ctrl_cluster(unsigned ncontrols, struct v4l2_ctrl **controls) +{ + int i; + + /* The first control is the master control and it must not be NULL */ + BUG_ON(controls[0] == NULL); + + for (i = 0; i < ncontrols; i++) { + if (controls[i]) { + controls[i]->cluster = controls; + controls[i]->ncontrols = ncontrols; + } + } +} +EXPORT_SYMBOL(v4l2_ctrl_cluster); + +/* Activate/deactivate a control. */ +void v4l2_ctrl_activate(struct v4l2_ctrl *ctrl, bool active) +{ + if (ctrl == NULL) + return; + + if (!active) + /* set V4L2_CTRL_FLAG_INACTIVE */ + set_bit(4, &ctrl->flags); + else + /* clear V4L2_CTRL_FLAG_INACTIVE */ + clear_bit(4, &ctrl->flags); +} +EXPORT_SYMBOL(v4l2_ctrl_activate); + +/* Grab/ungrab a control. + Typically used when streaming starts and you want to grab controls, + preventing the user from changing them. + + Just call this and the framework will block any attempts to change + these controls. */ +void v4l2_ctrl_grab(struct v4l2_ctrl *ctrl, bool grabbed) +{ + if (ctrl == NULL) + return; + + if (grabbed) + /* set V4L2_CTRL_FLAG_GRABBED */ + set_bit(1, &ctrl->flags); + else + /* clear V4L2_CTRL_FLAG_GRABBED */ + clear_bit(1, &ctrl->flags); +} +EXPORT_SYMBOL(v4l2_ctrl_grab); + +/* Log the control name and value */ +static void log_ctrl(const struct v4l2_ctrl *ctrl, + const char *prefix, const char *colon) +{ + int fl_inact = ctrl->flags & V4L2_CTRL_FLAG_INACTIVE; + int fl_grabbed = ctrl->flags & V4L2_CTRL_FLAG_GRABBED; + + if (ctrl->flags & (V4L2_CTRL_FLAG_DISABLED | V4L2_CTRL_FLAG_WRITE_ONLY)) + return; + if (ctrl->type == V4L2_CTRL_TYPE_CTRL_CLASS) + return; + + printk(KERN_INFO "%s%s%s: ", prefix, colon, ctrl->name); + + switch (ctrl->type) { + case V4L2_CTRL_TYPE_INTEGER: + printk(KERN_CONT "%d", ctrl->cur.val); + break; + case V4L2_CTRL_TYPE_BOOLEAN: + printk(KERN_CONT "%s", ctrl->cur.val ? "true" : "false"); + break; + case V4L2_CTRL_TYPE_MENU: + printk(KERN_CONT "%s", ctrl->qmenu[ctrl->cur.val]); + break; + case V4L2_CTRL_TYPE_INTEGER64: + printk(KERN_CONT "%lld", ctrl->cur.val64); + break; + case V4L2_CTRL_TYPE_STRING: + printk(KERN_CONT "%s", ctrl->cur.string); + break; + default: + printk(KERN_CONT "unknown type %d", ctrl->type); + break; + } + if (fl_inact && fl_grabbed) + printk(KERN_CONT " (inactive, grabbed)\n"); + else if (fl_inact) + printk(KERN_CONT " (inactive)\n"); + else if (fl_grabbed) + printk(KERN_CONT " (grabbed)\n"); + else + printk(KERN_CONT "\n"); +} + +/* Log all controls owned by the handler */ +void v4l2_ctrl_handler_log_status(struct v4l2_ctrl_handler *hdl, + const char *prefix) +{ + struct v4l2_ctrl *ctrl; + const char *colon = ""; + int len; + + if (hdl == NULL) + return; + if (prefix == NULL) + prefix = ""; + len = strlen(prefix); + if (len && prefix[len - 1] != ' ') + colon = ": "; + mutex_lock(&hdl->lock); + list_for_each_entry(ctrl, &hdl->ctrls, node) + if (!(ctrl->flags & V4L2_CTRL_FLAG_DISABLED)) + log_ctrl(ctrl, prefix, colon); + mutex_unlock(&hdl->lock); +} +EXPORT_SYMBOL(v4l2_ctrl_handler_log_status); + +/* Call s_ctrl for all controls owned by the handler */ +int v4l2_ctrl_handler_setup(struct v4l2_ctrl_handler *hdl) +{ + struct v4l2_ctrl *ctrl; + int ret = 0; + + if (hdl == NULL) + return 0; + mutex_lock(&hdl->lock); + list_for_each_entry(ctrl, &hdl->ctrls, node) + ctrl->done = false; + + list_for_each_entry(ctrl, &hdl->ctrls, node) { + struct v4l2_ctrl *master = ctrl->cluster[0]; + int i; + + /* Skip if this control was already handled by a cluster. */ + if (ctrl->done) + continue; + + for (i = 0; i < master->ncontrols; i++) + cur_to_new(master->cluster[i]); + + /* Skip button controls and read-only controls. */ + if (ctrl->type == V4L2_CTRL_TYPE_BUTTON || + (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY)) + continue; + ret = master->ops->s_ctrl(master); + if (ret) + break; + for (i = 0; i < master->ncontrols; i++) + if (master->cluster[i]) + master->cluster[i]->done = true; + } + mutex_unlock(&hdl->lock); + return ret; +} +EXPORT_SYMBOL(v4l2_ctrl_handler_setup); + +/* Implement VIDIOC_QUERYCTRL */ +int v4l2_queryctrl(struct v4l2_ctrl_handler *hdl, struct v4l2_queryctrl *qc) +{ + u32 id = qc->id & V4L2_CTRL_ID_MASK; + struct v4l2_ctrl_ref *ref; + struct v4l2_ctrl *ctrl; + + if (hdl == NULL) + return -EINVAL; + + mutex_lock(&hdl->lock); + + /* Try to find it */ + ref = find_ref(hdl, id); + + if ((qc->id & V4L2_CTRL_FLAG_NEXT_CTRL) && !list_empty(&hdl->ctrl_refs)) { + /* Find the next control with ID > qc->id */ + + /* Did we reach the end of the control list? */ + if (id >= node2id(hdl->ctrl_refs.prev)) { + ref = NULL; /* Yes, so there is no next control */ + } else if (ref) { + /* We found a control with the given ID, so just get + the next one in the list. */ + ref = list_entry(ref->node.next, typeof(*ref), node); + } else { + /* No control with the given ID exists, so start + searching for the next largest ID. We know there + is one, otherwise the first 'if' above would have + been true. */ + list_for_each_entry(ref, &hdl->ctrl_refs, node) + if (id < ref->ctrl->id) + break; + } + } + mutex_unlock(&hdl->lock); + if (!ref) + return -EINVAL; + + ctrl = ref->ctrl; + memset(qc, 0, sizeof(*qc)); + qc->id = ctrl->id; + strlcpy(qc->name, ctrl->name, sizeof(qc->name)); + qc->minimum = ctrl->minimum; + qc->maximum = ctrl->maximum; + qc->default_value = ctrl->default_value; + if (qc->type == V4L2_CTRL_TYPE_MENU) + qc->step = 1; + else + qc->step = ctrl->step; + qc->flags = ctrl->flags; + qc->type = ctrl->type; + return 0; +} +EXPORT_SYMBOL(v4l2_queryctrl); + +int v4l2_subdev_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc) +{ + return v4l2_queryctrl(sd->ctrl_handler, qc); +} +EXPORT_SYMBOL(v4l2_subdev_queryctrl); + +/* Implement VIDIOC_QUERYMENU */ +int v4l2_querymenu(struct v4l2_ctrl_handler *hdl, struct v4l2_querymenu *qm) +{ + struct v4l2_ctrl *ctrl; + u32 i = qm->index; + + ctrl = v4l2_ctrl_find(hdl, qm->id); + if (!ctrl) + return -EINVAL; + + qm->reserved = 0; + /* Sanity checks */ + if (ctrl->qmenu == NULL || + i < ctrl->minimum || i > ctrl->maximum) + return -EINVAL; + /* Use mask to see if this menu item should be skipped */ + if (ctrl->menu_skip_mask & (1 << i)) + return -EINVAL; + /* Empty menu items should also be skipped */ + if (ctrl->qmenu[i] == NULL || ctrl->qmenu[i][0] == '\0') + return -EINVAL; + strlcpy(qm->name, ctrl->qmenu[i], sizeof(qm->name)); + return 0; +} +EXPORT_SYMBOL(v4l2_querymenu); + +int v4l2_subdev_querymenu(struct v4l2_subdev *sd, struct v4l2_querymenu *qm) +{ + return v4l2_querymenu(sd->ctrl_handler, qm); +} +EXPORT_SYMBOL(v4l2_subdev_querymenu); + + + +/* Some general notes on the atomic requirements of VIDIOC_G/TRY/S_EXT_CTRLS: + + It is not a fully atomic operation, just best-effort only. After all, if + multiple controls have to be set through multiple i2c writes (for example) + then some initial writes may succeed while others fail. Thus leaving the + system in an inconsistent state. The question is how much effort you are + willing to spend on trying to make something atomic that really isn't. + + From the point of view of an application the main requirement is that + when you call VIDIOC_S_EXT_CTRLS and some values are invalid then an + error should be returned without actually affecting any controls. + + If all the values are correct, then it is acceptable to just give up + in case of low-level errors. + + It is important though that the application can tell when only a partial + configuration was done. The way we do that is through the error_idx field + of struct v4l2_ext_controls: if that is equal to the count field then no + controls were affected. Otherwise all controls before that index were + successful in performing their 'get' or 'set' operation, the control at + the given index failed, and you don't know what happened with the controls + after the failed one. Since if they were part of a control cluster they + could have been successfully processed (if a cluster member was encountered + at index < error_idx), they could have failed (if a cluster member was at + error_idx), or they may not have been processed yet (if the first cluster + member appeared after error_idx). + + It is all fairly theoretical, though. In practice all you can do is to + bail out. If error_idx == count, then it is an application bug. If + error_idx < count then it is only an application bug if the error code was + EBUSY. That usually means that something started streaming just when you + tried to set the controls. In all other cases it is a driver/hardware + problem and all you can do is to retry or bail out. + + Note that these rules do not apply to VIDIOC_TRY_EXT_CTRLS: since that + never modifies controls the error_idx is just set to whatever control + has an invalid value. + */ + +/* Prepare for the extended g/s/try functions. + Find the controls in the control array and do some basic checks. */ +static int prepare_ext_ctrls(struct v4l2_ctrl_handler *hdl, + struct v4l2_ext_controls *cs, + struct ctrl_helper *helpers, + bool try) +{ + u32 i; + + for (i = 0; i < cs->count; i++) { + struct v4l2_ext_control *c = &cs->controls[i]; + struct v4l2_ctrl *ctrl; + u32 id = c->id & V4L2_CTRL_ID_MASK; + + if (try) + cs->error_idx = i; + + if (cs->ctrl_class && V4L2_CTRL_ID2CLASS(id) != cs->ctrl_class) + return -EINVAL; + + /* Old-style private controls are not allowed for + extended controls */ + if (id >= V4L2_CID_PRIVATE_BASE) + return -EINVAL; + ctrl = v4l2_ctrl_find(hdl, id); + if (ctrl == NULL) + return -EINVAL; + if (ctrl->flags & V4L2_CTRL_FLAG_DISABLED) + return -EINVAL; + + helpers[i].ctrl = ctrl; + helpers[i].handled = false; + } + return 0; +} + +typedef int (*cluster_func)(struct v4l2_ext_control *c, + struct v4l2_ctrl *ctrl); + +/* Walk over all controls in v4l2_ext_controls belonging to the same cluster + and call the provided function. */ +static int cluster_walk(unsigned from, + struct v4l2_ext_controls *cs, + struct ctrl_helper *helpers, + cluster_func f) +{ + struct v4l2_ctrl **cluster = helpers[from].ctrl->cluster; + int ret = 0; + int i; + + /* Find any controls from the same cluster and call the function */ + for (i = from; !ret && i < cs->count; i++) { + struct v4l2_ctrl *ctrl = helpers[i].ctrl; + + if (!helpers[i].handled && ctrl->cluster == cluster) + ret = f(&cs->controls[i], ctrl); + } + return ret; +} + +static void cluster_done(unsigned from, + struct v4l2_ext_controls *cs, + struct ctrl_helper *helpers) +{ + struct v4l2_ctrl **cluster = helpers[from].ctrl->cluster; + int i; + + /* Find any controls from the same cluster and mark them as handled */ + for (i = from; i < cs->count; i++) + if (helpers[i].ctrl->cluster == cluster) + helpers[i].handled = true; +} + +/* Handles the corner case where cs->count == 0. It checks whether the + specified control class exists. If that class ID is 0, then it checks + whether there are any controls at all. */ +static int class_check(struct v4l2_ctrl_handler *hdl, u32 ctrl_class) +{ + if (ctrl_class == 0) + return list_empty(&hdl->ctrl_refs) ? -EINVAL : 0; + return find_ref_lock(hdl, ctrl_class | 1) ? 0 : -EINVAL; +} + + + +/* Get extended controls. Allocates the helpers array if needed. */ +int v4l2_g_ext_ctrls(struct v4l2_ctrl_handler *hdl, struct v4l2_ext_controls *cs) +{ + struct ctrl_helper helper[4]; + struct ctrl_helper *helpers = helper; + int ret; + int i; + + cs->error_idx = cs->count; + cs->ctrl_class = V4L2_CTRL_ID2CLASS(cs->ctrl_class); + + if (hdl == NULL) + return -EINVAL; + + if (cs->count == 0) + return class_check(hdl, cs->ctrl_class); + + if (cs->count > ARRAY_SIZE(helper)) { + helpers = kmalloc(sizeof(helper[0]) * cs->count, GFP_KERNEL); + if (helpers == NULL) + return -ENOMEM; + } + + ret = prepare_ext_ctrls(hdl, cs, helpers, false); + + for (i = 0; !ret && i < cs->count; i++) + if (helpers[i].ctrl->flags & V4L2_CTRL_FLAG_WRITE_ONLY) + ret = -EACCES; + + for (i = 0; !ret && i < cs->count; i++) { + struct v4l2_ctrl *ctrl = helpers[i].ctrl; + struct v4l2_ctrl *master = ctrl->cluster[0]; + + if (helpers[i].handled) + continue; + + cs->error_idx = i; + + v4l2_ctrl_lock(master); + /* g_volatile_ctrl will update the current control values */ + if (ctrl->is_volatile && master->ops->g_volatile_ctrl) + ret = master->ops->g_volatile_ctrl(master); + /* If OK, then copy the current control values to the caller */ + if (!ret) + ret = cluster_walk(i, cs, helpers, cur_to_user); + v4l2_ctrl_unlock(master); + cluster_done(i, cs, helpers); + } + + if (cs->count > ARRAY_SIZE(helper)) + kfree(helpers); + return ret; +} +EXPORT_SYMBOL(v4l2_g_ext_ctrls); + +int v4l2_subdev_g_ext_ctrls(struct v4l2_subdev *sd, struct v4l2_ext_controls *cs) +{ + return v4l2_g_ext_ctrls(sd->ctrl_handler, cs); +} +EXPORT_SYMBOL(v4l2_subdev_g_ext_ctrls); + +/* Helper function to get a single control */ +static int get_ctrl(struct v4l2_ctrl *ctrl, s32 *val) +{ + struct v4l2_ctrl *master = ctrl->cluster[0]; + int ret = 0; + + if (ctrl->flags & V4L2_CTRL_FLAG_WRITE_ONLY) + return -EACCES; + + v4l2_ctrl_lock(master); + /* g_volatile_ctrl will update the current control values */ + if (ctrl->is_volatile && master->ops->g_volatile_ctrl) + ret = master->ops->g_volatile_ctrl(master); + *val = ctrl->cur.val; + v4l2_ctrl_unlock(master); + return ret; +} + +int v4l2_g_ctrl(struct v4l2_ctrl_handler *hdl, struct v4l2_control *control) +{ + struct v4l2_ctrl *ctrl = v4l2_ctrl_find(hdl, control->id); + + if (ctrl == NULL || !type_is_int(ctrl)) + return -EINVAL; + return get_ctrl(ctrl, &control->value); +} +EXPORT_SYMBOL(v4l2_g_ctrl); + +int v4l2_subdev_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *control) +{ + return v4l2_g_ctrl(sd->ctrl_handler, control); +} +EXPORT_SYMBOL(v4l2_subdev_g_ctrl); + +s32 v4l2_ctrl_g_ctrl(struct v4l2_ctrl *ctrl) +{ + s32 val = 0; + + /* It's a driver bug if this happens. */ + WARN_ON(!type_is_int(ctrl)); + get_ctrl(ctrl, &val); + return val; +} +EXPORT_SYMBOL(v4l2_ctrl_g_ctrl); + + +/* Core function that calls try/s_ctrl and ensures that the new value is + copied to the current value on a set. + Must be called with ctrl->handler->lock held. */ +static int try_or_set_control_cluster(struct v4l2_ctrl *master, bool set) +{ + bool try = !set; + int ret = 0; + int i; + + /* Go through the cluster and either validate the new value or + (if no new value was set), copy the current value to the new + value, ensuring a consistent view for the control ops when + called. */ + for (i = 0; !ret && i < master->ncontrols; i++) { + struct v4l2_ctrl *ctrl = master->cluster[i]; + + if (ctrl == NULL) + continue; + + if (ctrl->has_new) { + /* Double check this: it may have changed since the + last check in try_or_set_ext_ctrls(). */ + if (set && (ctrl->flags & V4L2_CTRL_FLAG_GRABBED)) + return -EBUSY; + + /* Validate if required */ + if (!set) + ret = validate_new(ctrl); + continue; + } + /* No new value was set, so copy the current and force + a call to try_ctrl later, since the values for the cluster + may now have changed and the end result might be invalid. */ + try = true; + cur_to_new(ctrl); + } + + /* For larger clusters you have to call try_ctrl again to + verify that the controls are still valid after the + 'cur_to_new' above. */ + if (!ret && master->ops->try_ctrl && try) + ret = master->ops->try_ctrl(master); + + /* Don't set if there is no change */ + if (!ret && set && cluster_changed(master)) { + ret = master->ops->s_ctrl(master); + /* If OK, then make the new values permanent. */ + if (!ret) + for (i = 0; i < master->ncontrols; i++) + new_to_cur(master->cluster[i]); + } + return ret; +} + +/* Try or set controls. */ +static int try_or_set_ext_ctrls(struct v4l2_ctrl_handler *hdl, + struct v4l2_ext_controls *cs, + struct ctrl_helper *helpers, + bool set) +{ + unsigned i, j; + int ret = 0; + + cs->error_idx = cs->count; + for (i = 0; i < cs->count; i++) { + struct v4l2_ctrl *ctrl = helpers[i].ctrl; + + if (!set) + cs->error_idx = i; + + if (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY) + return -EACCES; + /* This test is also done in try_set_control_cluster() which + is called in atomic context, so that has the final say, + but it makes sense to do an up-front check as well. Once + an error occurs in try_set_control_cluster() some other + controls may have been set already and we want to do a + best-effort to avoid that. */ + if (set && (ctrl->flags & V4L2_CTRL_FLAG_GRABBED)) + return -EBUSY; + } + + for (i = 0; !ret && i < cs->count; i++) { + struct v4l2_ctrl *ctrl = helpers[i].ctrl; + struct v4l2_ctrl *master = ctrl->cluster[0]; + + cs->error_idx = i; + + if (helpers[i].handled) + continue; + + v4l2_ctrl_lock(ctrl); + + /* Reset the 'has_new' flags of the cluster */ + for (j = 0; j < master->ncontrols; j++) + if (master->cluster[j]) + master->cluster[j]->has_new = 0; + + /* Copy the new caller-supplied control values. + user_to_new() sets 'has_new' to 1. */ + ret = cluster_walk(i, cs, helpers, user_to_new); + + if (!ret) + ret = try_or_set_control_cluster(master, set); + + /* Copy the new values back to userspace. */ + if (!ret) + ret = cluster_walk(i, cs, helpers, new_to_user); + + v4l2_ctrl_unlock(ctrl); + cluster_done(i, cs, helpers); + } + return ret; +} + +/* Try or try-and-set controls */ +static int try_set_ext_ctrls(struct v4l2_ctrl_handler *hdl, + struct v4l2_ext_controls *cs, + bool set) +{ + struct ctrl_helper helper[4]; + struct ctrl_helper *helpers = helper; + int ret; + int i; + + cs->error_idx = cs->count; + cs->ctrl_class = V4L2_CTRL_ID2CLASS(cs->ctrl_class); + + if (hdl == NULL) + return -EINVAL; + + if (cs->count == 0) + return class_check(hdl, cs->ctrl_class); + + if (cs->count > ARRAY_SIZE(helper)) { + helpers = kmalloc(sizeof(helper[0]) * cs->count, GFP_KERNEL); + if (!helpers) + return -ENOMEM; + } + ret = prepare_ext_ctrls(hdl, cs, helpers, !set); + if (ret) + goto free; + + /* First 'try' all controls and abort on error */ + ret = try_or_set_ext_ctrls(hdl, cs, helpers, false); + /* If this is a 'set' operation and the initial 'try' failed, + then set error_idx to count to tell the application that no + controls changed value yet. */ + if (set) + cs->error_idx = cs->count; + if (!ret && set) { + /* Reset 'handled' state */ + for (i = 0; i < cs->count; i++) + helpers[i].handled = false; + ret = try_or_set_ext_ctrls(hdl, cs, helpers, true); + } + +free: + if (cs->count > ARRAY_SIZE(helper)) + kfree(helpers); + return ret; +} + +int v4l2_try_ext_ctrls(struct v4l2_ctrl_handler *hdl, struct v4l2_ext_controls *cs) +{ + return try_set_ext_ctrls(hdl, cs, false); +} +EXPORT_SYMBOL(v4l2_try_ext_ctrls); + +int v4l2_s_ext_ctrls(struct v4l2_ctrl_handler *hdl, struct v4l2_ext_controls *cs) +{ + return try_set_ext_ctrls(hdl, cs, true); +} +EXPORT_SYMBOL(v4l2_s_ext_ctrls); + +int v4l2_subdev_try_ext_ctrls(struct v4l2_subdev *sd, struct v4l2_ext_controls *cs) +{ + return try_set_ext_ctrls(sd->ctrl_handler, cs, false); +} +EXPORT_SYMBOL(v4l2_subdev_try_ext_ctrls); + +int v4l2_subdev_s_ext_ctrls(struct v4l2_subdev *sd, struct v4l2_ext_controls *cs) +{ + return try_set_ext_ctrls(sd->ctrl_handler, cs, true); +} +EXPORT_SYMBOL(v4l2_subdev_s_ext_ctrls); + +/* Helper function for VIDIOC_S_CTRL compatibility */ +static int set_ctrl(struct v4l2_ctrl *ctrl, s32 *val) +{ + struct v4l2_ctrl *master = ctrl->cluster[0]; + int ret; + int i; + + v4l2_ctrl_lock(ctrl); + + /* Reset the 'has_new' flags of the cluster */ + for (i = 0; i < master->ncontrols; i++) + if (master->cluster[i]) + master->cluster[i]->has_new = 0; + + ctrl->val = *val; + ctrl->has_new = 1; + ret = try_or_set_control_cluster(master, false); + if (!ret) + ret = try_or_set_control_cluster(master, true); + *val = ctrl->cur.val; + v4l2_ctrl_unlock(ctrl); + return ret; +} + +int v4l2_s_ctrl(struct v4l2_ctrl_handler *hdl, struct v4l2_control *control) +{ + struct v4l2_ctrl *ctrl = v4l2_ctrl_find(hdl, control->id); + + if (ctrl == NULL || !type_is_int(ctrl)) + return -EINVAL; + + return set_ctrl(ctrl, &control->value); +} +EXPORT_SYMBOL(v4l2_s_ctrl); + +int v4l2_subdev_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *control) +{ + return v4l2_s_ctrl(sd->ctrl_handler, control); +} +EXPORT_SYMBOL(v4l2_subdev_s_ctrl); + +int v4l2_ctrl_s_ctrl(struct v4l2_ctrl *ctrl, s32 val) +{ + /* It's a driver bug if this happens. */ + WARN_ON(!type_is_int(ctrl)); + return set_ctrl(ctrl, &val); +} +EXPORT_SYMBOL(v4l2_ctrl_s_ctrl); diff --git a/include/media/v4l2-ctrls.h b/include/media/v4l2-ctrls.h new file mode 100644 index 00000000000..9b7bea928a8 --- /dev/null +++ b/include/media/v4l2-ctrls.h @@ -0,0 +1,460 @@ +/* + V4L2 controls support header. + + Copyright (C) 2010 Hans Verkuil + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _V4L2_CTRLS_H +#define _V4L2_CTRLS_H + +#include +#include + +/* forward references */ +struct v4l2_ctrl_handler; +struct v4l2_ctrl; +struct video_device; +struct v4l2_subdev; + +/** struct v4l2_ctrl_ops - The control operations that the driver has to provide. + * @g_volatile_ctrl: Get a new value for this control. Generally only relevant + * for volatile (and usually read-only) controls such as a control + * that returns the current signal strength which changes + * continuously. + * If not set, then the currently cached value will be returned. + * @try_ctrl: Test whether the control's value is valid. Only relevant when + * the usual min/max/step checks are not sufficient. + * @s_ctrl: Actually set the new control value. s_ctrl is compulsory. The + * ctrl->handler->lock is held when these ops are called, so no + * one else can access controls owned by that handler. + */ +struct v4l2_ctrl_ops { + int (*g_volatile_ctrl)(struct v4l2_ctrl *ctrl); + int (*try_ctrl)(struct v4l2_ctrl *ctrl); + int (*s_ctrl)(struct v4l2_ctrl *ctrl); +}; + +/** struct v4l2_ctrl - The control structure. + * @node: The list node. + * @handler: The handler that owns the control. + * @cluster: Point to start of cluster array. + * @ncontrols: Number of controls in cluster array. + * @has_new: Internal flag: set when there is a valid new value. + * @done: Internal flag: set for each processed control. + * @is_private: If set, then this control is private to its handler and it + * will not be added to any other handlers. Drivers can set + * this flag. + * @is_volatile: If set, then this control is volatile. This means that the + * control's current value cannot be cached and needs to be + * retrieved through the g_volatile_ctrl op. Drivers can set + * this flag. + * @ops: The control ops. + * @id: The control ID. + * @name: The control name. + * @type: The control type. + * @minimum: The control's minimum value. + * @maximum: The control's maximum value. + * @default_value: The control's default value. + * @step: The control's step value for non-menu controls. + * @menu_skip_mask: The control's skip mask for menu controls. This makes it + * easy to skip menu items that are not valid. If bit X is set, + * then menu item X is skipped. Of course, this only works for + * menus with <= 32 menu items. There are no menus that come + * close to that number, so this is OK. Should we ever need more, + * then this will have to be extended to a u64 or a bit array. + * @qmenu: A const char * array for all menu items. Array entries that are + * empty strings ("") correspond to non-existing menu items (this + * is in addition to the menu_skip_mask above). The last entry + * must be NULL. + * @flags: The control's flags. + * @cur: The control's current value. + * @val: The control's new s32 value. + * @val64: The control's new s64 value. + * @string: The control's new string value. + * @priv: The control's private pointer. For use by the driver. It is + * untouched by the control framework. Note that this pointer is + * not freed when the control is deleted. Should this be needed + * then a new internal bitfield can be added to tell the framework + * to free this pointer. + */ +struct v4l2_ctrl { + /* Administrative fields */ + struct list_head node; + struct v4l2_ctrl_handler *handler; + struct v4l2_ctrl **cluster; + unsigned ncontrols; + unsigned int has_new:1; + unsigned int done:1; + + unsigned int is_private:1; + unsigned int is_volatile:1; + + const struct v4l2_ctrl_ops *ops; + u32 id; + const char *name; + enum v4l2_ctrl_type type; + s32 minimum, maximum, default_value; + union { + u32 step; + u32 menu_skip_mask; + }; + const char **qmenu; + unsigned long flags; + union { + s32 val; + s64 val64; + char *string; + } cur; + union { + s32 val; + s64 val64; + char *string; + }; + void *priv; +}; + +/** struct v4l2_ctrl_ref - The control reference. + * @node: List node for the sorted list. + * @next: Single-link list node for the hash. + * @ctrl: The actual control information. + * + * Each control handler has a list of these refs. The list_head is used to + * keep a sorted-by-control-ID list of all controls, while the next pointer + * is used to link the control in the hash's bucket. + */ +struct v4l2_ctrl_ref { + struct list_head node; + struct v4l2_ctrl_ref *next; + struct v4l2_ctrl *ctrl; +}; + +/** struct v4l2_ctrl_handler - The control handler keeps track of all the + * controls: both the controls owned by the handler and those inherited + * from other handlers. + * @lock: Lock to control access to this handler and its controls. + * @ctrls: The list of controls owned by this handler. + * @ctrl_refs: The list of control references. + * @cached: The last found control reference. It is common that the same + * control is needed multiple times, so this is a simple + * optimization. + * @buckets: Buckets for the hashing. Allows for quick control lookup. + * @nr_of_buckets: Total number of buckets in the array. + * @error: The error code of the first failed control addition. + */ +struct v4l2_ctrl_handler { + struct mutex lock; + struct list_head ctrls; + struct list_head ctrl_refs; + struct v4l2_ctrl_ref *cached; + struct v4l2_ctrl_ref **buckets; + u16 nr_of_buckets; + int error; +}; + +/** struct v4l2_ctrl_config - Control configuration structure. + * @ops: The control ops. + * @id: The control ID. + * @name: The control name. + * @type: The control type. + * @min: The control's minimum value. + * @max: The control's maximum value. + * @step: The control's step value for non-menu controls. + * @def: The control's default value. + * @flags: The control's flags. + * @menu_skip_mask: The control's skip mask for menu controls. This makes it + * easy to skip menu items that are not valid. If bit X is set, + * then menu item X is skipped. Of course, this only works for + * menus with <= 32 menu items. There are no menus that come + * close to that number, so this is OK. Should we ever need more, + * then this will have to be extended to a u64 or a bit array. + * @qmenu: A const char * array for all menu items. Array entries that are + * empty strings ("") correspond to non-existing menu items (this + * is in addition to the menu_skip_mask above). The last entry + * must be NULL. + * @is_private: If set, then this control is private to its handler and it + * will not be added to any other handlers. + * @is_volatile: If set, then this control is volatile. This means that the + * control's current value cannot be cached and needs to be + * retrieved through the g_volatile_ctrl op. + */ +struct v4l2_ctrl_config { + const struct v4l2_ctrl_ops *ops; + u32 id; + const char *name; + enum v4l2_ctrl_type type; + s32 min; + s32 max; + u32 step; + s32 def; + u32 flags; + u32 menu_skip_mask; + const char **qmenu; + unsigned int is_private:1; + unsigned int is_volatile:1; +}; + +/** v4l2_ctrl_fill() - Fill in the control fields based on the control ID. + * + * This works for all standard V4L2 controls. + * For non-standard controls it will only fill in the given arguments + * and @name will be NULL. + * + * This function will overwrite the contents of @name, @type and @flags. + * The contents of @min, @max, @step and @def may be modified depending on + * the type. + * + * Do not use in drivers! It is used internally for backwards compatibility + * control handling only. Once all drivers are converted to use the new + * control framework this function will no longer be exported. + */ +void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, + s32 *min, s32 *max, s32 *step, s32 *def, u32 *flags); + + +/** v4l2_ctrl_handler_init() - Initialize the control handler. + * @hdl: The control handler. + * @nr_of_controls_hint: A hint of how many controls this handler is + * expected to refer to. This is the total number, so including + * any inherited controls. It doesn't have to be precise, but if + * it is way off, then you either waste memory (too many buckets + * are allocated) or the control lookup becomes slower (not enough + * buckets are allocated, so there are more slow list lookups). + * It will always work, though. + * + * Returns an error if the buckets could not be allocated. This error will + * also be stored in @hdl->error. + */ +int v4l2_ctrl_handler_init(struct v4l2_ctrl_handler *hdl, + unsigned nr_of_controls_hint); + +/** v4l2_ctrl_handler_free() - Free all controls owned by the handler and free + * the control list. + * @hdl: The control handler. + * + * Does nothing if @hdl == NULL. + */ +void v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl); + +/** v4l2_ctrl_handler_setup() - Call the s_ctrl op for all controls belonging + * to the handler to initialize the hardware to the current control values. + * @hdl: The control handler. + * + * Button controls will be skipped, as are read-only controls. + * + * If @hdl == NULL, then this just returns 0. + */ +int v4l2_ctrl_handler_setup(struct v4l2_ctrl_handler *hdl); + +/** v4l2_ctrl_handler_log_status() - Log all controls owned by the handler. + * @hdl: The control handler. + * @prefix: The prefix to use when logging the control values. If the + * prefix does not end with a space, then ": " will be added + * after the prefix. If @prefix == NULL, then no prefix will be + * used. + * + * For use with VIDIOC_LOG_STATUS. + * + * Does nothing if @hdl == NULL. + */ +void v4l2_ctrl_handler_log_status(struct v4l2_ctrl_handler *hdl, + const char *prefix); + +/** v4l2_ctrl_new_custom() - Allocate and initialize a new custom V4L2 + * control. + * @hdl: The control handler. + * @cfg: The control's configuration data. + * @priv: The control's driver-specific private data. + * + * If the &v4l2_ctrl struct could not be allocated then NULL is returned + * and @hdl->error is set to the error code (if it wasn't set already). + */ +struct v4l2_ctrl *v4l2_ctrl_new_custom(struct v4l2_ctrl_handler *hdl, + const struct v4l2_ctrl_config *cfg, void *priv); + +/** v4l2_ctrl_new_std() - Allocate and initialize a new standard V4L2 non-menu control. + * @hdl: The control handler. + * @ops: The control ops. + * @id: The control ID. + * @min: The control's minimum value. + * @max: The control's maximum value. + * @step: The control's step value + * @def: The control's default value. + * + * If the &v4l2_ctrl struct could not be allocated, or the control + * ID is not known, then NULL is returned and @hdl->error is set to the + * appropriate error code (if it wasn't set already). + * + * If @id refers to a menu control, then this function will return NULL. + * + * Use v4l2_ctrl_new_std_menu() when adding menu controls. + */ +struct v4l2_ctrl *v4l2_ctrl_new_std(struct v4l2_ctrl_handler *hdl, + const struct v4l2_ctrl_ops *ops, + u32 id, s32 min, s32 max, u32 step, s32 def); + +/** v4l2_ctrl_new_std_menu() - Allocate and initialize a new standard V4L2 menu control. + * @hdl: The control handler. + * @ops: The control ops. + * @id: The control ID. + * @max: The control's maximum value. + * @mask: The control's skip mask for menu controls. This makes it + * easy to skip menu items that are not valid. If bit X is set, + * then menu item X is skipped. Of course, this only works for + * menus with <= 32 menu items. There are no menus that come + * close to that number, so this is OK. Should we ever need more, + * then this will have to be extended to a u64 or a bit array. + * @def: The control's default value. + * + * Same as v4l2_ctrl_new_std(), but @min is set to 0 and the @mask value + * determines which menu items are to be skipped. + * + * If @id refers to a non-menu control, then this function will return NULL. + */ +struct v4l2_ctrl *v4l2_ctrl_new_std_menu(struct v4l2_ctrl_handler *hdl, + const struct v4l2_ctrl_ops *ops, + u32 id, s32 max, s32 mask, s32 def); + +/** v4l2_ctrl_add_ctrl() - Add a control from another handler to this handler. + * @hdl: The control handler. + * @ctrl: The control to add. + * + * It will return NULL if it was unable to add the control reference. + * If the control already belonged to the handler, then it will do + * nothing and just return @ctrl. + */ +struct v4l2_ctrl *v4l2_ctrl_add_ctrl(struct v4l2_ctrl_handler *hdl, + struct v4l2_ctrl *ctrl); + +/** v4l2_ctrl_add_handler() - Add all controls from handler @add to + * handler @hdl. + * @hdl: The control handler. + * @add: The control handler whose controls you want to add to + * the @hdl control handler. + * + * Does nothing if either of the two is a NULL pointer. + * In case of an error @hdl->error will be set to the error code (if it + * wasn't set already). + */ +int v4l2_ctrl_add_handler(struct v4l2_ctrl_handler *hdl, + struct v4l2_ctrl_handler *add); + + +/** v4l2_ctrl_cluster() - Mark all controls in the cluster as belonging to that cluster. + * @ncontrols: The number of controls in this cluster. + * @controls: The cluster control array of size @ncontrols. + */ +void v4l2_ctrl_cluster(unsigned ncontrols, struct v4l2_ctrl **controls); + + +/** v4l2_ctrl_find() - Find a control with the given ID. + * @hdl: The control handler. + * @id: The control ID to find. + * + * If @hdl == NULL this will return NULL as well. Will lock the handler so + * do not use from inside &v4l2_ctrl_ops. + */ +struct v4l2_ctrl *v4l2_ctrl_find(struct v4l2_ctrl_handler *hdl, u32 id); + +/** v4l2_ctrl_activate() - Make the control active or inactive. + * @ctrl: The control to (de)activate. + * @active: True if the control should become active. + * + * This sets or clears the V4L2_CTRL_FLAG_INACTIVE flag atomically. + * Does nothing if @ctrl == NULL. + * This will usually be called from within the s_ctrl op. + * + * This function can be called regardless of whether the control handler + * is locked or not. + */ +void v4l2_ctrl_activate(struct v4l2_ctrl *ctrl, bool active); + +/** v4l2_ctrl_grab() - Mark the control as grabbed or not grabbed. + * @ctrl: The control to (de)activate. + * @grabbed: True if the control should become grabbed. + * + * This sets or clears the V4L2_CTRL_FLAG_GRABBED flag atomically. + * Does nothing if @ctrl == NULL. + * This will usually be called when starting or stopping streaming in the + * driver. + * + * This function can be called regardless of whether the control handler + * is locked or not. + */ +void v4l2_ctrl_grab(struct v4l2_ctrl *ctrl, bool grabbed); + +/** v4l2_ctrl_lock() - Helper function to lock the handler + * associated with the control. + * @ctrl: The control to lock. + */ +static inline void v4l2_ctrl_lock(struct v4l2_ctrl *ctrl) +{ + mutex_lock(&ctrl->handler->lock); +} + +/** v4l2_ctrl_lock() - Helper function to unlock the handler + * associated with the control. + * @ctrl: The control to unlock. + */ +static inline void v4l2_ctrl_unlock(struct v4l2_ctrl *ctrl) +{ + mutex_unlock(&ctrl->handler->lock); +} + +/** v4l2_ctrl_g_ctrl() - Helper function to get the control's value from within a driver. + * @ctrl: The control. + * + * This returns the control's value safely by going through the control + * framework. This function will lock the control's handler, so it cannot be + * used from within the &v4l2_ctrl_ops functions. + * + * This function is for integer type controls only. + */ +s32 v4l2_ctrl_g_ctrl(struct v4l2_ctrl *ctrl); + +/** v4l2_ctrl_s_ctrl() - Helper function to set the control's value from within a driver. + * @ctrl: The control. + * @val: The new value. + * + * This set the control's new value safely by going through the control + * framework. This function will lock the control's handler, so it cannot be + * used from within the &v4l2_ctrl_ops functions. + * + * This function is for integer type controls only. + */ +int v4l2_ctrl_s_ctrl(struct v4l2_ctrl *ctrl, s32 val); + + +/* Helpers for ioctl_ops. If hdl == NULL then they will all return -EINVAL. */ +int v4l2_queryctrl(struct v4l2_ctrl_handler *hdl, struct v4l2_queryctrl *qc); +int v4l2_querymenu(struct v4l2_ctrl_handler *hdl, struct v4l2_querymenu *qm); +int v4l2_g_ctrl(struct v4l2_ctrl_handler *hdl, struct v4l2_control *ctrl); +int v4l2_s_ctrl(struct v4l2_ctrl_handler *hdl, struct v4l2_control *ctrl); +int v4l2_g_ext_ctrls(struct v4l2_ctrl_handler *hdl, struct v4l2_ext_controls *c); +int v4l2_try_ext_ctrls(struct v4l2_ctrl_handler *hdl, struct v4l2_ext_controls *c); +int v4l2_s_ext_ctrls(struct v4l2_ctrl_handler *hdl, struct v4l2_ext_controls *c); + +/* Helpers for subdevices. If the associated ctrl_handler == NULL then they + will all return -EINVAL. */ +int v4l2_subdev_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc); +int v4l2_subdev_querymenu(struct v4l2_subdev *sd, struct v4l2_querymenu *qm); +int v4l2_subdev_g_ext_ctrls(struct v4l2_subdev *sd, struct v4l2_ext_controls *cs); +int v4l2_subdev_try_ext_ctrls(struct v4l2_subdev *sd, struct v4l2_ext_controls *cs); +int v4l2_subdev_s_ext_ctrls(struct v4l2_subdev *sd, struct v4l2_ext_controls *cs); +int v4l2_subdev_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl); +int v4l2_subdev_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl); + +#endif diff --git a/include/media/v4l2-dev.h b/include/media/v4l2-dev.h index bebe44b03e0..1efcacbed01 100644 --- a/include/media/v4l2-dev.h +++ b/include/media/v4l2-dev.h @@ -27,6 +27,7 @@ struct v4l2_ioctl_callbacks; struct video_device; struct v4l2_device; +struct v4l2_ctrl_handler; /* Flag to mark the video_device struct as registered. Drivers can clear this flag if they want to block all future @@ -67,6 +68,9 @@ struct video_device struct device *parent; /* device parent */ struct v4l2_device *v4l2_dev; /* v4l2_device parent */ + /* Control handler associated with this device node. May be NULL. */ + struct v4l2_ctrl_handler *ctrl_handler; + /* device info */ char name[32]; int vfl_type; diff --git a/include/media/v4l2-device.h b/include/media/v4l2-device.h index 5d5d550e63a..8bcbd7a0271 100644 --- a/include/media/v4l2-device.h +++ b/include/media/v4l2-device.h @@ -32,6 +32,8 @@ #define V4L2_DEVICE_NAME_SIZE (20 + 16) +struct v4l2_ctrl_handler; + struct v4l2_device { /* dev->driver_data points to this struct. Note: dev might be NULL if there is no parent device @@ -47,6 +49,8 @@ struct v4l2_device { /* notify callback called by some sub-devices. */ void (*notify)(struct v4l2_subdev *sd, unsigned int notification, void *arg); + /* The control handler. May be NULL. */ + struct v4l2_ctrl_handler *ctrl_handler; }; /* Initialize v4l2_dev and make dev->driver_data point to v4l2_dev. diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h index 08880dd15d2..4a97d7341a9 100644 --- a/include/media/v4l2-subdev.h +++ b/include/media/v4l2-subdev.h @@ -35,6 +35,7 @@ #define V4L2_SUBDEV_IR_TX_FIFO_SERVICE_REQ 0x00000001 struct v4l2_device; +struct v4l2_ctrl_handler; struct v4l2_subdev; struct tuner_setup; @@ -434,6 +435,8 @@ struct v4l2_subdev { u32 flags; struct v4l2_device *v4l2_dev; const struct v4l2_subdev_ops *ops; + /* The control handler of this subdev. May be NULL. */ + struct v4l2_ctrl_handler *ctrl_handler; /* name must be unique */ char name[V4L2_SUBDEV_NAME_SIZE]; /* can be used to group similar subdevs, value is driver-specific */ -- cgit v1.2.3-70-g09d2 From 72c851b00f6c86353c54fdd9f1ef88d82e8df6c5 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Fri, 6 Aug 2010 10:53:19 -0300 Subject: V4L/DVB: cx25840/ivtv: replace ugly priv control with s_config The cx25840 used a private control CX25840_CID_ENABLE_PVR150_WORKAROUND to be told whether to enable a workaround for certain pvr150 cards. This is really config data that it needs to get at load time. Implemented this in cx25840 and ivtv. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/video/cx25840/cx25840-core.c | 23 +++++++++++++++-------- drivers/media/video/cx25840/cx25840-core.h | 8 -------- drivers/media/video/ivtv/ivtv-driver.c | 9 +-------- drivers/media/video/ivtv/ivtv-i2c.c | 7 +++++++ include/media/cx25840.h | 12 ++++++++++++ 5 files changed, 35 insertions(+), 24 deletions(-) (limited to 'include') diff --git a/drivers/media/video/cx25840/cx25840-core.c b/drivers/media/video/cx25840/cx25840-core.c index 9fab0b17084..69763729ccc 100644 --- a/drivers/media/video/cx25840/cx25840-core.c +++ b/drivers/media/video/cx25840/cx25840-core.c @@ -1127,11 +1127,6 @@ static int cx25840_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) struct i2c_client *client = v4l2_get_subdevdata(sd); switch (ctrl->id) { - case CX25840_CID_ENABLE_PVR150_WORKAROUND: - state->pvr150_workaround = ctrl->value; - set_input(client, state->vid_input, state->aud_input); - break; - case V4L2_CID_BRIGHTNESS: if (ctrl->value < 0 || ctrl->value > 255) { v4l_err(client, "invalid brightness setting %d\n", @@ -1194,9 +1189,6 @@ static int cx25840_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) struct i2c_client *client = v4l2_get_subdevdata(sd); switch (ctrl->id) { - case CX25840_CID_ENABLE_PVR150_WORKAROUND: - ctrl->value = state->pvr150_workaround; - break; case V4L2_CID_BRIGHTNESS: ctrl->value = (s8)cx25840_read(client, 0x414) + 128; break; @@ -1792,6 +1784,20 @@ static int cx25840_log_status(struct v4l2_subdev *sd) return 0; } +static int cx25840_s_config(struct v4l2_subdev *sd, int irq, void *platform_data) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (platform_data) { + struct cx25840_platform_data *pdata = platform_data; + + state->pvr150_workaround = pdata->pvr150_workaround; + set_input(client, state->vid_input, state->aud_input); + } + return 0; +} + static int cx23885_irq_handler(struct v4l2_subdev *sd, u32 status, bool *handled) { @@ -1879,6 +1885,7 @@ static int cx25840_irq_handler(struct v4l2_subdev *sd, u32 status, static const struct v4l2_subdev_core_ops cx25840_core_ops = { .log_status = cx25840_log_status, + .s_config = cx25840_s_config, .g_chip_ident = cx25840_g_chip_ident, .g_ctrl = cx25840_g_ctrl, .s_ctrl = cx25840_s_ctrl, diff --git a/drivers/media/video/cx25840/cx25840-core.h b/drivers/media/video/cx25840/cx25840-core.h index 8f47322c003..8ac57a13a45 100644 --- a/drivers/media/video/cx25840/cx25840-core.h +++ b/drivers/media/video/cx25840/cx25840-core.h @@ -26,14 +26,6 @@ #include #include -/* ENABLE_PVR150_WORKAROUND activates a workaround for a hardware bug that is - present in Hauppauge PVR-150 (and possibly PVR-500) cards that have - certain NTSC tuners (tveeprom tuner model numbers 85, 99 and 112). The - audio autodetect fails on some channels for these models and the workaround - is to select the audio standard explicitly. Many thanks to Hauppauge for - providing this information. */ -#define CX25840_CID_ENABLE_PVR150_WORKAROUND (V4L2_CID_PRIVATE_BASE+0) - struct cx25840_ir_state; struct cx25840_state { diff --git a/drivers/media/video/ivtv/ivtv-driver.c b/drivers/media/video/ivtv/ivtv-driver.c index 07c5c18a25c..f72e9d1cee0 100644 --- a/drivers/media/video/ivtv/ivtv-driver.c +++ b/drivers/media/video/ivtv/ivtv-driver.c @@ -1269,15 +1269,8 @@ int ivtv_init_on_first_open(struct ivtv *itv) IVTV_DEBUG_INFO("Getting firmware version..\n"); ivtv_firmware_versions(itv); - if (itv->card->hw_all & IVTV_HW_CX25840) { - struct v4l2_control ctrl; - + if (itv->card->hw_all & IVTV_HW_CX25840) v4l2_subdev_call(itv->sd_video, core, load_fw); - /* CX25840_CID_ENABLE_PVR150_WORKAROUND */ - ctrl.id = V4L2_CID_PRIVATE_BASE; - ctrl.value = itv->pvr150_workaround; - v4l2_subdev_call(itv->sd_video, core, s_ctrl, &ctrl); - } vf.tuner = 0; vf.type = V4L2_TUNER_ANALOG_TV; diff --git a/drivers/media/video/ivtv/ivtv-i2c.c b/drivers/media/video/ivtv/ivtv-i2c.c index a5b92d109c6..d391bbdb0b8 100644 --- a/drivers/media/video/ivtv/ivtv-i2c.c +++ b/drivers/media/video/ivtv/ivtv-i2c.c @@ -63,6 +63,7 @@ #include "ivtv-cards.h" #include "ivtv-gpio.h" #include "ivtv-i2c.h" +#include /* i2c implementation for cx23415/6 chip, ivtv project. * Author: Kevin Thayer (nufan_wfk at yahoo.com) @@ -292,6 +293,12 @@ int ivtv_i2c_register(struct ivtv *itv, unsigned idx) if (hw == IVTV_HW_UPD64031A || hw == IVTV_HW_UPD6408X) { sd = v4l2_i2c_new_subdev(&itv->v4l2_dev, adap, mod, type, 0, I2C_ADDRS(hw_addrs[idx])); + } else if (hw == IVTV_HW_CX25840) { + struct cx25840_platform_data pdata; + + pdata.pvr150_workaround = itv->pvr150_workaround; + sd = v4l2_i2c_new_subdev_cfg(&itv->v4l2_dev, + adap, mod, type, 0, &pdata, hw_addrs[idx], NULL); } else { sd = v4l2_i2c_new_subdev(&itv->v4l2_dev, adap, mod, type, hw_addrs[idx], NULL); diff --git a/include/media/cx25840.h b/include/media/cx25840.h index 1bba39e3d40..46d1a141208 100644 --- a/include/media/cx25840.h +++ b/include/media/cx25840.h @@ -172,4 +172,16 @@ enum cx23885_io_pad { CX23885_PAD_IRQ_N, CX23885_PAD_GPIO16, }; + +/* pvr150_workaround activates a workaround for a hardware bug that is + present in Hauppauge PVR-150 (and possibly PVR-500) cards that have + certain NTSC tuners (tveeprom tuner model numbers 85, 99 and 112). The + audio autodetect fails on some channels for these models and the workaround + is to select the audio standard explicitly. Many thanks to Hauppauge for + providing this information. + This platform data only needs to be supplied by the ivtv driver. */ +struct cx25840_platform_data { + int pvr150_workaround; +}; + #endif -- cgit v1.2.3-70-g09d2 From f6e114eed0414fbba9eb73480cd8efdaba96aaa7 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Mon, 24 May 2010 10:16:43 -0300 Subject: V4L/DVB: cx2341x: convert to the control framework Since this module is also used by drivers that are not yet converted, the old and new code have to co-exist. The source is split into three parts: a common part at the top, which is used by both old and new code, then the old code followed by the new control framework implementation. This new code is much more readable (and shorter!) than the original code. Once all bridge drivers that use this are converted the old code can be deleted. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/video/cx2341x.c | 747 +++++++++++++++++++++++++++++++++++------- include/media/cx2341x.h | 97 ++++++ 2 files changed, 732 insertions(+), 112 deletions(-) (limited to 'include') diff --git a/drivers/media/video/cx2341x.c b/drivers/media/video/cx2341x.c index 2bf44ef10fe..e5c3c8da4be 100644 --- a/drivers/media/video/cx2341x.c +++ b/drivers/media/video/cx2341x.c @@ -38,6 +38,145 @@ static int debug; module_param(debug, int, 0644); MODULE_PARM_DESC(debug, "Debug level (0-1)"); +/********************** COMMON CODE *********************/ + +/* definitions for audio properties bits 29-28 */ +#define CX2341X_AUDIO_ENCODING_METHOD_MPEG 0 +#define CX2341X_AUDIO_ENCODING_METHOD_AC3 1 +#define CX2341X_AUDIO_ENCODING_METHOD_LPCM 2 + +static const char *cx2341x_get_name(u32 id) +{ + switch (id) { + case V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE: + return "Spatial Filter Mode"; + case V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER: + return "Spatial Filter"; + case V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE: + return "Spatial Luma Filter Type"; + case V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE: + return "Spatial Chroma Filter Type"; + case V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE: + return "Temporal Filter Mode"; + case V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER: + return "Temporal Filter"; + case V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE: + return "Median Filter Type"; + case V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_TOP: + return "Median Luma Filter Maximum"; + case V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_BOTTOM: + return "Median Luma Filter Minimum"; + case V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_TOP: + return "Median Chroma Filter Maximum"; + case V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_BOTTOM: + return "Median Chroma Filter Minimum"; + case V4L2_CID_MPEG_CX2341X_STREAM_INSERT_NAV_PACKETS: + return "Insert Navigation Packets"; + } + return NULL; +} + +static const char **cx2341x_get_menu(u32 id) +{ + static const char *cx2341x_video_spatial_filter_mode_menu[] = { + "Manual", + "Auto", + NULL + }; + + static const char *cx2341x_video_luma_spatial_filter_type_menu[] = { + "Off", + "1D Horizontal", + "1D Vertical", + "2D H/V Separable", + "2D Symmetric non-separable", + NULL + }; + + static const char *cx2341x_video_chroma_spatial_filter_type_menu[] = { + "Off", + "1D Horizontal", + NULL + }; + + static const char *cx2341x_video_temporal_filter_mode_menu[] = { + "Manual", + "Auto", + NULL + }; + + static const char *cx2341x_video_median_filter_type_menu[] = { + "Off", + "Horizontal", + "Vertical", + "Horizontal/Vertical", + "Diagonal", + NULL + }; + + switch (id) { + case V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE: + return cx2341x_video_spatial_filter_mode_menu; + case V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE: + return cx2341x_video_luma_spatial_filter_type_menu; + case V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE: + return cx2341x_video_chroma_spatial_filter_type_menu; + case V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE: + return cx2341x_video_temporal_filter_mode_menu; + case V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE: + return cx2341x_video_median_filter_type_menu; + } + return NULL; +} + +static void cx2341x_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, + s32 *min, s32 *max, s32 *step, s32 *def, u32 *flags) +{ + *name = cx2341x_get_name(id); + *flags = 0; + + switch (id) { + case V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE: + case V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE: + case V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE: + case V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE: + case V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE: + *type = V4L2_CTRL_TYPE_MENU; + *min = 0; + *step = 0; + break; + case V4L2_CID_MPEG_CX2341X_STREAM_INSERT_NAV_PACKETS: + *type = V4L2_CTRL_TYPE_BOOLEAN; + *min = 0; + *max = *step = 1; + break; + default: + *type = V4L2_CTRL_TYPE_INTEGER; + break; + } + switch (id) { + case V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE: + case V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE: + case V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE: + *flags |= V4L2_CTRL_FLAG_UPDATE; + break; + case V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER: + case V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER: + case V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_TOP: + case V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_BOTTOM: + case V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_TOP: + case V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_BOTTOM: + *flags |= V4L2_CTRL_FLAG_SLIDER; + break; + case V4L2_CID_MPEG_VIDEO_ENCODING: + *flags |= V4L2_CTRL_FLAG_READ_ONLY; + break; + } +} + + +/********************** OLD CODE *********************/ + /* Must be sorted from low to high control ID! */ const u32 cx2341x_mpeg_ctrls[] = { V4L2_CID_MPEG_CLASS, @@ -134,8 +273,6 @@ static const struct cx2341x_mpeg_params default_params = { .video_chroma_median_filter_top = 255, .video_chroma_median_filter_bottom = 0, }; - - /* Map the control ID to the correct field in the cx2341x_mpeg_params struct. Return -EINVAL if the ID is unknown, else return 0. */ static int cx2341x_get_ctrl(const struct cx2341x_mpeg_params *params, @@ -415,83 +552,33 @@ static int cx2341x_ctrl_query_fill(struct v4l2_queryctrl *qctrl, { const char *name; - qctrl->flags = 0; switch (qctrl->id) { /* MPEG controls */ case V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE: - name = "Spatial Filter Mode"; - break; case V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER: - name = "Spatial Filter"; - break; case V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE: - name = "Spatial Luma Filter Type"; - break; case V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE: - name = "Spatial Chroma Filter Type"; - break; case V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE: - name = "Temporal Filter Mode"; - break; case V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER: - name = "Temporal Filter"; - break; case V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE: - name = "Median Filter Type"; - break; case V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_TOP: - name = "Median Luma Filter Maximum"; - break; case V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_BOTTOM: - name = "Median Luma Filter Minimum"; - break; case V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_TOP: - name = "Median Chroma Filter Maximum"; - break; case V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_BOTTOM: - name = "Median Chroma Filter Minimum"; - break; case V4L2_CID_MPEG_CX2341X_STREAM_INSERT_NAV_PACKETS: - name = "Insert Navigation Packets"; - break; + cx2341x_ctrl_fill(qctrl->id, &name, &qctrl->type, + &min, &max, &step, &def, &qctrl->flags); + qctrl->minimum = min; + qctrl->maximum = max; + qctrl->step = step; + qctrl->default_value = def; + qctrl->reserved[0] = qctrl->reserved[1] = 0; + strlcpy(qctrl->name, name, sizeof(qctrl->name)); + return 0; default: return v4l2_ctrl_query_fill(qctrl, min, max, step, def); } - switch (qctrl->id) { - case V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE: - case V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE: - case V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE: - case V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE: - case V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE: - qctrl->type = V4L2_CTRL_TYPE_MENU; - min = 0; - step = 1; - break; - case V4L2_CID_MPEG_CX2341X_STREAM_INSERT_NAV_PACKETS: - qctrl->type = V4L2_CTRL_TYPE_BOOLEAN; - min = 0; - max = 1; - step = 1; - break; - default: - qctrl->type = V4L2_CTRL_TYPE_INTEGER; - break; - } - switch (qctrl->id) { - case V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE: - case V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE: - case V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE: - qctrl->flags |= V4L2_CTRL_FLAG_UPDATE; - break; - } - qctrl->minimum = min; - qctrl->maximum = max; - qctrl->step = step; - qctrl->default_value = def; - qctrl->reserved[0] = qctrl->reserved[1] = 0; - snprintf(qctrl->name, sizeof(qctrl->name), name); - return 0; } int cx2341x_ctrl_query(const struct cx2341x_mpeg_params *params, @@ -797,42 +884,6 @@ const char **cx2341x_ctrl_get_menu(const struct cx2341x_mpeg_params *p, u32 id) NULL }; - static const char *cx2341x_video_spatial_filter_mode_menu[] = { - "Manual", - "Auto", - NULL - }; - - static const char *cx2341x_video_luma_spatial_filter_type_menu[] = { - "Off", - "1D Horizontal", - "1D Vertical", - "2D H/V Separable", - "2D Symmetric non-separable", - NULL - }; - - static const char *cx2341x_video_chroma_spatial_filter_type_menu[] = { - "Off", - "1D Horizontal", - NULL - }; - - static const char *cx2341x_video_temporal_filter_mode_menu[] = { - "Manual", - "Auto", - NULL - }; - - static const char *cx2341x_video_median_filter_type_menu[] = { - "Off", - "Horizontal", - "Vertical", - "Horizontal/Vertical", - "Diagonal", - NULL - }; - switch (id) { case V4L2_CID_MPEG_STREAM_TYPE: return (p->capabilities & CX2341X_CAP_HAS_TS) ? @@ -844,26 +895,17 @@ const char **cx2341x_ctrl_get_menu(const struct cx2341x_mpeg_params *p, u32 id) case V4L2_CID_MPEG_AUDIO_L3_BITRATE: return NULL; case V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE: - return cx2341x_video_spatial_filter_mode_menu; case V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE: - return cx2341x_video_luma_spatial_filter_type_menu; case V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE: - return cx2341x_video_chroma_spatial_filter_type_menu; case V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE: - return cx2341x_video_temporal_filter_mode_menu; case V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE: - return cx2341x_video_median_filter_type_menu; + return cx2341x_get_menu(id); default: return v4l2_ctrl_get_menu(id); } } EXPORT_SYMBOL(cx2341x_ctrl_get_menu); -/* definitions for audio properties bits 29-28 */ -#define CX2341X_AUDIO_ENCODING_METHOD_MPEG 0 -#define CX2341X_AUDIO_ENCODING_METHOD_AC3 1 -#define CX2341X_AUDIO_ENCODING_METHOD_LPCM 2 - static void cx2341x_calc_audio_properties(struct cx2341x_mpeg_params *params) { params->audio_properties = @@ -1195,9 +1237,490 @@ void cx2341x_log_status(const struct cx2341x_mpeg_params *p, const char *prefix) } EXPORT_SYMBOL(cx2341x_log_status); -/* - * Local variables: - * c-basic-offset: 8 - * End: - */ + +/********************** NEW CODE *********************/ + +static inline struct cx2341x_handler *to_cxhdl(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct cx2341x_handler, hdl); +} + +static int cx2341x_hdl_api(struct cx2341x_handler *hdl, + u32 cmd, int args, ...) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + va_list vargs; + int i; + + va_start(vargs, args); + + for (i = 0; i < args; i++) + data[i] = va_arg(vargs, int); + va_end(vargs); + return hdl->func(hdl->priv, cmd, args, 0, data); +} + +/* ctrl->handler->lock is held, so it is safe to access cur.val */ +static inline int cx2341x_neq(struct v4l2_ctrl *ctrl) +{ + return ctrl && ctrl->val != ctrl->cur.val; +} + +static int cx2341x_try_ctrl(struct v4l2_ctrl *ctrl) +{ + struct cx2341x_handler *hdl = to_cxhdl(ctrl); + s32 val = ctrl->val; + + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_B_FRAMES: { + /* video gop cluster */ + int b = val + 1; + int gop = hdl->video_gop_size->val; + + gop = b * ((gop + b - 1) / b); + + /* Max GOP size = 34 */ + while (gop > 34) + gop -= b; + hdl->video_gop_size->val = gop; + break; + } + + case V4L2_CID_MPEG_STREAM_TYPE: + /* stream type cluster */ + hdl->video_encoding->val = + (hdl->stream_type->val == V4L2_MPEG_STREAM_TYPE_MPEG1_SS || + hdl->stream_type->val == V4L2_MPEG_STREAM_TYPE_MPEG1_VCD) ? + V4L2_MPEG_VIDEO_ENCODING_MPEG_1 : + V4L2_MPEG_VIDEO_ENCODING_MPEG_2; + if (hdl->video_encoding->val == V4L2_MPEG_VIDEO_ENCODING_MPEG_1) + /* MPEG-1 implies CBR */ + hdl->video_bitrate_mode->val = + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR; + /* peak bitrate shall be >= normal bitrate */ + if (hdl->video_bitrate_mode->val == V4L2_MPEG_VIDEO_BITRATE_MODE_VBR && + hdl->video_bitrate_peak->val < hdl->video_bitrate->val) + hdl->video_bitrate_peak->val = hdl->video_bitrate->val; + break; + } + return 0; +} + +static int cx2341x_s_ctrl(struct v4l2_ctrl *ctrl) +{ + static const int mpeg_stream_type[] = { + 0, /* MPEG-2 PS */ + 1, /* MPEG-2 TS */ + 2, /* MPEG-1 SS */ + 14, /* DVD */ + 11, /* VCD */ + 12, /* SVCD */ + }; + struct cx2341x_handler *hdl = to_cxhdl(ctrl); + s32 val = ctrl->val; + u32 props; + int err; + + switch (ctrl->id) { + case V4L2_CID_MPEG_STREAM_VBI_FMT: + if (hdl->ops && hdl->ops->s_stream_vbi_fmt) + return hdl->ops->s_stream_vbi_fmt(hdl, val); + return 0; + + case V4L2_CID_MPEG_VIDEO_ASPECT: + return cx2341x_hdl_api(hdl, + CX2341X_ENC_SET_ASPECT_RATIO, 1, val + 1); + + case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE: + return cx2341x_hdl_api(hdl, CX2341X_ENC_SET_GOP_CLOSURE, 1, val); + + case V4L2_CID_MPEG_AUDIO_MUTE: + return cx2341x_hdl_api(hdl, CX2341X_ENC_MUTE_AUDIO, 1, val); + + case V4L2_CID_MPEG_VIDEO_TEMPORAL_DECIMATION: + return cx2341x_hdl_api(hdl, + CX2341X_ENC_SET_FRAME_DROP_RATE, 1, val); + + case V4L2_CID_MPEG_CX2341X_STREAM_INSERT_NAV_PACKETS: + return cx2341x_hdl_api(hdl, CX2341X_ENC_MISC, 2, 7, val); + + case V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ: + /* audio properties cluster */ + props = (hdl->audio_sampling_freq->val << 0) | + (hdl->audio_mode->val << 8) | + (hdl->audio_mode_extension->val << 10) | + (hdl->audio_crc->val << 14); + if (hdl->audio_emphasis->val == V4L2_MPEG_AUDIO_EMPHASIS_CCITT_J17) + props |= 3 << 12; + else + props |= hdl->audio_emphasis->val << 12; + + if (hdl->audio_encoding->val == V4L2_MPEG_AUDIO_ENCODING_AC3) { + props |= +#if 1 + /* Not sure if this MPEG Layer II setting is required */ + ((3 - V4L2_MPEG_AUDIO_ENCODING_LAYER_2) << 2) | +#endif + (hdl->audio_ac3_bitrate->val << 4) | + (CX2341X_AUDIO_ENCODING_METHOD_AC3 << 28); + } else { + /* Assuming MPEG Layer II */ + props |= + ((3 - hdl->audio_encoding->val) << 2) | + ((1 + hdl->audio_l2_bitrate->val) << 4); + } + err = cx2341x_hdl_api(hdl, + CX2341X_ENC_SET_AUDIO_PROPERTIES, 1, props); + if (err) + return err; + + hdl->audio_properties = props; + if (hdl->audio_ac3_bitrate) { + int is_ac3 = hdl->audio_encoding->val == + V4L2_MPEG_AUDIO_ENCODING_AC3; + + v4l2_ctrl_activate(hdl->audio_ac3_bitrate, is_ac3); + v4l2_ctrl_activate(hdl->audio_l2_bitrate, !is_ac3); + } + v4l2_ctrl_activate(hdl->audio_mode_extension, + hdl->audio_mode->val == V4L2_MPEG_AUDIO_MODE_JOINT_STEREO); + if (cx2341x_neq(hdl->audio_sampling_freq) && + hdl->ops && hdl->ops->s_audio_sampling_freq) + return hdl->ops->s_audio_sampling_freq(hdl, hdl->audio_sampling_freq->val); + if (cx2341x_neq(hdl->audio_mode) && + hdl->ops && hdl->ops->s_audio_mode) + return hdl->ops->s_audio_mode(hdl, hdl->audio_mode->val); + return 0; + + case V4L2_CID_MPEG_VIDEO_B_FRAMES: + /* video gop cluster */ + return cx2341x_hdl_api(hdl, CX2341X_ENC_SET_GOP_PROPERTIES, 2, + hdl->video_gop_size->val, + hdl->video_b_frames->val + 1); + + case V4L2_CID_MPEG_STREAM_TYPE: + /* stream type cluster */ + err = cx2341x_hdl_api(hdl, + CX2341X_ENC_SET_STREAM_TYPE, 1, mpeg_stream_type[val]); + if (err) + return err; + + err = cx2341x_hdl_api(hdl, CX2341X_ENC_SET_BIT_RATE, 5, + hdl->video_bitrate_mode->val, + hdl->video_bitrate->val, + hdl->video_bitrate_peak->val / 400, 0, 0); + if (err) + return err; + + v4l2_ctrl_activate(hdl->video_bitrate_mode, + hdl->video_encoding->val != V4L2_MPEG_VIDEO_ENCODING_MPEG_1); + v4l2_ctrl_activate(hdl->video_bitrate_peak, + hdl->video_bitrate_mode->val != V4L2_MPEG_VIDEO_BITRATE_MODE_CBR); + if (cx2341x_neq(hdl->video_encoding) && + hdl->ops && hdl->ops->s_video_encoding) + return hdl->ops->s_video_encoding(hdl, hdl->video_encoding->val); + return 0; + + case V4L2_CID_MPEG_VIDEO_MUTE: + /* video mute cluster */ + return cx2341x_hdl_api(hdl, CX2341X_ENC_MUTE_VIDEO, 1, + hdl->video_mute->val | + (hdl->video_mute_yuv->val << 8)); + + case V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE: { + int active_filter; + + /* video filter mode */ + err = cx2341x_hdl_api(hdl, CX2341X_ENC_SET_DNR_FILTER_MODE, 2, + hdl->video_spatial_filter_mode->val | + (hdl->video_temporal_filter_mode->val << 1), + hdl->video_median_filter_type->val); + if (err) + return err; + + active_filter = hdl->video_spatial_filter_mode->val != + V4L2_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE_AUTO; + v4l2_ctrl_activate(hdl->video_spatial_filter, active_filter); + v4l2_ctrl_activate(hdl->video_luma_spatial_filter_type, active_filter); + v4l2_ctrl_activate(hdl->video_chroma_spatial_filter_type, active_filter); + active_filter = hdl->video_temporal_filter_mode->val != + V4L2_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE_AUTO; + v4l2_ctrl_activate(hdl->video_temporal_filter, active_filter); + active_filter = hdl->video_median_filter_type->val != + V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_OFF; + v4l2_ctrl_activate(hdl->video_luma_median_filter_bottom, active_filter); + v4l2_ctrl_activate(hdl->video_luma_median_filter_top, active_filter); + v4l2_ctrl_activate(hdl->video_chroma_median_filter_bottom, active_filter); + v4l2_ctrl_activate(hdl->video_chroma_median_filter_top, active_filter); + return 0; + } + + case V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE: + /* video filter type cluster */ + return cx2341x_hdl_api(hdl, + CX2341X_ENC_SET_SPATIAL_FILTER_TYPE, 2, + hdl->video_luma_spatial_filter_type->val, + hdl->video_chroma_spatial_filter_type->val); + + case V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER: + /* video filter cluster */ + return cx2341x_hdl_api(hdl, CX2341X_ENC_SET_DNR_FILTER_PROPS, 2, + hdl->video_spatial_filter->val, + hdl->video_temporal_filter->val); + + case V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_TOP: + /* video median cluster */ + return cx2341x_hdl_api(hdl, CX2341X_ENC_SET_CORING_LEVELS, 4, + hdl->video_luma_median_filter_bottom->val, + hdl->video_luma_median_filter_top->val, + hdl->video_chroma_median_filter_bottom->val, + hdl->video_chroma_median_filter_top->val); + } + return -EINVAL; +} + +static const struct v4l2_ctrl_ops cx2341x_ops = { + .try_ctrl = cx2341x_try_ctrl, + .s_ctrl = cx2341x_s_ctrl, +}; + +static struct v4l2_ctrl *cx2341x_ctrl_new_custom(struct v4l2_ctrl_handler *hdl, + u32 id, s32 min, s32 max, s32 step, s32 def) +{ + struct v4l2_ctrl_config cfg; + + cx2341x_ctrl_fill(id, &cfg.name, &cfg.type, &min, &max, &step, &def, &cfg.flags); + cfg.ops = &cx2341x_ops; + cfg.id = id; + cfg.min = min; + cfg.max = max; + cfg.def = def; + if (cfg.type == V4L2_CTRL_TYPE_MENU) { + cfg.step = 0; + cfg.menu_skip_mask = step; + cfg.qmenu = cx2341x_get_menu(id); + } else { + cfg.step = step; + cfg.menu_skip_mask = 0; + } + return v4l2_ctrl_new_custom(hdl, &cfg, NULL); +} + +static struct v4l2_ctrl *cx2341x_ctrl_new_std(struct v4l2_ctrl_handler *hdl, + u32 id, s32 min, s32 max, s32 step, s32 def) +{ + return v4l2_ctrl_new_std(hdl, &cx2341x_ops, id, min, max, step, def); +} + +static struct v4l2_ctrl *cx2341x_ctrl_new_menu(struct v4l2_ctrl_handler *hdl, + u32 id, s32 max, s32 mask, s32 def) +{ + return v4l2_ctrl_new_std_menu(hdl, &cx2341x_ops, id, max, mask, def); +} + +int cx2341x_handler_init(struct cx2341x_handler *cxhdl, + unsigned nr_of_controls_hint) +{ + struct v4l2_ctrl_handler *hdl = &cxhdl->hdl; + u32 caps = cxhdl->capabilities; + int has_sliced_vbi = caps & CX2341X_CAP_HAS_SLICED_VBI; + int has_ac3 = caps & CX2341X_CAP_HAS_AC3; + int has_ts = caps & CX2341X_CAP_HAS_TS; + + cxhdl->width = 720; + cxhdl->height = 480; + + v4l2_ctrl_handler_init(hdl, nr_of_controls_hint); + + /* Add controls in ascending control ID order for fastest + insertion time. */ + cxhdl->stream_type = cx2341x_ctrl_new_menu(hdl, + V4L2_CID_MPEG_STREAM_TYPE, + V4L2_MPEG_STREAM_TYPE_MPEG2_SVCD, has_ts ? 0 : 2, + V4L2_MPEG_STREAM_TYPE_MPEG2_PS); + cxhdl->stream_vbi_fmt = cx2341x_ctrl_new_menu(hdl, + V4L2_CID_MPEG_STREAM_VBI_FMT, + V4L2_MPEG_STREAM_VBI_FMT_IVTV, has_sliced_vbi ? 0 : 2, + V4L2_MPEG_STREAM_VBI_FMT_NONE); + cxhdl->audio_sampling_freq = cx2341x_ctrl_new_menu(hdl, + V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ, + V4L2_MPEG_AUDIO_SAMPLING_FREQ_32000, 0, + V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000); + cxhdl->audio_encoding = cx2341x_ctrl_new_menu(hdl, + V4L2_CID_MPEG_AUDIO_ENCODING, + V4L2_MPEG_AUDIO_ENCODING_AC3, has_ac3 ? ~0x12 : ~0x2, + V4L2_MPEG_AUDIO_ENCODING_LAYER_2); + cxhdl->audio_l2_bitrate = cx2341x_ctrl_new_menu(hdl, + V4L2_CID_MPEG_AUDIO_L2_BITRATE, + V4L2_MPEG_AUDIO_L2_BITRATE_384K, 0x1ff, + V4L2_MPEG_AUDIO_L2_BITRATE_224K); + cxhdl->audio_mode = cx2341x_ctrl_new_menu(hdl, + V4L2_CID_MPEG_AUDIO_MODE, + V4L2_MPEG_AUDIO_MODE_MONO, 0, + V4L2_MPEG_AUDIO_MODE_STEREO); + cxhdl->audio_mode_extension = cx2341x_ctrl_new_menu(hdl, + V4L2_CID_MPEG_AUDIO_MODE_EXTENSION, + V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_16, 0, + V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_4); + cxhdl->audio_emphasis = cx2341x_ctrl_new_menu(hdl, + V4L2_CID_MPEG_AUDIO_EMPHASIS, + V4L2_MPEG_AUDIO_EMPHASIS_CCITT_J17, 0, + V4L2_MPEG_AUDIO_EMPHASIS_NONE); + cxhdl->audio_crc = cx2341x_ctrl_new_menu(hdl, + V4L2_CID_MPEG_AUDIO_CRC, + V4L2_MPEG_AUDIO_CRC_CRC16, 0, + V4L2_MPEG_AUDIO_CRC_NONE); + + cx2341x_ctrl_new_std(hdl, V4L2_CID_MPEG_AUDIO_MUTE, 0, 1, 1, 0); + if (has_ac3) + cxhdl->audio_ac3_bitrate = cx2341x_ctrl_new_menu(hdl, + V4L2_CID_MPEG_AUDIO_AC3_BITRATE, + V4L2_MPEG_AUDIO_AC3_BITRATE_448K, 0x03, + V4L2_MPEG_AUDIO_AC3_BITRATE_224K); + cxhdl->video_encoding = cx2341x_ctrl_new_menu(hdl, + V4L2_CID_MPEG_VIDEO_ENCODING, + V4L2_MPEG_VIDEO_ENCODING_MPEG_2, 0, + V4L2_MPEG_VIDEO_ENCODING_MPEG_2); + cx2341x_ctrl_new_menu(hdl, + V4L2_CID_MPEG_VIDEO_ASPECT, + V4L2_MPEG_VIDEO_ASPECT_221x100, 0, + V4L2_MPEG_VIDEO_ASPECT_4x3); + cxhdl->video_b_frames = cx2341x_ctrl_new_std(hdl, + V4L2_CID_MPEG_VIDEO_B_FRAMES, 0, 33, 1, 2); + cxhdl->video_gop_size = cx2341x_ctrl_new_std(hdl, + V4L2_CID_MPEG_VIDEO_GOP_SIZE, + 1, 34, 1, cxhdl->is_50hz ? 12 : 15); + cx2341x_ctrl_new_std(hdl, V4L2_CID_MPEG_VIDEO_GOP_CLOSURE, 0, 1, 1, 1); + cxhdl->video_bitrate_mode = cx2341x_ctrl_new_menu(hdl, + V4L2_CID_MPEG_VIDEO_BITRATE_MODE, + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR, 0, + V4L2_MPEG_VIDEO_BITRATE_MODE_VBR); + cxhdl->video_bitrate = cx2341x_ctrl_new_std(hdl, + V4L2_CID_MPEG_VIDEO_BITRATE, + 0, 27000000, 1, 6000000); + cxhdl->video_bitrate_peak = cx2341x_ctrl_new_std(hdl, + V4L2_CID_MPEG_VIDEO_BITRATE_PEAK, + 0, 27000000, 1, 8000000); + cx2341x_ctrl_new_std(hdl, + V4L2_CID_MPEG_VIDEO_TEMPORAL_DECIMATION, 0, 255, 1, 0); + cxhdl->video_mute = cx2341x_ctrl_new_std(hdl, + V4L2_CID_MPEG_VIDEO_MUTE, 0, 1, 1, 0); + cxhdl->video_mute_yuv = cx2341x_ctrl_new_std(hdl, + V4L2_CID_MPEG_VIDEO_MUTE_YUV, 0, 0xffffff, 1, 0x008080); + + /* CX23415/6 specific */ + cxhdl->video_spatial_filter_mode = cx2341x_ctrl_new_custom(hdl, + V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE, + V4L2_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE_MANUAL, + V4L2_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE_AUTO, 0, + V4L2_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE_MANUAL); + cxhdl->video_spatial_filter = cx2341x_ctrl_new_custom(hdl, + V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER, + 0, 15, 1, 0); + cxhdl->video_luma_spatial_filter_type = cx2341x_ctrl_new_custom(hdl, + V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE, + V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_OFF, + V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_2D_SYM_NON_SEPARABLE, + 0, + V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_1D_HOR); + cxhdl->video_chroma_spatial_filter_type = cx2341x_ctrl_new_custom(hdl, + V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE, + V4L2_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE_OFF, + V4L2_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE_1D_HOR, + 0, + V4L2_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE_1D_HOR); + cxhdl->video_temporal_filter_mode = cx2341x_ctrl_new_custom(hdl, + V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE, + V4L2_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE_MANUAL, + V4L2_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE_AUTO, + 0, + V4L2_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE_MANUAL); + cxhdl->video_temporal_filter = cx2341x_ctrl_new_custom(hdl, + V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER, + 0, 31, 1, 8); + cxhdl->video_median_filter_type = cx2341x_ctrl_new_custom(hdl, + V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE, + V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_OFF, + V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_DIAG, + 0, + V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_OFF); + cxhdl->video_luma_median_filter_bottom = cx2341x_ctrl_new_custom(hdl, + V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_BOTTOM, + 0, 255, 1, 0); + cxhdl->video_luma_median_filter_top = cx2341x_ctrl_new_custom(hdl, + V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_TOP, + 0, 255, 1, 255); + cxhdl->video_chroma_median_filter_bottom = cx2341x_ctrl_new_custom(hdl, + V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_BOTTOM, + 0, 255, 1, 0); + cxhdl->video_chroma_median_filter_top = cx2341x_ctrl_new_custom(hdl, + V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_TOP, + 0, 255, 1, 255); + cx2341x_ctrl_new_custom(hdl, V4L2_CID_MPEG_CX2341X_STREAM_INSERT_NAV_PACKETS, + 0, 1, 1, 0); + + if (hdl->error) { + int err = hdl->error; + + v4l2_ctrl_handler_free(hdl); + return err; + } + + v4l2_ctrl_cluster(8, &cxhdl->audio_sampling_freq); + v4l2_ctrl_cluster(2, &cxhdl->video_b_frames); + v4l2_ctrl_cluster(5, &cxhdl->stream_type); + v4l2_ctrl_cluster(2, &cxhdl->video_mute); + v4l2_ctrl_cluster(3, &cxhdl->video_spatial_filter_mode); + v4l2_ctrl_cluster(2, &cxhdl->video_luma_spatial_filter_type); + v4l2_ctrl_cluster(2, &cxhdl->video_spatial_filter); + v4l2_ctrl_cluster(4, &cxhdl->video_luma_median_filter_top); + + return 0; +} +EXPORT_SYMBOL(cx2341x_handler_init); + +void cx2341x_handler_set_50hz(struct cx2341x_handler *cxhdl, int is_50hz) +{ + cxhdl->is_50hz = is_50hz; + cxhdl->video_gop_size->default_value = cxhdl->is_50hz ? 12 : 15; +} +EXPORT_SYMBOL(cx2341x_handler_set_50hz); + +int cx2341x_handler_setup(struct cx2341x_handler *cxhdl) +{ + int h = cxhdl->height; + int w = cxhdl->width; + int err; + + err = cx2341x_hdl_api(cxhdl, CX2341X_ENC_SET_OUTPUT_PORT, 2, cxhdl->port, 0); + if (err) + return err; + err = cx2341x_hdl_api(cxhdl, CX2341X_ENC_SET_FRAME_RATE, 1, cxhdl->is_50hz); + if (err) + return err; + + if (v4l2_ctrl_g_ctrl(cxhdl->video_encoding) == V4L2_MPEG_VIDEO_ENCODING_MPEG_1) { + w /= 2; + h /= 2; + } + err = cx2341x_hdl_api(cxhdl, CX2341X_ENC_SET_FRAME_SIZE, 2, h, w); + if (err) + return err; + return v4l2_ctrl_handler_setup(&cxhdl->hdl); +} +EXPORT_SYMBOL(cx2341x_handler_setup); + +void cx2341x_handler_set_busy(struct cx2341x_handler *cxhdl, int busy) +{ + v4l2_ctrl_grab(cxhdl->audio_sampling_freq, busy); + v4l2_ctrl_grab(cxhdl->audio_encoding, busy); + v4l2_ctrl_grab(cxhdl->audio_l2_bitrate, busy); + v4l2_ctrl_grab(cxhdl->audio_ac3_bitrate, busy); + v4l2_ctrl_grab(cxhdl->stream_vbi_fmt, busy); + v4l2_ctrl_grab(cxhdl->stream_type, busy); + v4l2_ctrl_grab(cxhdl->video_bitrate_mode, busy); + v4l2_ctrl_grab(cxhdl->video_bitrate, busy); + v4l2_ctrl_grab(cxhdl->video_bitrate_peak, busy); +} +EXPORT_SYMBOL(cx2341x_handler_set_busy); diff --git a/include/media/cx2341x.h b/include/media/cx2341x.h index 9ebe8558b9b..8d08ebfe20b 100644 --- a/include/media/cx2341x.h +++ b/include/media/cx2341x.h @@ -19,6 +19,8 @@ #ifndef CX2341X_H #define CX2341X_H +#include + enum cx2341x_port { CX2341X_PORT_MEMORY = 0, CX2341X_PORT_STREAMING = 1, @@ -99,6 +101,101 @@ int cx2341x_ext_ctrls(struct cx2341x_mpeg_params *params, int busy, void cx2341x_fill_defaults(struct cx2341x_mpeg_params *p); void cx2341x_log_status(const struct cx2341x_mpeg_params *p, const char *prefix); +struct cx2341x_handler; + +struct cx2341x_handler_ops { + /* needed for the video clock freq */ + int (*s_audio_sampling_freq)(struct cx2341x_handler *hdl, u32 val); + /* needed for dualwatch */ + int (*s_audio_mode)(struct cx2341x_handler *hdl, u32 val); + /* needed for setting up the video resolution */ + int (*s_video_encoding)(struct cx2341x_handler *hdl, u32 val); + /* needed for setting up the sliced vbi insertion data structures */ + int (*s_stream_vbi_fmt)(struct cx2341x_handler *hdl, u32 val); +}; + +struct cx2341x_handler { + u32 capabilities; + enum cx2341x_port port; + u16 width; + u16 height; + u16 is_50hz; + u32 audio_properties; + + struct v4l2_ctrl_handler hdl; + void *priv; + cx2341x_mbox_func func; + const struct cx2341x_handler_ops *ops; + + struct v4l2_ctrl *stream_vbi_fmt; + + struct { + /* audio cluster */ + struct v4l2_ctrl *audio_sampling_freq; + struct v4l2_ctrl *audio_encoding; + struct v4l2_ctrl *audio_l2_bitrate; + struct v4l2_ctrl *audio_mode; + struct v4l2_ctrl *audio_mode_extension; + struct v4l2_ctrl *audio_emphasis; + struct v4l2_ctrl *audio_crc; + struct v4l2_ctrl *audio_ac3_bitrate; + }; + + struct { + /* video gop cluster */ + struct v4l2_ctrl *video_b_frames; + struct v4l2_ctrl *video_gop_size; + }; + + struct { + /* stream type cluster */ + struct v4l2_ctrl *stream_type; + struct v4l2_ctrl *video_encoding; + struct v4l2_ctrl *video_bitrate_mode; + struct v4l2_ctrl *video_bitrate; + struct v4l2_ctrl *video_bitrate_peak; + }; + + struct { + /* video mute cluster */ + struct v4l2_ctrl *video_mute; + struct v4l2_ctrl *video_mute_yuv; + }; + + struct { + /* video filter mode cluster */ + struct v4l2_ctrl *video_spatial_filter_mode; + struct v4l2_ctrl *video_temporal_filter_mode; + struct v4l2_ctrl *video_median_filter_type; + }; + + struct { + /* video filter type cluster */ + struct v4l2_ctrl *video_luma_spatial_filter_type; + struct v4l2_ctrl *video_chroma_spatial_filter_type; + }; + + struct { + /* video filter cluster */ + struct v4l2_ctrl *video_spatial_filter; + struct v4l2_ctrl *video_temporal_filter; + }; + + struct { + /* video median cluster */ + struct v4l2_ctrl *video_luma_median_filter_top; + struct v4l2_ctrl *video_luma_median_filter_bottom; + struct v4l2_ctrl *video_chroma_median_filter_top; + struct v4l2_ctrl *video_chroma_median_filter_bottom; + }; +}; + +int cx2341x_handler_init(struct cx2341x_handler *cxhdl, + unsigned nr_of_controls_hint); +void cx2341x_handler_set_50hz(struct cx2341x_handler *cxhdl, int is_50hz); +int cx2341x_handler_setup(struct cx2341x_handler *cxhdl); +void cx2341x_handler_set_busy(struct cx2341x_handler *cxhdl, int busy); + /* Firmware names */ #define CX2341X_FIRM_ENC_FILENAME "v4l-cx2341x-enc.fw" /* Decoder firmware for the cx23415 only */ -- cgit v1.2.3-70-g09d2