1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 2cb7a01acSMauro Carvalho Chehab /* 3cb7a01acSMauro Carvalho Chehab * Driver for MT9P031 CMOS Image Sensor from Aptina 4cb7a01acSMauro Carvalho Chehab * 5cb7a01acSMauro Carvalho Chehab * Copyright (C) 2011, Laurent Pinchart <laurent.pinchart@ideasonboard.com> 6cb7a01acSMauro Carvalho Chehab * Copyright (C) 2011, Javier Martin <javier.martin@vista-silicon.com> 7cb7a01acSMauro Carvalho Chehab * Copyright (C) 2011, Guennadi Liakhovetski <g.liakhovetski@gmx.de> 8cb7a01acSMauro Carvalho Chehab * 9cb7a01acSMauro Carvalho Chehab * Based on the MT9V032 driver and Bastian Hecht's code. 10cb7a01acSMauro Carvalho Chehab */ 11cb7a01acSMauro Carvalho Chehab 12d6749258SLaurent Pinchart #include <linux/clk.h> 13cb7a01acSMauro Carvalho Chehab #include <linux/delay.h> 14cb7a01acSMauro Carvalho Chehab #include <linux/device.h> 157c3be9f8SLaurent Pinchart #include <linux/gpio/consumer.h> 16cb7a01acSMauro Carvalho Chehab #include <linux/i2c.h> 17cb7a01acSMauro Carvalho Chehab #include <linux/log2.h> 188d4da37cSLad, Prabhakar #include <linux/module.h> 1964695905SSachin Kamat #include <linux/of.h> 20fd9fdb78SPhilipp Zabel #include <linux/of_graph.h> 21cb7a01acSMauro Carvalho Chehab #include <linux/pm.h> 2297f21276SLaurent Pinchart #include <linux/regulator/consumer.h> 23cb7a01acSMauro Carvalho Chehab #include <linux/slab.h> 24cb7a01acSMauro Carvalho Chehab #include <linux/videodev2.h> 25cb7a01acSMauro Carvalho Chehab 26b5dcee22SMauro Carvalho Chehab #include <media/i2c/mt9p031.h> 279012d088SLad, Prabhakar #include <media/v4l2-async.h> 28cb7a01acSMauro Carvalho Chehab #include <media/v4l2-ctrls.h> 29cb7a01acSMauro Carvalho Chehab #include <media/v4l2-device.h> 30ae47ee5fSChristian Hemp #include <media/v4l2-fwnode.h> 31cb7a01acSMauro Carvalho Chehab #include <media/v4l2-subdev.h> 32cb7a01acSMauro Carvalho Chehab 33cb7a01acSMauro Carvalho Chehab #include "aptina-pll.h" 34cb7a01acSMauro Carvalho Chehab 35cb7a01acSMauro Carvalho Chehab #define MT9P031_PIXEL_ARRAY_WIDTH 2752 36cb7a01acSMauro Carvalho Chehab #define MT9P031_PIXEL_ARRAY_HEIGHT 2004 37cb7a01acSMauro Carvalho Chehab 38cb7a01acSMauro Carvalho Chehab #define MT9P031_CHIP_VERSION 0x00 39cb7a01acSMauro Carvalho Chehab #define MT9P031_CHIP_VERSION_VALUE 0x1801 40cb7a01acSMauro Carvalho Chehab #define MT9P031_ROW_START 0x01 41cb7a01acSMauro Carvalho Chehab #define MT9P031_ROW_START_MIN 0 42cb7a01acSMauro Carvalho Chehab #define MT9P031_ROW_START_MAX 2004 43cb7a01acSMauro Carvalho Chehab #define MT9P031_ROW_START_DEF 54 44cb7a01acSMauro Carvalho Chehab #define MT9P031_COLUMN_START 0x02 45cb7a01acSMauro Carvalho Chehab #define MT9P031_COLUMN_START_MIN 0 46cb7a01acSMauro Carvalho Chehab #define MT9P031_COLUMN_START_MAX 2750 47cb7a01acSMauro Carvalho Chehab #define MT9P031_COLUMN_START_DEF 16 48cb7a01acSMauro Carvalho Chehab #define MT9P031_WINDOW_HEIGHT 0x03 49cb7a01acSMauro Carvalho Chehab #define MT9P031_WINDOW_HEIGHT_MIN 2 50cb7a01acSMauro Carvalho Chehab #define MT9P031_WINDOW_HEIGHT_MAX 2006 51cb7a01acSMauro Carvalho Chehab #define MT9P031_WINDOW_HEIGHT_DEF 1944 52cb7a01acSMauro Carvalho Chehab #define MT9P031_WINDOW_WIDTH 0x04 53cb7a01acSMauro Carvalho Chehab #define MT9P031_WINDOW_WIDTH_MIN 2 54cb7a01acSMauro Carvalho Chehab #define MT9P031_WINDOW_WIDTH_MAX 2752 55cb7a01acSMauro Carvalho Chehab #define MT9P031_WINDOW_WIDTH_DEF 2592 56cb7a01acSMauro Carvalho Chehab #define MT9P031_HORIZONTAL_BLANK 0x05 57cb7a01acSMauro Carvalho Chehab #define MT9P031_HORIZONTAL_BLANK_MIN 0 58cb7a01acSMauro Carvalho Chehab #define MT9P031_HORIZONTAL_BLANK_MAX 4095 59cb7a01acSMauro Carvalho Chehab #define MT9P031_VERTICAL_BLANK 0x06 605266c98bSLaurent Pinchart #define MT9P031_VERTICAL_BLANK_MIN 1 615266c98bSLaurent Pinchart #define MT9P031_VERTICAL_BLANK_MAX 4096 625266c98bSLaurent Pinchart #define MT9P031_VERTICAL_BLANK_DEF 26 63cb7a01acSMauro Carvalho Chehab #define MT9P031_OUTPUT_CONTROL 0x07 64cb7a01acSMauro Carvalho Chehab #define MT9P031_OUTPUT_CONTROL_CEN 2 65cb7a01acSMauro Carvalho Chehab #define MT9P031_OUTPUT_CONTROL_SYN 1 66cb7a01acSMauro Carvalho Chehab #define MT9P031_OUTPUT_CONTROL_DEF 0x1f82 67cb7a01acSMauro Carvalho Chehab #define MT9P031_SHUTTER_WIDTH_UPPER 0x08 68cb7a01acSMauro Carvalho Chehab #define MT9P031_SHUTTER_WIDTH_LOWER 0x09 69cb7a01acSMauro Carvalho Chehab #define MT9P031_SHUTTER_WIDTH_MIN 1 70cb7a01acSMauro Carvalho Chehab #define MT9P031_SHUTTER_WIDTH_MAX 1048575 71cb7a01acSMauro Carvalho Chehab #define MT9P031_SHUTTER_WIDTH_DEF 1943 72cb7a01acSMauro Carvalho Chehab #define MT9P031_PLL_CONTROL 0x10 73cb7a01acSMauro Carvalho Chehab #define MT9P031_PLL_CONTROL_PWROFF 0x0050 74cb7a01acSMauro Carvalho Chehab #define MT9P031_PLL_CONTROL_PWRON 0x0051 75cb7a01acSMauro Carvalho Chehab #define MT9P031_PLL_CONTROL_USEPLL 0x0052 76cb7a01acSMauro Carvalho Chehab #define MT9P031_PLL_CONFIG_1 0x11 77cb7a01acSMauro Carvalho Chehab #define MT9P031_PLL_CONFIG_2 0x12 78cb7a01acSMauro Carvalho Chehab #define MT9P031_PIXEL_CLOCK_CONTROL 0x0a 790a0e78d1SStefan Riedmueller #define MT9P031_PIXEL_CLOCK_INVERT BIT(15) 80a970449eSLaurent Pinchart #define MT9P031_PIXEL_CLOCK_SHIFT(n) ((n) << 8) 81a970449eSLaurent Pinchart #define MT9P031_PIXEL_CLOCK_DIVIDE(n) ((n) << 0) 820961ba6dSDirk Bender #define MT9P031_RESTART 0x0b 830a0e78d1SStefan Riedmueller #define MT9P031_FRAME_PAUSE_RESTART BIT(1) 840a0e78d1SStefan Riedmueller #define MT9P031_FRAME_RESTART BIT(0) 85cb7a01acSMauro Carvalho Chehab #define MT9P031_SHUTTER_DELAY 0x0c 86cb7a01acSMauro Carvalho Chehab #define MT9P031_RST 0x0d 870a0e78d1SStefan Riedmueller #define MT9P031_RST_ENABLE BIT(0) 88cb7a01acSMauro Carvalho Chehab #define MT9P031_READ_MODE_1 0x1e 89cb7a01acSMauro Carvalho Chehab #define MT9P031_READ_MODE_2 0x20 900a0e78d1SStefan Riedmueller #define MT9P031_READ_MODE_2_ROW_MIR BIT(15) 910a0e78d1SStefan Riedmueller #define MT9P031_READ_MODE_2_COL_MIR BIT(14) 920a0e78d1SStefan Riedmueller #define MT9P031_READ_MODE_2_ROW_BLC BIT(6) 93cb7a01acSMauro Carvalho Chehab #define MT9P031_ROW_ADDRESS_MODE 0x22 94cb7a01acSMauro Carvalho Chehab #define MT9P031_COLUMN_ADDRESS_MODE 0x23 95cb7a01acSMauro Carvalho Chehab #define MT9P031_GLOBAL_GAIN 0x35 96cb7a01acSMauro Carvalho Chehab #define MT9P031_GLOBAL_GAIN_MIN 8 97cb7a01acSMauro Carvalho Chehab #define MT9P031_GLOBAL_GAIN_MAX 1024 98cb7a01acSMauro Carvalho Chehab #define MT9P031_GLOBAL_GAIN_DEF 8 990a0e78d1SStefan Riedmueller #define MT9P031_GLOBAL_GAIN_MULT BIT(6) 100cb7a01acSMauro Carvalho Chehab #define MT9P031_ROW_BLACK_TARGET 0x49 101cb7a01acSMauro Carvalho Chehab #define MT9P031_ROW_BLACK_DEF_OFFSET 0x4b 102cb7a01acSMauro Carvalho Chehab #define MT9P031_GREEN1_OFFSET 0x60 103cb7a01acSMauro Carvalho Chehab #define MT9P031_GREEN2_OFFSET 0x61 104cb7a01acSMauro Carvalho Chehab #define MT9P031_BLACK_LEVEL_CALIBRATION 0x62 1050a0e78d1SStefan Riedmueller #define MT9P031_BLC_MANUAL_BLC BIT(0) 106cb7a01acSMauro Carvalho Chehab #define MT9P031_RED_OFFSET 0x63 107cb7a01acSMauro Carvalho Chehab #define MT9P031_BLUE_OFFSET 0x64 108cb7a01acSMauro Carvalho Chehab #define MT9P031_TEST_PATTERN 0xa0 109cb7a01acSMauro Carvalho Chehab #define MT9P031_TEST_PATTERN_SHIFT 3 1100a0e78d1SStefan Riedmueller #define MT9P031_TEST_PATTERN_ENABLE BIT(0) 111cb7a01acSMauro Carvalho Chehab #define MT9P031_TEST_PATTERN_GREEN 0xa1 112cb7a01acSMauro Carvalho Chehab #define MT9P031_TEST_PATTERN_RED 0xa2 113cb7a01acSMauro Carvalho Chehab #define MT9P031_TEST_PATTERN_BLUE 0xa3 114cb7a01acSMauro Carvalho Chehab 115cb7a01acSMauro Carvalho Chehab enum mt9p031_model { 116cb7a01acSMauro Carvalho Chehab MT9P031_MODEL_COLOR, 117cb7a01acSMauro Carvalho Chehab MT9P031_MODEL_MONOCHROME, 118cb7a01acSMauro Carvalho Chehab }; 119cb7a01acSMauro Carvalho Chehab 120cb7a01acSMauro Carvalho Chehab struct mt9p031 { 121cb7a01acSMauro Carvalho Chehab struct v4l2_subdev subdev; 122cb7a01acSMauro Carvalho Chehab struct media_pad pad; 123cb7a01acSMauro Carvalho Chehab struct v4l2_rect crop; /* Sensor window */ 124cb7a01acSMauro Carvalho Chehab struct v4l2_mbus_framefmt format; 125cb7a01acSMauro Carvalho Chehab struct mt9p031_platform_data *pdata; 126cb7a01acSMauro Carvalho Chehab struct mutex power_lock; /* lock to protect power_count */ 127cb7a01acSMauro Carvalho Chehab int power_count; 128cb7a01acSMauro Carvalho Chehab 129d6749258SLaurent Pinchart struct clk *clk; 1307997196cSLaurent Pinchart struct regulator_bulk_data regulators[3]; 13197f21276SLaurent Pinchart 132cb7a01acSMauro Carvalho Chehab enum mt9p031_model model; 133cb7a01acSMauro Carvalho Chehab struct aptina_pll pll; 134a970449eSLaurent Pinchart unsigned int clk_div; 135a970449eSLaurent Pinchart bool use_pll; 1367c3be9f8SLaurent Pinchart struct gpio_desc *reset; 137cb7a01acSMauro Carvalho Chehab 138cb7a01acSMauro Carvalho Chehab struct v4l2_ctrl_handler ctrls; 139cb7a01acSMauro Carvalho Chehab struct v4l2_ctrl *blc_auto; 140cb7a01acSMauro Carvalho Chehab struct v4l2_ctrl *blc_offset; 141cb7a01acSMauro Carvalho Chehab 142cb7a01acSMauro Carvalho Chehab /* Registers cache */ 143cb7a01acSMauro Carvalho Chehab u16 output_control; 144cb7a01acSMauro Carvalho Chehab u16 mode2; 145cb7a01acSMauro Carvalho Chehab }; 146cb7a01acSMauro Carvalho Chehab 147cb7a01acSMauro Carvalho Chehab static struct mt9p031 *to_mt9p031(struct v4l2_subdev *sd) 148cb7a01acSMauro Carvalho Chehab { 149cb7a01acSMauro Carvalho Chehab return container_of(sd, struct mt9p031, subdev); 150cb7a01acSMauro Carvalho Chehab } 151cb7a01acSMauro Carvalho Chehab 152cb7a01acSMauro Carvalho Chehab static int mt9p031_read(struct i2c_client *client, u8 reg) 153cb7a01acSMauro Carvalho Chehab { 154cb7a01acSMauro Carvalho Chehab return i2c_smbus_read_word_swapped(client, reg); 155cb7a01acSMauro Carvalho Chehab } 156cb7a01acSMauro Carvalho Chehab 157cb7a01acSMauro Carvalho Chehab static int mt9p031_write(struct i2c_client *client, u8 reg, u16 data) 158cb7a01acSMauro Carvalho Chehab { 159cb7a01acSMauro Carvalho Chehab return i2c_smbus_write_word_swapped(client, reg, data); 160cb7a01acSMauro Carvalho Chehab } 161cb7a01acSMauro Carvalho Chehab 162cb7a01acSMauro Carvalho Chehab static int mt9p031_set_output_control(struct mt9p031 *mt9p031, u16 clear, 163cb7a01acSMauro Carvalho Chehab u16 set) 164cb7a01acSMauro Carvalho Chehab { 165cb7a01acSMauro Carvalho Chehab struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); 166cb7a01acSMauro Carvalho Chehab u16 value = (mt9p031->output_control & ~clear) | set; 167cb7a01acSMauro Carvalho Chehab int ret; 168cb7a01acSMauro Carvalho Chehab 169cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_OUTPUT_CONTROL, value); 170cb7a01acSMauro Carvalho Chehab if (ret < 0) 171cb7a01acSMauro Carvalho Chehab return ret; 172cb7a01acSMauro Carvalho Chehab 173cb7a01acSMauro Carvalho Chehab mt9p031->output_control = value; 174cb7a01acSMauro Carvalho Chehab return 0; 175cb7a01acSMauro Carvalho Chehab } 176cb7a01acSMauro Carvalho Chehab 177cb7a01acSMauro Carvalho Chehab static int mt9p031_set_mode2(struct mt9p031 *mt9p031, u16 clear, u16 set) 178cb7a01acSMauro Carvalho Chehab { 179cb7a01acSMauro Carvalho Chehab struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); 180cb7a01acSMauro Carvalho Chehab u16 value = (mt9p031->mode2 & ~clear) | set; 181cb7a01acSMauro Carvalho Chehab int ret; 182cb7a01acSMauro Carvalho Chehab 183cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_READ_MODE_2, value); 184cb7a01acSMauro Carvalho Chehab if (ret < 0) 185cb7a01acSMauro Carvalho Chehab return ret; 186cb7a01acSMauro Carvalho Chehab 187cb7a01acSMauro Carvalho Chehab mt9p031->mode2 = value; 188cb7a01acSMauro Carvalho Chehab return 0; 189cb7a01acSMauro Carvalho Chehab } 190cb7a01acSMauro Carvalho Chehab 191cb7a01acSMauro Carvalho Chehab static int mt9p031_reset(struct mt9p031 *mt9p031) 192cb7a01acSMauro Carvalho Chehab { 193cb7a01acSMauro Carvalho Chehab struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); 194cb7a01acSMauro Carvalho Chehab int ret; 195cb7a01acSMauro Carvalho Chehab 196cb7a01acSMauro Carvalho Chehab /* Disable chip output, synchronous option update */ 197cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_RST, MT9P031_RST_ENABLE); 198cb7a01acSMauro Carvalho Chehab if (ret < 0) 199cb7a01acSMauro Carvalho Chehab return ret; 2000a0e78d1SStefan Riedmueller ret = mt9p031_write(client, MT9P031_RST, 0); 201cb7a01acSMauro Carvalho Chehab if (ret < 0) 202cb7a01acSMauro Carvalho Chehab return ret; 203cb7a01acSMauro Carvalho Chehab 204a970449eSLaurent Pinchart ret = mt9p031_write(client, MT9P031_PIXEL_CLOCK_CONTROL, 205a970449eSLaurent Pinchart MT9P031_PIXEL_CLOCK_DIVIDE(mt9p031->clk_div)); 206a970449eSLaurent Pinchart if (ret < 0) 207a970449eSLaurent Pinchart return ret; 208a970449eSLaurent Pinchart 209cb7a01acSMauro Carvalho Chehab return mt9p031_set_output_control(mt9p031, MT9P031_OUTPUT_CONTROL_CEN, 210cb7a01acSMauro Carvalho Chehab 0); 211cb7a01acSMauro Carvalho Chehab } 212cb7a01acSMauro Carvalho Chehab 213d6749258SLaurent Pinchart static int mt9p031_clk_setup(struct mt9p031 *mt9p031) 214cb7a01acSMauro Carvalho Chehab { 215cb7a01acSMauro Carvalho Chehab static const struct aptina_pll_limits limits = { 216cb7a01acSMauro Carvalho Chehab .ext_clock_min = 6000000, 217cb7a01acSMauro Carvalho Chehab .ext_clock_max = 27000000, 218cb7a01acSMauro Carvalho Chehab .int_clock_min = 2000000, 219cb7a01acSMauro Carvalho Chehab .int_clock_max = 13500000, 220cb7a01acSMauro Carvalho Chehab .out_clock_min = 180000000, 221cb7a01acSMauro Carvalho Chehab .out_clock_max = 360000000, 222cb7a01acSMauro Carvalho Chehab .pix_clock_max = 96000000, 223cb7a01acSMauro Carvalho Chehab .n_min = 1, 224cb7a01acSMauro Carvalho Chehab .n_max = 64, 225cb7a01acSMauro Carvalho Chehab .m_min = 16, 226cb7a01acSMauro Carvalho Chehab .m_max = 255, 227cb7a01acSMauro Carvalho Chehab .p1_min = 1, 228cb7a01acSMauro Carvalho Chehab .p1_max = 128, 229cb7a01acSMauro Carvalho Chehab }; 230cb7a01acSMauro Carvalho Chehab 231cb7a01acSMauro Carvalho Chehab struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); 232cb7a01acSMauro Carvalho Chehab struct mt9p031_platform_data *pdata = mt9p031->pdata; 233b9c18096SEnrico Scholz unsigned long ext_freq; 234ee2d16d7SLad, Prabhakar int ret; 235cb7a01acSMauro Carvalho Chehab 236d6749258SLaurent Pinchart mt9p031->clk = devm_clk_get(&client->dev, NULL); 237d6749258SLaurent Pinchart if (IS_ERR(mt9p031->clk)) 238d6749258SLaurent Pinchart return PTR_ERR(mt9p031->clk); 239d6749258SLaurent Pinchart 240ee2d16d7SLad, Prabhakar ret = clk_set_rate(mt9p031->clk, pdata->ext_freq); 241ee2d16d7SLad, Prabhakar if (ret < 0) 242ee2d16d7SLad, Prabhakar return ret; 243d6749258SLaurent Pinchart 244b9c18096SEnrico Scholz ext_freq = clk_get_rate(mt9p031->clk); 245b9c18096SEnrico Scholz 246a970449eSLaurent Pinchart /* If the external clock frequency is out of bounds for the PLL use the 247a970449eSLaurent Pinchart * pixel clock divider only and disable the PLL. 248a970449eSLaurent Pinchart */ 249b9c18096SEnrico Scholz if (ext_freq > limits.ext_clock_max) { 250a970449eSLaurent Pinchart unsigned int div; 251a970449eSLaurent Pinchart 252b9c18096SEnrico Scholz div = DIV_ROUND_UP(ext_freq, pdata->target_freq); 253a970449eSLaurent Pinchart div = roundup_pow_of_two(div) / 2; 254a970449eSLaurent Pinchart 255198b47ddSEnrico Scholz mt9p031->clk_div = min_t(unsigned int, div, 64); 256a970449eSLaurent Pinchart mt9p031->use_pll = false; 257a970449eSLaurent Pinchart 258a970449eSLaurent Pinchart return 0; 259a970449eSLaurent Pinchart } 260cb7a01acSMauro Carvalho Chehab 261b9c18096SEnrico Scholz mt9p031->pll.ext_clock = ext_freq; 262cb7a01acSMauro Carvalho Chehab mt9p031->pll.pix_clock = pdata->target_freq; 263a970449eSLaurent Pinchart mt9p031->use_pll = true; 264cb7a01acSMauro Carvalho Chehab 265cb7a01acSMauro Carvalho Chehab return aptina_pll_calculate(&client->dev, &limits, &mt9p031->pll); 266cb7a01acSMauro Carvalho Chehab } 267cb7a01acSMauro Carvalho Chehab 268cb7a01acSMauro Carvalho Chehab static int mt9p031_pll_enable(struct mt9p031 *mt9p031) 269cb7a01acSMauro Carvalho Chehab { 270cb7a01acSMauro Carvalho Chehab struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); 271cb7a01acSMauro Carvalho Chehab int ret; 272cb7a01acSMauro Carvalho Chehab 273a970449eSLaurent Pinchart if (!mt9p031->use_pll) 274a970449eSLaurent Pinchart return 0; 275a970449eSLaurent Pinchart 276cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_PLL_CONTROL, 277cb7a01acSMauro Carvalho Chehab MT9P031_PLL_CONTROL_PWRON); 278cb7a01acSMauro Carvalho Chehab if (ret < 0) 279cb7a01acSMauro Carvalho Chehab return ret; 280cb7a01acSMauro Carvalho Chehab 281cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_PLL_CONFIG_1, 282cb7a01acSMauro Carvalho Chehab (mt9p031->pll.m << 8) | (mt9p031->pll.n - 1)); 283cb7a01acSMauro Carvalho Chehab if (ret < 0) 284cb7a01acSMauro Carvalho Chehab return ret; 285cb7a01acSMauro Carvalho Chehab 286cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_PLL_CONFIG_2, mt9p031->pll.p1 - 1); 287cb7a01acSMauro Carvalho Chehab if (ret < 0) 288cb7a01acSMauro Carvalho Chehab return ret; 289cb7a01acSMauro Carvalho Chehab 290cb7a01acSMauro Carvalho Chehab usleep_range(1000, 2000); 291cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_PLL_CONTROL, 292cb7a01acSMauro Carvalho Chehab MT9P031_PLL_CONTROL_PWRON | 293cb7a01acSMauro Carvalho Chehab MT9P031_PLL_CONTROL_USEPLL); 294cb7a01acSMauro Carvalho Chehab return ret; 295cb7a01acSMauro Carvalho Chehab } 296cb7a01acSMauro Carvalho Chehab 297cb7a01acSMauro Carvalho Chehab static inline int mt9p031_pll_disable(struct mt9p031 *mt9p031) 298cb7a01acSMauro Carvalho Chehab { 299cb7a01acSMauro Carvalho Chehab struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); 300cb7a01acSMauro Carvalho Chehab 301a970449eSLaurent Pinchart if (!mt9p031->use_pll) 302a970449eSLaurent Pinchart return 0; 303a970449eSLaurent Pinchart 304cb7a01acSMauro Carvalho Chehab return mt9p031_write(client, MT9P031_PLL_CONTROL, 305cb7a01acSMauro Carvalho Chehab MT9P031_PLL_CONTROL_PWROFF); 306cb7a01acSMauro Carvalho Chehab } 307cb7a01acSMauro Carvalho Chehab 308cb7a01acSMauro Carvalho Chehab static int mt9p031_power_on(struct mt9p031 *mt9p031) 309cb7a01acSMauro Carvalho Chehab { 3107997196cSLaurent Pinchart int ret; 3117997196cSLaurent Pinchart 3127c3be9f8SLaurent Pinchart /* Ensure RESET_BAR is active */ 3137c3be9f8SLaurent Pinchart if (mt9p031->reset) { 3147c3be9f8SLaurent Pinchart gpiod_set_value(mt9p031->reset, 1); 315cb7a01acSMauro Carvalho Chehab usleep_range(1000, 2000); 316cb7a01acSMauro Carvalho Chehab } 317cb7a01acSMauro Carvalho Chehab 31897f21276SLaurent Pinchart /* Bring up the supplies */ 3197997196cSLaurent Pinchart ret = regulator_bulk_enable(ARRAY_SIZE(mt9p031->regulators), 3207997196cSLaurent Pinchart mt9p031->regulators); 3217997196cSLaurent Pinchart if (ret < 0) 3227997196cSLaurent Pinchart return ret; 32397f21276SLaurent Pinchart 324e8e45593SLaurent Pinchart /* Enable clock */ 325ee2d16d7SLad, Prabhakar if (mt9p031->clk) { 326ee2d16d7SLad, Prabhakar ret = clk_prepare_enable(mt9p031->clk); 327ee2d16d7SLad, Prabhakar if (ret) { 328ee2d16d7SLad, Prabhakar regulator_bulk_disable(ARRAY_SIZE(mt9p031->regulators), 329ee2d16d7SLad, Prabhakar mt9p031->regulators); 330ee2d16d7SLad, Prabhakar return ret; 331ee2d16d7SLad, Prabhakar } 332ee2d16d7SLad, Prabhakar } 333cb7a01acSMauro Carvalho Chehab 334cb7a01acSMauro Carvalho Chehab /* Now RESET_BAR must be high */ 3357c3be9f8SLaurent Pinchart if (mt9p031->reset) { 3367c3be9f8SLaurent Pinchart gpiod_set_value(mt9p031->reset, 0); 337cb7a01acSMauro Carvalho Chehab usleep_range(1000, 2000); 338cb7a01acSMauro Carvalho Chehab } 339cb7a01acSMauro Carvalho Chehab 340cb7a01acSMauro Carvalho Chehab return 0; 341cb7a01acSMauro Carvalho Chehab } 342cb7a01acSMauro Carvalho Chehab 343cb7a01acSMauro Carvalho Chehab static void mt9p031_power_off(struct mt9p031 *mt9p031) 344cb7a01acSMauro Carvalho Chehab { 3457c3be9f8SLaurent Pinchart if (mt9p031->reset) { 3467c3be9f8SLaurent Pinchart gpiod_set_value(mt9p031->reset, 1); 347cb7a01acSMauro Carvalho Chehab usleep_range(1000, 2000); 348cb7a01acSMauro Carvalho Chehab } 349cb7a01acSMauro Carvalho Chehab 3507997196cSLaurent Pinchart regulator_bulk_disable(ARRAY_SIZE(mt9p031->regulators), 3517997196cSLaurent Pinchart mt9p031->regulators); 35297f21276SLaurent Pinchart 353d6749258SLaurent Pinchart clk_disable_unprepare(mt9p031->clk); 354cb7a01acSMauro Carvalho Chehab } 355cb7a01acSMauro Carvalho Chehab 356cb7a01acSMauro Carvalho Chehab static int __mt9p031_set_power(struct mt9p031 *mt9p031, bool on) 357cb7a01acSMauro Carvalho Chehab { 358cb7a01acSMauro Carvalho Chehab struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); 359cb7a01acSMauro Carvalho Chehab int ret; 360cb7a01acSMauro Carvalho Chehab 361cb7a01acSMauro Carvalho Chehab if (!on) { 362cb7a01acSMauro Carvalho Chehab mt9p031_power_off(mt9p031); 363cb7a01acSMauro Carvalho Chehab return 0; 364cb7a01acSMauro Carvalho Chehab } 365cb7a01acSMauro Carvalho Chehab 366cb7a01acSMauro Carvalho Chehab ret = mt9p031_power_on(mt9p031); 367cb7a01acSMauro Carvalho Chehab if (ret < 0) 368cb7a01acSMauro Carvalho Chehab return ret; 369cb7a01acSMauro Carvalho Chehab 370cb7a01acSMauro Carvalho Chehab ret = mt9p031_reset(mt9p031); 371cb7a01acSMauro Carvalho Chehab if (ret < 0) { 372cb7a01acSMauro Carvalho Chehab dev_err(&client->dev, "Failed to reset the camera\n"); 373cb7a01acSMauro Carvalho Chehab return ret; 374cb7a01acSMauro Carvalho Chehab } 375cb7a01acSMauro Carvalho Chehab 376ae47ee5fSChristian Hemp /* Configure the pixel clock polarity */ 377ae47ee5fSChristian Hemp if (mt9p031->pdata && mt9p031->pdata->pixclk_pol) { 378ae47ee5fSChristian Hemp ret = mt9p031_write(client, MT9P031_PIXEL_CLOCK_CONTROL, 379ae47ee5fSChristian Hemp MT9P031_PIXEL_CLOCK_INVERT); 380ae47ee5fSChristian Hemp if (ret < 0) 381ae47ee5fSChristian Hemp return ret; 382ae47ee5fSChristian Hemp } 383ae47ee5fSChristian Hemp 384cb7a01acSMauro Carvalho Chehab return v4l2_ctrl_handler_setup(&mt9p031->ctrls); 385cb7a01acSMauro Carvalho Chehab } 386cb7a01acSMauro Carvalho Chehab 387cb7a01acSMauro Carvalho Chehab /* ----------------------------------------------------------------------------- 388cb7a01acSMauro Carvalho Chehab * V4L2 subdev video operations 389cb7a01acSMauro Carvalho Chehab */ 390cb7a01acSMauro Carvalho Chehab 391cb7a01acSMauro Carvalho Chehab static int mt9p031_set_params(struct mt9p031 *mt9p031) 392cb7a01acSMauro Carvalho Chehab { 393cb7a01acSMauro Carvalho Chehab struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); 394cb7a01acSMauro Carvalho Chehab struct v4l2_mbus_framefmt *format = &mt9p031->format; 395cb7a01acSMauro Carvalho Chehab const struct v4l2_rect *crop = &mt9p031->crop; 396cb7a01acSMauro Carvalho Chehab unsigned int hblank; 397cb7a01acSMauro Carvalho Chehab unsigned int vblank; 398cb7a01acSMauro Carvalho Chehab unsigned int xskip; 399cb7a01acSMauro Carvalho Chehab unsigned int yskip; 400cb7a01acSMauro Carvalho Chehab unsigned int xbin; 401cb7a01acSMauro Carvalho Chehab unsigned int ybin; 402cb7a01acSMauro Carvalho Chehab int ret; 403cb7a01acSMauro Carvalho Chehab 404cb7a01acSMauro Carvalho Chehab /* Windows position and size. 405cb7a01acSMauro Carvalho Chehab * 406cb7a01acSMauro Carvalho Chehab * TODO: Make sure the start coordinates and window size match the 407cb7a01acSMauro Carvalho Chehab * skipping, binning and mirroring (see description of registers 2 and 4 408cb7a01acSMauro Carvalho Chehab * in table 13, and Binning section on page 41). 409cb7a01acSMauro Carvalho Chehab */ 410cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_COLUMN_START, crop->left); 411cb7a01acSMauro Carvalho Chehab if (ret < 0) 412cb7a01acSMauro Carvalho Chehab return ret; 413cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_ROW_START, crop->top); 414cb7a01acSMauro Carvalho Chehab if (ret < 0) 415cb7a01acSMauro Carvalho Chehab return ret; 416cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_WINDOW_WIDTH, crop->width - 1); 417cb7a01acSMauro Carvalho Chehab if (ret < 0) 418cb7a01acSMauro Carvalho Chehab return ret; 419cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_WINDOW_HEIGHT, crop->height - 1); 420cb7a01acSMauro Carvalho Chehab if (ret < 0) 421cb7a01acSMauro Carvalho Chehab return ret; 422cb7a01acSMauro Carvalho Chehab 423cb7a01acSMauro Carvalho Chehab /* Row and column binning and skipping. Use the maximum binning value 424cb7a01acSMauro Carvalho Chehab * compatible with the skipping settings. 425cb7a01acSMauro Carvalho Chehab */ 426cb7a01acSMauro Carvalho Chehab xskip = DIV_ROUND_CLOSEST(crop->width, format->width); 427cb7a01acSMauro Carvalho Chehab yskip = DIV_ROUND_CLOSEST(crop->height, format->height); 428cb7a01acSMauro Carvalho Chehab xbin = 1 << (ffs(xskip) - 1); 429cb7a01acSMauro Carvalho Chehab ybin = 1 << (ffs(yskip) - 1); 430cb7a01acSMauro Carvalho Chehab 431cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_COLUMN_ADDRESS_MODE, 432cb7a01acSMauro Carvalho Chehab ((xbin - 1) << 4) | (xskip - 1)); 433cb7a01acSMauro Carvalho Chehab if (ret < 0) 434cb7a01acSMauro Carvalho Chehab return ret; 435cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_ROW_ADDRESS_MODE, 436cb7a01acSMauro Carvalho Chehab ((ybin - 1) << 4) | (yskip - 1)); 437cb7a01acSMauro Carvalho Chehab if (ret < 0) 438cb7a01acSMauro Carvalho Chehab return ret; 439cb7a01acSMauro Carvalho Chehab 440cb7a01acSMauro Carvalho Chehab /* Blanking - use minimum value for horizontal blanking and default 441cb7a01acSMauro Carvalho Chehab * value for vertical blanking. 442cb7a01acSMauro Carvalho Chehab */ 4435266c98bSLaurent Pinchart hblank = 346 * ybin + 64 + (80 >> min_t(unsigned int, xbin, 3)); 444cb7a01acSMauro Carvalho Chehab vblank = MT9P031_VERTICAL_BLANK_DEF; 445cb7a01acSMauro Carvalho Chehab 4465266c98bSLaurent Pinchart ret = mt9p031_write(client, MT9P031_HORIZONTAL_BLANK, hblank - 1); 447cb7a01acSMauro Carvalho Chehab if (ret < 0) 448cb7a01acSMauro Carvalho Chehab return ret; 4495266c98bSLaurent Pinchart ret = mt9p031_write(client, MT9P031_VERTICAL_BLANK, vblank - 1); 450cb7a01acSMauro Carvalho Chehab if (ret < 0) 451cb7a01acSMauro Carvalho Chehab return ret; 452cb7a01acSMauro Carvalho Chehab 453cb7a01acSMauro Carvalho Chehab return ret; 454cb7a01acSMauro Carvalho Chehab } 455cb7a01acSMauro Carvalho Chehab 456cb7a01acSMauro Carvalho Chehab static int mt9p031_s_stream(struct v4l2_subdev *subdev, int enable) 457cb7a01acSMauro Carvalho Chehab { 458cb7a01acSMauro Carvalho Chehab struct mt9p031 *mt9p031 = to_mt9p031(subdev); 4590961ba6dSDirk Bender struct i2c_client *client = v4l2_get_subdevdata(subdev); 4600961ba6dSDirk Bender int val; 461cb7a01acSMauro Carvalho Chehab int ret; 462cb7a01acSMauro Carvalho Chehab 463cb7a01acSMauro Carvalho Chehab if (!enable) { 4640961ba6dSDirk Bender /* enable pause restart */ 4650961ba6dSDirk Bender val = MT9P031_FRAME_PAUSE_RESTART; 4660961ba6dSDirk Bender ret = mt9p031_write(client, MT9P031_RESTART, val); 4670961ba6dSDirk Bender if (ret < 0) 4680961ba6dSDirk Bender return ret; 4690961ba6dSDirk Bender 4700961ba6dSDirk Bender /* enable restart + keep pause restart set */ 4710961ba6dSDirk Bender val |= MT9P031_FRAME_RESTART; 4720961ba6dSDirk Bender ret = mt9p031_write(client, MT9P031_RESTART, val); 4730961ba6dSDirk Bender if (ret < 0) 4740961ba6dSDirk Bender return ret; 4750961ba6dSDirk Bender 476cb7a01acSMauro Carvalho Chehab /* Stop sensor readout */ 477cb7a01acSMauro Carvalho Chehab ret = mt9p031_set_output_control(mt9p031, 478cb7a01acSMauro Carvalho Chehab MT9P031_OUTPUT_CONTROL_CEN, 0); 479cb7a01acSMauro Carvalho Chehab if (ret < 0) 480cb7a01acSMauro Carvalho Chehab return ret; 481cb7a01acSMauro Carvalho Chehab 482cb7a01acSMauro Carvalho Chehab return mt9p031_pll_disable(mt9p031); 483cb7a01acSMauro Carvalho Chehab } 484cb7a01acSMauro Carvalho Chehab 485cb7a01acSMauro Carvalho Chehab ret = mt9p031_set_params(mt9p031); 486cb7a01acSMauro Carvalho Chehab if (ret < 0) 487cb7a01acSMauro Carvalho Chehab return ret; 488cb7a01acSMauro Carvalho Chehab 489cb7a01acSMauro Carvalho Chehab /* Switch to master "normal" mode */ 490cb7a01acSMauro Carvalho Chehab ret = mt9p031_set_output_control(mt9p031, 0, 491cb7a01acSMauro Carvalho Chehab MT9P031_OUTPUT_CONTROL_CEN); 492cb7a01acSMauro Carvalho Chehab if (ret < 0) 493cb7a01acSMauro Carvalho Chehab return ret; 494cb7a01acSMauro Carvalho Chehab 4950961ba6dSDirk Bender /* 4960961ba6dSDirk Bender * - clear pause restart 4970961ba6dSDirk Bender * - don't clear restart as clearing restart manually can cause 4980961ba6dSDirk Bender * undefined behavior 4990961ba6dSDirk Bender */ 5000961ba6dSDirk Bender val = MT9P031_FRAME_RESTART; 5010961ba6dSDirk Bender ret = mt9p031_write(client, MT9P031_RESTART, val); 5020961ba6dSDirk Bender if (ret < 0) 5030961ba6dSDirk Bender return ret; 5040961ba6dSDirk Bender 505cb7a01acSMauro Carvalho Chehab return mt9p031_pll_enable(mt9p031); 506cb7a01acSMauro Carvalho Chehab } 507cb7a01acSMauro Carvalho Chehab 508cb7a01acSMauro Carvalho Chehab static int mt9p031_enum_mbus_code(struct v4l2_subdev *subdev, 5090d346d2aSTomi Valkeinen struct v4l2_subdev_state *sd_state, 510cb7a01acSMauro Carvalho Chehab struct v4l2_subdev_mbus_code_enum *code) 511cb7a01acSMauro Carvalho Chehab { 512cb7a01acSMauro Carvalho Chehab struct mt9p031 *mt9p031 = to_mt9p031(subdev); 513cb7a01acSMauro Carvalho Chehab 514cb7a01acSMauro Carvalho Chehab if (code->pad || code->index) 515cb7a01acSMauro Carvalho Chehab return -EINVAL; 516cb7a01acSMauro Carvalho Chehab 517cb7a01acSMauro Carvalho Chehab code->code = mt9p031->format.code; 518cb7a01acSMauro Carvalho Chehab return 0; 519cb7a01acSMauro Carvalho Chehab } 520cb7a01acSMauro Carvalho Chehab 521cb7a01acSMauro Carvalho Chehab static int mt9p031_enum_frame_size(struct v4l2_subdev *subdev, 5220d346d2aSTomi Valkeinen struct v4l2_subdev_state *sd_state, 523cb7a01acSMauro Carvalho Chehab struct v4l2_subdev_frame_size_enum *fse) 524cb7a01acSMauro Carvalho Chehab { 525cb7a01acSMauro Carvalho Chehab struct mt9p031 *mt9p031 = to_mt9p031(subdev); 526cb7a01acSMauro Carvalho Chehab 527cb7a01acSMauro Carvalho Chehab if (fse->index >= 8 || fse->code != mt9p031->format.code) 528cb7a01acSMauro Carvalho Chehab return -EINVAL; 529cb7a01acSMauro Carvalho Chehab 530cb7a01acSMauro Carvalho Chehab fse->min_width = MT9P031_WINDOW_WIDTH_DEF 531cb7a01acSMauro Carvalho Chehab / min_t(unsigned int, 7, fse->index + 1); 532cb7a01acSMauro Carvalho Chehab fse->max_width = fse->min_width; 533cb7a01acSMauro Carvalho Chehab fse->min_height = MT9P031_WINDOW_HEIGHT_DEF / (fse->index + 1); 534cb7a01acSMauro Carvalho Chehab fse->max_height = fse->min_height; 535cb7a01acSMauro Carvalho Chehab 536cb7a01acSMauro Carvalho Chehab return 0; 537cb7a01acSMauro Carvalho Chehab } 538cb7a01acSMauro Carvalho Chehab 539cb7a01acSMauro Carvalho Chehab static struct v4l2_mbus_framefmt * 5400d346d2aSTomi Valkeinen __mt9p031_get_pad_format(struct mt9p031 *mt9p031, 5410d346d2aSTomi Valkeinen struct v4l2_subdev_state *sd_state, 542cb7a01acSMauro Carvalho Chehab unsigned int pad, u32 which) 543cb7a01acSMauro Carvalho Chehab { 544cb7a01acSMauro Carvalho Chehab switch (which) { 545cb7a01acSMauro Carvalho Chehab case V4L2_SUBDEV_FORMAT_TRY: 5460d346d2aSTomi Valkeinen return v4l2_subdev_get_try_format(&mt9p031->subdev, sd_state, 5470d346d2aSTomi Valkeinen pad); 548cb7a01acSMauro Carvalho Chehab case V4L2_SUBDEV_FORMAT_ACTIVE: 549cb7a01acSMauro Carvalho Chehab return &mt9p031->format; 550cb7a01acSMauro Carvalho Chehab default: 551cb7a01acSMauro Carvalho Chehab return NULL; 552cb7a01acSMauro Carvalho Chehab } 553cb7a01acSMauro Carvalho Chehab } 554cb7a01acSMauro Carvalho Chehab 555cb7a01acSMauro Carvalho Chehab static struct v4l2_rect * 5560d346d2aSTomi Valkeinen __mt9p031_get_pad_crop(struct mt9p031 *mt9p031, 5570d346d2aSTomi Valkeinen struct v4l2_subdev_state *sd_state, 558cb7a01acSMauro Carvalho Chehab unsigned int pad, u32 which) 559cb7a01acSMauro Carvalho Chehab { 560cb7a01acSMauro Carvalho Chehab switch (which) { 561cb7a01acSMauro Carvalho Chehab case V4L2_SUBDEV_FORMAT_TRY: 5620d346d2aSTomi Valkeinen return v4l2_subdev_get_try_crop(&mt9p031->subdev, sd_state, 5630d346d2aSTomi Valkeinen pad); 564cb7a01acSMauro Carvalho Chehab case V4L2_SUBDEV_FORMAT_ACTIVE: 565cb7a01acSMauro Carvalho Chehab return &mt9p031->crop; 566cb7a01acSMauro Carvalho Chehab default: 567cb7a01acSMauro Carvalho Chehab return NULL; 568cb7a01acSMauro Carvalho Chehab } 569cb7a01acSMauro Carvalho Chehab } 570cb7a01acSMauro Carvalho Chehab 571cb7a01acSMauro Carvalho Chehab static int mt9p031_get_format(struct v4l2_subdev *subdev, 5720d346d2aSTomi Valkeinen struct v4l2_subdev_state *sd_state, 573cb7a01acSMauro Carvalho Chehab struct v4l2_subdev_format *fmt) 574cb7a01acSMauro Carvalho Chehab { 575cb7a01acSMauro Carvalho Chehab struct mt9p031 *mt9p031 = to_mt9p031(subdev); 576cb7a01acSMauro Carvalho Chehab 5770d346d2aSTomi Valkeinen fmt->format = *__mt9p031_get_pad_format(mt9p031, sd_state, fmt->pad, 578cb7a01acSMauro Carvalho Chehab fmt->which); 579cb7a01acSMauro Carvalho Chehab return 0; 580cb7a01acSMauro Carvalho Chehab } 581cb7a01acSMauro Carvalho Chehab 582cb7a01acSMauro Carvalho Chehab static int mt9p031_set_format(struct v4l2_subdev *subdev, 5830d346d2aSTomi Valkeinen struct v4l2_subdev_state *sd_state, 584cb7a01acSMauro Carvalho Chehab struct v4l2_subdev_format *format) 585cb7a01acSMauro Carvalho Chehab { 586cb7a01acSMauro Carvalho Chehab struct mt9p031 *mt9p031 = to_mt9p031(subdev); 587cb7a01acSMauro Carvalho Chehab struct v4l2_mbus_framefmt *__format; 588cb7a01acSMauro Carvalho Chehab struct v4l2_rect *__crop; 589cb7a01acSMauro Carvalho Chehab unsigned int width; 590cb7a01acSMauro Carvalho Chehab unsigned int height; 591cb7a01acSMauro Carvalho Chehab unsigned int hratio; 592cb7a01acSMauro Carvalho Chehab unsigned int vratio; 593cb7a01acSMauro Carvalho Chehab 5940d346d2aSTomi Valkeinen __crop = __mt9p031_get_pad_crop(mt9p031, sd_state, format->pad, 595cb7a01acSMauro Carvalho Chehab format->which); 596cb7a01acSMauro Carvalho Chehab 597cb7a01acSMauro Carvalho Chehab /* Clamp the width and height to avoid dividing by zero. */ 598cb7a01acSMauro Carvalho Chehab width = clamp_t(unsigned int, ALIGN(format->format.width, 2), 599f90580caSRicardo Ribalda max_t(unsigned int, __crop->width / 7, 600f90580caSRicardo Ribalda MT9P031_WINDOW_WIDTH_MIN), 601cb7a01acSMauro Carvalho Chehab __crop->width); 602cb7a01acSMauro Carvalho Chehab height = clamp_t(unsigned int, ALIGN(format->format.height, 2), 603f90580caSRicardo Ribalda max_t(unsigned int, __crop->height / 8, 604f90580caSRicardo Ribalda MT9P031_WINDOW_HEIGHT_MIN), 605cb7a01acSMauro Carvalho Chehab __crop->height); 606cb7a01acSMauro Carvalho Chehab 607cb7a01acSMauro Carvalho Chehab hratio = DIV_ROUND_CLOSEST(__crop->width, width); 608cb7a01acSMauro Carvalho Chehab vratio = DIV_ROUND_CLOSEST(__crop->height, height); 609cb7a01acSMauro Carvalho Chehab 6100d346d2aSTomi Valkeinen __format = __mt9p031_get_pad_format(mt9p031, sd_state, format->pad, 611cb7a01acSMauro Carvalho Chehab format->which); 612cb7a01acSMauro Carvalho Chehab __format->width = __crop->width / hratio; 613cb7a01acSMauro Carvalho Chehab __format->height = __crop->height / vratio; 614cb7a01acSMauro Carvalho Chehab 615cb7a01acSMauro Carvalho Chehab format->format = *__format; 616cb7a01acSMauro Carvalho Chehab 617cb7a01acSMauro Carvalho Chehab return 0; 618cb7a01acSMauro Carvalho Chehab } 619cb7a01acSMauro Carvalho Chehab 6201a023febSHans Verkuil static int mt9p031_get_selection(struct v4l2_subdev *subdev, 6210d346d2aSTomi Valkeinen struct v4l2_subdev_state *sd_state, 6221a023febSHans Verkuil struct v4l2_subdev_selection *sel) 623cb7a01acSMauro Carvalho Chehab { 624cb7a01acSMauro Carvalho Chehab struct mt9p031 *mt9p031 = to_mt9p031(subdev); 625cb7a01acSMauro Carvalho Chehab 6261a023febSHans Verkuil if (sel->target != V4L2_SEL_TGT_CROP) 6271a023febSHans Verkuil return -EINVAL; 6281a023febSHans Verkuil 6290d346d2aSTomi Valkeinen sel->r = *__mt9p031_get_pad_crop(mt9p031, sd_state, sel->pad, 6300d346d2aSTomi Valkeinen sel->which); 631cb7a01acSMauro Carvalho Chehab return 0; 632cb7a01acSMauro Carvalho Chehab } 633cb7a01acSMauro Carvalho Chehab 6341a023febSHans Verkuil static int mt9p031_set_selection(struct v4l2_subdev *subdev, 6350d346d2aSTomi Valkeinen struct v4l2_subdev_state *sd_state, 6361a023febSHans Verkuil struct v4l2_subdev_selection *sel) 637cb7a01acSMauro Carvalho Chehab { 638cb7a01acSMauro Carvalho Chehab struct mt9p031 *mt9p031 = to_mt9p031(subdev); 639cb7a01acSMauro Carvalho Chehab struct v4l2_mbus_framefmt *__format; 640cb7a01acSMauro Carvalho Chehab struct v4l2_rect *__crop; 641cb7a01acSMauro Carvalho Chehab struct v4l2_rect rect; 642cb7a01acSMauro Carvalho Chehab 6431a023febSHans Verkuil if (sel->target != V4L2_SEL_TGT_CROP) 6441a023febSHans Verkuil return -EINVAL; 6451a023febSHans Verkuil 646cb7a01acSMauro Carvalho Chehab /* Clamp the crop rectangle boundaries and align them to a multiple of 2 647cb7a01acSMauro Carvalho Chehab * pixels to ensure a GRBG Bayer pattern. 648cb7a01acSMauro Carvalho Chehab */ 6491a023febSHans Verkuil rect.left = clamp(ALIGN(sel->r.left, 2), MT9P031_COLUMN_START_MIN, 650cb7a01acSMauro Carvalho Chehab MT9P031_COLUMN_START_MAX); 6511a023febSHans Verkuil rect.top = clamp(ALIGN(sel->r.top, 2), MT9P031_ROW_START_MIN, 652cb7a01acSMauro Carvalho Chehab MT9P031_ROW_START_MAX); 6531a023febSHans Verkuil rect.width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), 654cb7a01acSMauro Carvalho Chehab MT9P031_WINDOW_WIDTH_MIN, 655cb7a01acSMauro Carvalho Chehab MT9P031_WINDOW_WIDTH_MAX); 6561a023febSHans Verkuil rect.height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), 657cb7a01acSMauro Carvalho Chehab MT9P031_WINDOW_HEIGHT_MIN, 658cb7a01acSMauro Carvalho Chehab MT9P031_WINDOW_HEIGHT_MAX); 659cb7a01acSMauro Carvalho Chehab 660f90580caSRicardo Ribalda rect.width = min_t(unsigned int, rect.width, 661f90580caSRicardo Ribalda MT9P031_PIXEL_ARRAY_WIDTH - rect.left); 662f90580caSRicardo Ribalda rect.height = min_t(unsigned int, rect.height, 663f90580caSRicardo Ribalda MT9P031_PIXEL_ARRAY_HEIGHT - rect.top); 664cb7a01acSMauro Carvalho Chehab 6650d346d2aSTomi Valkeinen __crop = __mt9p031_get_pad_crop(mt9p031, sd_state, sel->pad, 6660d346d2aSTomi Valkeinen sel->which); 667cb7a01acSMauro Carvalho Chehab 668cb7a01acSMauro Carvalho Chehab if (rect.width != __crop->width || rect.height != __crop->height) { 669cb7a01acSMauro Carvalho Chehab /* Reset the output image size if the crop rectangle size has 670cb7a01acSMauro Carvalho Chehab * been modified. 671cb7a01acSMauro Carvalho Chehab */ 6720d346d2aSTomi Valkeinen __format = __mt9p031_get_pad_format(mt9p031, sd_state, 6730d346d2aSTomi Valkeinen sel->pad, 6741a023febSHans Verkuil sel->which); 675cb7a01acSMauro Carvalho Chehab __format->width = rect.width; 676cb7a01acSMauro Carvalho Chehab __format->height = rect.height; 677cb7a01acSMauro Carvalho Chehab } 678cb7a01acSMauro Carvalho Chehab 679cb7a01acSMauro Carvalho Chehab *__crop = rect; 6801a023febSHans Verkuil sel->r = rect; 681cb7a01acSMauro Carvalho Chehab 682cb7a01acSMauro Carvalho Chehab return 0; 683cb7a01acSMauro Carvalho Chehab } 684cb7a01acSMauro Carvalho Chehab 685cb7a01acSMauro Carvalho Chehab /* ----------------------------------------------------------------------------- 686cb7a01acSMauro Carvalho Chehab * V4L2 subdev control operations 687cb7a01acSMauro Carvalho Chehab */ 688cb7a01acSMauro Carvalho Chehab 689cb7a01acSMauro Carvalho Chehab #define V4L2_CID_BLC_AUTO (V4L2_CID_USER_BASE | 0x1002) 690cb7a01acSMauro Carvalho Chehab #define V4L2_CID_BLC_TARGET_LEVEL (V4L2_CID_USER_BASE | 0x1003) 691cb7a01acSMauro Carvalho Chehab #define V4L2_CID_BLC_ANALOG_OFFSET (V4L2_CID_USER_BASE | 0x1004) 692cb7a01acSMauro Carvalho Chehab #define V4L2_CID_BLC_DIGITAL_OFFSET (V4L2_CID_USER_BASE | 0x1005) 693cb7a01acSMauro Carvalho Chehab 694535ec214SLaurent Pinchart static int mt9p031_restore_blc(struct mt9p031 *mt9p031) 695535ec214SLaurent Pinchart { 696535ec214SLaurent Pinchart struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); 697535ec214SLaurent Pinchart int ret; 698535ec214SLaurent Pinchart 699535ec214SLaurent Pinchart if (mt9p031->blc_auto->cur.val != 0) { 700535ec214SLaurent Pinchart ret = mt9p031_set_mode2(mt9p031, 0, 701535ec214SLaurent Pinchart MT9P031_READ_MODE_2_ROW_BLC); 702535ec214SLaurent Pinchart if (ret < 0) 703535ec214SLaurent Pinchart return ret; 704535ec214SLaurent Pinchart } 705535ec214SLaurent Pinchart 706535ec214SLaurent Pinchart if (mt9p031->blc_offset->cur.val != 0) { 707535ec214SLaurent Pinchart ret = mt9p031_write(client, MT9P031_ROW_BLACK_TARGET, 708535ec214SLaurent Pinchart mt9p031->blc_offset->cur.val); 709535ec214SLaurent Pinchart if (ret < 0) 710535ec214SLaurent Pinchart return ret; 711535ec214SLaurent Pinchart } 712535ec214SLaurent Pinchart 713535ec214SLaurent Pinchart return 0; 714535ec214SLaurent Pinchart } 715535ec214SLaurent Pinchart 716cb7a01acSMauro Carvalho Chehab static int mt9p031_s_ctrl(struct v4l2_ctrl *ctrl) 717cb7a01acSMauro Carvalho Chehab { 718cb7a01acSMauro Carvalho Chehab struct mt9p031 *mt9p031 = 719cb7a01acSMauro Carvalho Chehab container_of(ctrl->handler, struct mt9p031, ctrls); 720cb7a01acSMauro Carvalho Chehab struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); 721cb7a01acSMauro Carvalho Chehab u16 data; 722cb7a01acSMauro Carvalho Chehab int ret; 723cb7a01acSMauro Carvalho Chehab 7248bf54c43SLaurent Pinchart if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) 7258bf54c43SLaurent Pinchart return 0; 7268bf54c43SLaurent Pinchart 727cb7a01acSMauro Carvalho Chehab switch (ctrl->id) { 728cb7a01acSMauro Carvalho Chehab case V4L2_CID_EXPOSURE: 729cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_SHUTTER_WIDTH_UPPER, 730cb7a01acSMauro Carvalho Chehab (ctrl->val >> 16) & 0xffff); 731cb7a01acSMauro Carvalho Chehab if (ret < 0) 732cb7a01acSMauro Carvalho Chehab return ret; 733cb7a01acSMauro Carvalho Chehab 734cb7a01acSMauro Carvalho Chehab return mt9p031_write(client, MT9P031_SHUTTER_WIDTH_LOWER, 735cb7a01acSMauro Carvalho Chehab ctrl->val & 0xffff); 736cb7a01acSMauro Carvalho Chehab 737cb7a01acSMauro Carvalho Chehab case V4L2_CID_GAIN: 738cb7a01acSMauro Carvalho Chehab /* Gain is controlled by 2 analog stages and a digital stage. 739cb7a01acSMauro Carvalho Chehab * Valid values for the 3 stages are 740cb7a01acSMauro Carvalho Chehab * 741cb7a01acSMauro Carvalho Chehab * Stage Min Max Step 742cb7a01acSMauro Carvalho Chehab * ------------------------------------------ 743cb7a01acSMauro Carvalho Chehab * First analog stage x1 x2 1 744cb7a01acSMauro Carvalho Chehab * Second analog stage x1 x4 0.125 745cb7a01acSMauro Carvalho Chehab * Digital stage x1 x16 0.125 746cb7a01acSMauro Carvalho Chehab * 747cb7a01acSMauro Carvalho Chehab * To minimize noise, the gain stages should be used in the 748cb7a01acSMauro Carvalho Chehab * second analog stage, first analog stage, digital stage order. 749cb7a01acSMauro Carvalho Chehab * Gain from a previous stage should be pushed to its maximum 750cb7a01acSMauro Carvalho Chehab * value before the next stage is used. 751cb7a01acSMauro Carvalho Chehab */ 752cb7a01acSMauro Carvalho Chehab if (ctrl->val <= 32) { 753cb7a01acSMauro Carvalho Chehab data = ctrl->val; 754cb7a01acSMauro Carvalho Chehab } else if (ctrl->val <= 64) { 755cb7a01acSMauro Carvalho Chehab ctrl->val &= ~1; 756cb7a01acSMauro Carvalho Chehab data = (1 << 6) | (ctrl->val >> 1); 757cb7a01acSMauro Carvalho Chehab } else { 758cb7a01acSMauro Carvalho Chehab ctrl->val &= ~7; 759cb7a01acSMauro Carvalho Chehab data = ((ctrl->val - 64) << 5) | (1 << 6) | 32; 760cb7a01acSMauro Carvalho Chehab } 761cb7a01acSMauro Carvalho Chehab 762cb7a01acSMauro Carvalho Chehab return mt9p031_write(client, MT9P031_GLOBAL_GAIN, data); 763cb7a01acSMauro Carvalho Chehab 764cb7a01acSMauro Carvalho Chehab case V4L2_CID_HFLIP: 765cb7a01acSMauro Carvalho Chehab if (ctrl->val) 766cb7a01acSMauro Carvalho Chehab return mt9p031_set_mode2(mt9p031, 767cb7a01acSMauro Carvalho Chehab 0, MT9P031_READ_MODE_2_COL_MIR); 768cb7a01acSMauro Carvalho Chehab else 769cb7a01acSMauro Carvalho Chehab return mt9p031_set_mode2(mt9p031, 770cb7a01acSMauro Carvalho Chehab MT9P031_READ_MODE_2_COL_MIR, 0); 771cb7a01acSMauro Carvalho Chehab 772cb7a01acSMauro Carvalho Chehab case V4L2_CID_VFLIP: 773cb7a01acSMauro Carvalho Chehab if (ctrl->val) 774cb7a01acSMauro Carvalho Chehab return mt9p031_set_mode2(mt9p031, 775cb7a01acSMauro Carvalho Chehab 0, MT9P031_READ_MODE_2_ROW_MIR); 776cb7a01acSMauro Carvalho Chehab else 777cb7a01acSMauro Carvalho Chehab return mt9p031_set_mode2(mt9p031, 778cb7a01acSMauro Carvalho Chehab MT9P031_READ_MODE_2_ROW_MIR, 0); 779cb7a01acSMauro Carvalho Chehab 780cb7a01acSMauro Carvalho Chehab case V4L2_CID_TEST_PATTERN: 7818bf54c43SLaurent Pinchart /* The digital side of the Black Level Calibration function must 7828bf54c43SLaurent Pinchart * be disabled when generating a test pattern to avoid artifacts 7838bf54c43SLaurent Pinchart * in the image. Activate (deactivate) the BLC-related controls 7848bf54c43SLaurent Pinchart * when the test pattern is enabled (disabled). 7858bf54c43SLaurent Pinchart */ 7868bf54c43SLaurent Pinchart v4l2_ctrl_activate(mt9p031->blc_auto, ctrl->val == 0); 7878bf54c43SLaurent Pinchart v4l2_ctrl_activate(mt9p031->blc_offset, ctrl->val == 0); 7888bf54c43SLaurent Pinchart 789cb7a01acSMauro Carvalho Chehab if (!ctrl->val) { 7908bf54c43SLaurent Pinchart /* Restore the BLC settings. */ 791535ec214SLaurent Pinchart ret = mt9p031_restore_blc(mt9p031); 792cb7a01acSMauro Carvalho Chehab if (ret < 0) 793cb7a01acSMauro Carvalho Chehab return ret; 794535ec214SLaurent Pinchart 7950a0e78d1SStefan Riedmueller return mt9p031_write(client, MT9P031_TEST_PATTERN, 0); 796cb7a01acSMauro Carvalho Chehab } 797cb7a01acSMauro Carvalho Chehab 798cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_TEST_PATTERN_GREEN, 0x05a0); 799cb7a01acSMauro Carvalho Chehab if (ret < 0) 800cb7a01acSMauro Carvalho Chehab return ret; 801cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_TEST_PATTERN_RED, 0x0a50); 802cb7a01acSMauro Carvalho Chehab if (ret < 0) 803cb7a01acSMauro Carvalho Chehab return ret; 804cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_TEST_PATTERN_BLUE, 0x0aa0); 805cb7a01acSMauro Carvalho Chehab if (ret < 0) 806cb7a01acSMauro Carvalho Chehab return ret; 807cb7a01acSMauro Carvalho Chehab 8088bf54c43SLaurent Pinchart /* Disable digital BLC when generating a test pattern. */ 809cb7a01acSMauro Carvalho Chehab ret = mt9p031_set_mode2(mt9p031, MT9P031_READ_MODE_2_ROW_BLC, 810cb7a01acSMauro Carvalho Chehab 0); 811cb7a01acSMauro Carvalho Chehab if (ret < 0) 812cb7a01acSMauro Carvalho Chehab return ret; 813cb7a01acSMauro Carvalho Chehab 814cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_ROW_BLACK_DEF_OFFSET, 0); 815cb7a01acSMauro Carvalho Chehab if (ret < 0) 816cb7a01acSMauro Carvalho Chehab return ret; 817cb7a01acSMauro Carvalho Chehab 818cb7a01acSMauro Carvalho Chehab return mt9p031_write(client, MT9P031_TEST_PATTERN, 819cb7a01acSMauro Carvalho Chehab ((ctrl->val - 1) << MT9P031_TEST_PATTERN_SHIFT) 820cb7a01acSMauro Carvalho Chehab | MT9P031_TEST_PATTERN_ENABLE); 821cb7a01acSMauro Carvalho Chehab 822cb7a01acSMauro Carvalho Chehab case V4L2_CID_BLC_AUTO: 823cb7a01acSMauro Carvalho Chehab ret = mt9p031_set_mode2(mt9p031, 824cb7a01acSMauro Carvalho Chehab ctrl->val ? 0 : MT9P031_READ_MODE_2_ROW_BLC, 825cb7a01acSMauro Carvalho Chehab ctrl->val ? MT9P031_READ_MODE_2_ROW_BLC : 0); 826cb7a01acSMauro Carvalho Chehab if (ret < 0) 827cb7a01acSMauro Carvalho Chehab return ret; 828cb7a01acSMauro Carvalho Chehab 829cb7a01acSMauro Carvalho Chehab return mt9p031_write(client, MT9P031_BLACK_LEVEL_CALIBRATION, 830cb7a01acSMauro Carvalho Chehab ctrl->val ? 0 : MT9P031_BLC_MANUAL_BLC); 831cb7a01acSMauro Carvalho Chehab 832cb7a01acSMauro Carvalho Chehab case V4L2_CID_BLC_TARGET_LEVEL: 833cb7a01acSMauro Carvalho Chehab return mt9p031_write(client, MT9P031_ROW_BLACK_TARGET, 834cb7a01acSMauro Carvalho Chehab ctrl->val); 835cb7a01acSMauro Carvalho Chehab 836cb7a01acSMauro Carvalho Chehab case V4L2_CID_BLC_ANALOG_OFFSET: 837cb7a01acSMauro Carvalho Chehab data = ctrl->val & ((1 << 9) - 1); 838cb7a01acSMauro Carvalho Chehab 839cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_GREEN1_OFFSET, data); 840cb7a01acSMauro Carvalho Chehab if (ret < 0) 841cb7a01acSMauro Carvalho Chehab return ret; 842cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_GREEN2_OFFSET, data); 843cb7a01acSMauro Carvalho Chehab if (ret < 0) 844cb7a01acSMauro Carvalho Chehab return ret; 845cb7a01acSMauro Carvalho Chehab ret = mt9p031_write(client, MT9P031_RED_OFFSET, data); 846cb7a01acSMauro Carvalho Chehab if (ret < 0) 847cb7a01acSMauro Carvalho Chehab return ret; 848cb7a01acSMauro Carvalho Chehab return mt9p031_write(client, MT9P031_BLUE_OFFSET, data); 849cb7a01acSMauro Carvalho Chehab 850cb7a01acSMauro Carvalho Chehab case V4L2_CID_BLC_DIGITAL_OFFSET: 851cb7a01acSMauro Carvalho Chehab return mt9p031_write(client, MT9P031_ROW_BLACK_DEF_OFFSET, 852cb7a01acSMauro Carvalho Chehab ctrl->val & ((1 << 12) - 1)); 853cb7a01acSMauro Carvalho Chehab } 854cb7a01acSMauro Carvalho Chehab 855cb7a01acSMauro Carvalho Chehab return 0; 856cb7a01acSMauro Carvalho Chehab } 857cb7a01acSMauro Carvalho Chehab 858217bdb07SJulia Lawall static const struct v4l2_ctrl_ops mt9p031_ctrl_ops = { 859cb7a01acSMauro Carvalho Chehab .s_ctrl = mt9p031_s_ctrl, 860cb7a01acSMauro Carvalho Chehab }; 861cb7a01acSMauro Carvalho Chehab 862cb7a01acSMauro Carvalho Chehab static const char * const mt9p031_test_pattern_menu[] = { 863cb7a01acSMauro Carvalho Chehab "Disabled", 864cb7a01acSMauro Carvalho Chehab "Color Field", 865cb7a01acSMauro Carvalho Chehab "Horizontal Gradient", 866cb7a01acSMauro Carvalho Chehab "Vertical Gradient", 867cb7a01acSMauro Carvalho Chehab "Diagonal Gradient", 868cb7a01acSMauro Carvalho Chehab "Classic Test Pattern", 869cb7a01acSMauro Carvalho Chehab "Walking 1s", 870cb7a01acSMauro Carvalho Chehab "Monochrome Horizontal Bars", 871cb7a01acSMauro Carvalho Chehab "Monochrome Vertical Bars", 872cb7a01acSMauro Carvalho Chehab "Vertical Color Bars", 873cb7a01acSMauro Carvalho Chehab }; 874cb7a01acSMauro Carvalho Chehab 875cb7a01acSMauro Carvalho Chehab static const struct v4l2_ctrl_config mt9p031_ctrls[] = { 876cb7a01acSMauro Carvalho Chehab { 877cb7a01acSMauro Carvalho Chehab .ops = &mt9p031_ctrl_ops, 878cb7a01acSMauro Carvalho Chehab .id = V4L2_CID_BLC_AUTO, 879cb7a01acSMauro Carvalho Chehab .type = V4L2_CTRL_TYPE_BOOLEAN, 880cb7a01acSMauro Carvalho Chehab .name = "BLC, Auto", 881cb7a01acSMauro Carvalho Chehab .min = 0, 882cb7a01acSMauro Carvalho Chehab .max = 1, 883cb7a01acSMauro Carvalho Chehab .step = 1, 884cb7a01acSMauro Carvalho Chehab .def = 1, 885cb7a01acSMauro Carvalho Chehab .flags = 0, 886cb7a01acSMauro Carvalho Chehab }, { 887cb7a01acSMauro Carvalho Chehab .ops = &mt9p031_ctrl_ops, 888cb7a01acSMauro Carvalho Chehab .id = V4L2_CID_BLC_TARGET_LEVEL, 889cb7a01acSMauro Carvalho Chehab .type = V4L2_CTRL_TYPE_INTEGER, 890cb7a01acSMauro Carvalho Chehab .name = "BLC Target Level", 891cb7a01acSMauro Carvalho Chehab .min = 0, 892cb7a01acSMauro Carvalho Chehab .max = 4095, 893cb7a01acSMauro Carvalho Chehab .step = 1, 894cb7a01acSMauro Carvalho Chehab .def = 168, 895cb7a01acSMauro Carvalho Chehab .flags = 0, 896cb7a01acSMauro Carvalho Chehab }, { 897cb7a01acSMauro Carvalho Chehab .ops = &mt9p031_ctrl_ops, 898cb7a01acSMauro Carvalho Chehab .id = V4L2_CID_BLC_ANALOG_OFFSET, 899cb7a01acSMauro Carvalho Chehab .type = V4L2_CTRL_TYPE_INTEGER, 900cb7a01acSMauro Carvalho Chehab .name = "BLC Analog Offset", 901cb7a01acSMauro Carvalho Chehab .min = -255, 902cb7a01acSMauro Carvalho Chehab .max = 255, 903cb7a01acSMauro Carvalho Chehab .step = 1, 904cb7a01acSMauro Carvalho Chehab .def = 32, 905cb7a01acSMauro Carvalho Chehab .flags = 0, 906cb7a01acSMauro Carvalho Chehab }, { 907cb7a01acSMauro Carvalho Chehab .ops = &mt9p031_ctrl_ops, 908cb7a01acSMauro Carvalho Chehab .id = V4L2_CID_BLC_DIGITAL_OFFSET, 909cb7a01acSMauro Carvalho Chehab .type = V4L2_CTRL_TYPE_INTEGER, 910cb7a01acSMauro Carvalho Chehab .name = "BLC Digital Offset", 911cb7a01acSMauro Carvalho Chehab .min = -2048, 912cb7a01acSMauro Carvalho Chehab .max = 2047, 913cb7a01acSMauro Carvalho Chehab .step = 1, 914cb7a01acSMauro Carvalho Chehab .def = 40, 915cb7a01acSMauro Carvalho Chehab .flags = 0, 916cb7a01acSMauro Carvalho Chehab } 917cb7a01acSMauro Carvalho Chehab }; 918cb7a01acSMauro Carvalho Chehab 919cb7a01acSMauro Carvalho Chehab /* ----------------------------------------------------------------------------- 920cb7a01acSMauro Carvalho Chehab * V4L2 subdev core operations 921cb7a01acSMauro Carvalho Chehab */ 922cb7a01acSMauro Carvalho Chehab 923cb7a01acSMauro Carvalho Chehab static int mt9p031_set_power(struct v4l2_subdev *subdev, int on) 924cb7a01acSMauro Carvalho Chehab { 925cb7a01acSMauro Carvalho Chehab struct mt9p031 *mt9p031 = to_mt9p031(subdev); 926cb7a01acSMauro Carvalho Chehab int ret = 0; 927cb7a01acSMauro Carvalho Chehab 928cb7a01acSMauro Carvalho Chehab mutex_lock(&mt9p031->power_lock); 929cb7a01acSMauro Carvalho Chehab 930cb7a01acSMauro Carvalho Chehab /* If the power count is modified from 0 to != 0 or from != 0 to 0, 931cb7a01acSMauro Carvalho Chehab * update the power state. 932cb7a01acSMauro Carvalho Chehab */ 933cb7a01acSMauro Carvalho Chehab if (mt9p031->power_count == !on) { 934cb7a01acSMauro Carvalho Chehab ret = __mt9p031_set_power(mt9p031, !!on); 935cb7a01acSMauro Carvalho Chehab if (ret < 0) 936cb7a01acSMauro Carvalho Chehab goto out; 937cb7a01acSMauro Carvalho Chehab } 938cb7a01acSMauro Carvalho Chehab 939cb7a01acSMauro Carvalho Chehab /* Update the power count. */ 940cb7a01acSMauro Carvalho Chehab mt9p031->power_count += on ? 1 : -1; 941cb7a01acSMauro Carvalho Chehab WARN_ON(mt9p031->power_count < 0); 942cb7a01acSMauro Carvalho Chehab 943cb7a01acSMauro Carvalho Chehab out: 944cb7a01acSMauro Carvalho Chehab mutex_unlock(&mt9p031->power_lock); 945cb7a01acSMauro Carvalho Chehab return ret; 946cb7a01acSMauro Carvalho Chehab } 947cb7a01acSMauro Carvalho Chehab 948cb7a01acSMauro Carvalho Chehab /* ----------------------------------------------------------------------------- 949cb7a01acSMauro Carvalho Chehab * V4L2 subdev internal operations 950cb7a01acSMauro Carvalho Chehab */ 951cb7a01acSMauro Carvalho Chehab 952cb7a01acSMauro Carvalho Chehab static int mt9p031_registered(struct v4l2_subdev *subdev) 953cb7a01acSMauro Carvalho Chehab { 954cb7a01acSMauro Carvalho Chehab struct i2c_client *client = v4l2_get_subdevdata(subdev); 955cb7a01acSMauro Carvalho Chehab struct mt9p031 *mt9p031 = to_mt9p031(subdev); 956cb7a01acSMauro Carvalho Chehab s32 data; 957cb7a01acSMauro Carvalho Chehab int ret; 958cb7a01acSMauro Carvalho Chehab 959cb7a01acSMauro Carvalho Chehab ret = mt9p031_power_on(mt9p031); 960cb7a01acSMauro Carvalho Chehab if (ret < 0) { 961cb7a01acSMauro Carvalho Chehab dev_err(&client->dev, "MT9P031 power up failed\n"); 962cb7a01acSMauro Carvalho Chehab return ret; 963cb7a01acSMauro Carvalho Chehab } 964cb7a01acSMauro Carvalho Chehab 965cb7a01acSMauro Carvalho Chehab /* Read out the chip version register */ 966cb7a01acSMauro Carvalho Chehab data = mt9p031_read(client, MT9P031_CHIP_VERSION); 967bbcc9fa0SGuennadi Liakhovetski mt9p031_power_off(mt9p031); 968bbcc9fa0SGuennadi Liakhovetski 969cb7a01acSMauro Carvalho Chehab if (data != MT9P031_CHIP_VERSION_VALUE) { 970cb7a01acSMauro Carvalho Chehab dev_err(&client->dev, "MT9P031 not detected, wrong version " 971cb7a01acSMauro Carvalho Chehab "0x%04x\n", data); 972cb7a01acSMauro Carvalho Chehab return -ENODEV; 973cb7a01acSMauro Carvalho Chehab } 974cb7a01acSMauro Carvalho Chehab 975cb7a01acSMauro Carvalho Chehab dev_info(&client->dev, "MT9P031 detected at address 0x%02x\n", 976cb7a01acSMauro Carvalho Chehab client->addr); 977cb7a01acSMauro Carvalho Chehab 978bbcc9fa0SGuennadi Liakhovetski return 0; 979cb7a01acSMauro Carvalho Chehab } 980cb7a01acSMauro Carvalho Chehab 981cb7a01acSMauro Carvalho Chehab static int mt9p031_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) 982cb7a01acSMauro Carvalho Chehab { 983cb7a01acSMauro Carvalho Chehab struct mt9p031 *mt9p031 = to_mt9p031(subdev); 984cb7a01acSMauro Carvalho Chehab struct v4l2_mbus_framefmt *format; 985cb7a01acSMauro Carvalho Chehab struct v4l2_rect *crop; 986cb7a01acSMauro Carvalho Chehab 9870d346d2aSTomi Valkeinen crop = v4l2_subdev_get_try_crop(subdev, fh->state, 0); 988cb7a01acSMauro Carvalho Chehab crop->left = MT9P031_COLUMN_START_DEF; 989cb7a01acSMauro Carvalho Chehab crop->top = MT9P031_ROW_START_DEF; 990cb7a01acSMauro Carvalho Chehab crop->width = MT9P031_WINDOW_WIDTH_DEF; 991cb7a01acSMauro Carvalho Chehab crop->height = MT9P031_WINDOW_HEIGHT_DEF; 992cb7a01acSMauro Carvalho Chehab 9930d346d2aSTomi Valkeinen format = v4l2_subdev_get_try_format(subdev, fh->state, 0); 994cb7a01acSMauro Carvalho Chehab 995cb7a01acSMauro Carvalho Chehab if (mt9p031->model == MT9P031_MODEL_MONOCHROME) 996f5fe58fdSBoris BREZILLON format->code = MEDIA_BUS_FMT_Y12_1X12; 997cb7a01acSMauro Carvalho Chehab else 998f5fe58fdSBoris BREZILLON format->code = MEDIA_BUS_FMT_SGRBG12_1X12; 999cb7a01acSMauro Carvalho Chehab 1000cb7a01acSMauro Carvalho Chehab format->width = MT9P031_WINDOW_WIDTH_DEF; 1001cb7a01acSMauro Carvalho Chehab format->height = MT9P031_WINDOW_HEIGHT_DEF; 1002cb7a01acSMauro Carvalho Chehab format->field = V4L2_FIELD_NONE; 1003cb7a01acSMauro Carvalho Chehab format->colorspace = V4L2_COLORSPACE_SRGB; 1004cb7a01acSMauro Carvalho Chehab 1005cb7a01acSMauro Carvalho Chehab return mt9p031_set_power(subdev, 1); 1006cb7a01acSMauro Carvalho Chehab } 1007cb7a01acSMauro Carvalho Chehab 1008cb7a01acSMauro Carvalho Chehab static int mt9p031_close(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) 1009cb7a01acSMauro Carvalho Chehab { 1010cb7a01acSMauro Carvalho Chehab return mt9p031_set_power(subdev, 0); 1011cb7a01acSMauro Carvalho Chehab } 1012cb7a01acSMauro Carvalho Chehab 10137c137c60SBhumika Goyal static const struct v4l2_subdev_core_ops mt9p031_subdev_core_ops = { 1014cb7a01acSMauro Carvalho Chehab .s_power = mt9p031_set_power, 1015cb7a01acSMauro Carvalho Chehab }; 1016cb7a01acSMauro Carvalho Chehab 10177c137c60SBhumika Goyal static const struct v4l2_subdev_video_ops mt9p031_subdev_video_ops = { 1018cb7a01acSMauro Carvalho Chehab .s_stream = mt9p031_s_stream, 1019cb7a01acSMauro Carvalho Chehab }; 1020cb7a01acSMauro Carvalho Chehab 10217c137c60SBhumika Goyal static const struct v4l2_subdev_pad_ops mt9p031_subdev_pad_ops = { 1022cb7a01acSMauro Carvalho Chehab .enum_mbus_code = mt9p031_enum_mbus_code, 1023cb7a01acSMauro Carvalho Chehab .enum_frame_size = mt9p031_enum_frame_size, 1024cb7a01acSMauro Carvalho Chehab .get_fmt = mt9p031_get_format, 1025cb7a01acSMauro Carvalho Chehab .set_fmt = mt9p031_set_format, 10261a023febSHans Verkuil .get_selection = mt9p031_get_selection, 10271a023febSHans Verkuil .set_selection = mt9p031_set_selection, 1028cb7a01acSMauro Carvalho Chehab }; 1029cb7a01acSMauro Carvalho Chehab 10307c137c60SBhumika Goyal static const struct v4l2_subdev_ops mt9p031_subdev_ops = { 1031cb7a01acSMauro Carvalho Chehab .core = &mt9p031_subdev_core_ops, 1032cb7a01acSMauro Carvalho Chehab .video = &mt9p031_subdev_video_ops, 1033cb7a01acSMauro Carvalho Chehab .pad = &mt9p031_subdev_pad_ops, 1034cb7a01acSMauro Carvalho Chehab }; 1035cb7a01acSMauro Carvalho Chehab 1036cb7a01acSMauro Carvalho Chehab static const struct v4l2_subdev_internal_ops mt9p031_subdev_internal_ops = { 1037cb7a01acSMauro Carvalho Chehab .registered = mt9p031_registered, 1038cb7a01acSMauro Carvalho Chehab .open = mt9p031_open, 1039cb7a01acSMauro Carvalho Chehab .close = mt9p031_close, 1040cb7a01acSMauro Carvalho Chehab }; 1041cb7a01acSMauro Carvalho Chehab 1042cb7a01acSMauro Carvalho Chehab /* ----------------------------------------------------------------------------- 1043cb7a01acSMauro Carvalho Chehab * Driver initialization and probing 1044cb7a01acSMauro Carvalho Chehab */ 1045cb7a01acSMauro Carvalho Chehab 10468d4da37cSLad, Prabhakar static struct mt9p031_platform_data * 10478d4da37cSLad, Prabhakar mt9p031_get_pdata(struct i2c_client *client) 10488d4da37cSLad, Prabhakar { 1049ae47ee5fSChristian Hemp struct mt9p031_platform_data *pdata = NULL; 10508d4da37cSLad, Prabhakar struct device_node *np; 1051ae47ee5fSChristian Hemp struct v4l2_fwnode_endpoint endpoint = { 1052ae47ee5fSChristian Hemp .bus_type = V4L2_MBUS_PARALLEL 1053ae47ee5fSChristian Hemp }; 10548d4da37cSLad, Prabhakar 10558d4da37cSLad, Prabhakar if (!IS_ENABLED(CONFIG_OF) || !client->dev.of_node) 10568d4da37cSLad, Prabhakar return client->dev.platform_data; 10578d4da37cSLad, Prabhakar 1058fd9fdb78SPhilipp Zabel np = of_graph_get_next_endpoint(client->dev.of_node, NULL); 10598d4da37cSLad, Prabhakar if (!np) 10608d4da37cSLad, Prabhakar return NULL; 10618d4da37cSLad, Prabhakar 1062ae47ee5fSChristian Hemp if (v4l2_fwnode_endpoint_parse(of_fwnode_handle(np), &endpoint) < 0) 1063ae47ee5fSChristian Hemp goto done; 1064ae47ee5fSChristian Hemp 10658d4da37cSLad, Prabhakar pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); 10668d4da37cSLad, Prabhakar if (!pdata) 10678d4da37cSLad, Prabhakar goto done; 10688d4da37cSLad, Prabhakar 10698d4da37cSLad, Prabhakar of_property_read_u32(np, "input-clock-frequency", &pdata->ext_freq); 10708d4da37cSLad, Prabhakar of_property_read_u32(np, "pixel-clock-frequency", &pdata->target_freq); 10718d4da37cSLad, Prabhakar 1072ae47ee5fSChristian Hemp pdata->pixclk_pol = !!(endpoint.bus.parallel.flags & 1073ae47ee5fSChristian Hemp V4L2_MBUS_PCLK_SAMPLE_RISING); 1074ae47ee5fSChristian Hemp 10758d4da37cSLad, Prabhakar done: 10768d4da37cSLad, Prabhakar of_node_put(np); 10778d4da37cSLad, Prabhakar return pdata; 10788d4da37cSLad, Prabhakar } 10798d4da37cSLad, Prabhakar 1080cb7a01acSMauro Carvalho Chehab static int mt9p031_probe(struct i2c_client *client, 1081cb7a01acSMauro Carvalho Chehab const struct i2c_device_id *did) 1082cb7a01acSMauro Carvalho Chehab { 10838d4da37cSLad, Prabhakar struct mt9p031_platform_data *pdata = mt9p031_get_pdata(client); 1084a8a3e813SWolfram Sang struct i2c_adapter *adapter = client->adapter; 1085cb7a01acSMauro Carvalho Chehab struct mt9p031 *mt9p031; 1086cb7a01acSMauro Carvalho Chehab unsigned int i; 1087cb7a01acSMauro Carvalho Chehab int ret; 1088cb7a01acSMauro Carvalho Chehab 1089cb7a01acSMauro Carvalho Chehab if (pdata == NULL) { 1090cb7a01acSMauro Carvalho Chehab dev_err(&client->dev, "No platform data\n"); 1091cb7a01acSMauro Carvalho Chehab return -EINVAL; 1092cb7a01acSMauro Carvalho Chehab } 1093cb7a01acSMauro Carvalho Chehab 1094cb7a01acSMauro Carvalho Chehab if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) { 1095cb7a01acSMauro Carvalho Chehab dev_warn(&client->dev, 1096cb7a01acSMauro Carvalho Chehab "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n"); 1097cb7a01acSMauro Carvalho Chehab return -EIO; 1098cb7a01acSMauro Carvalho Chehab } 1099cb7a01acSMauro Carvalho Chehab 110037b9f211SLaurent Pinchart mt9p031 = devm_kzalloc(&client->dev, sizeof(*mt9p031), GFP_KERNEL); 1101cb7a01acSMauro Carvalho Chehab if (mt9p031 == NULL) 1102cb7a01acSMauro Carvalho Chehab return -ENOMEM; 1103cb7a01acSMauro Carvalho Chehab 1104cb7a01acSMauro Carvalho Chehab mt9p031->pdata = pdata; 1105cb7a01acSMauro Carvalho Chehab mt9p031->output_control = MT9P031_OUTPUT_CONTROL_DEF; 1106cb7a01acSMauro Carvalho Chehab mt9p031->mode2 = MT9P031_READ_MODE_2_ROW_BLC; 1107cb7a01acSMauro Carvalho Chehab mt9p031->model = did->driver_data; 1108cb7a01acSMauro Carvalho Chehab 11097997196cSLaurent Pinchart mt9p031->regulators[0].supply = "vdd"; 11107997196cSLaurent Pinchart mt9p031->regulators[1].supply = "vdd_io"; 11117997196cSLaurent Pinchart mt9p031->regulators[2].supply = "vaa"; 111297f21276SLaurent Pinchart 11137997196cSLaurent Pinchart ret = devm_regulator_bulk_get(&client->dev, 3, mt9p031->regulators); 11147997196cSLaurent Pinchart if (ret < 0) { 111597f21276SLaurent Pinchart dev_err(&client->dev, "Unable to get regulators\n"); 11167997196cSLaurent Pinchart return ret; 111797f21276SLaurent Pinchart } 111897f21276SLaurent Pinchart 111915af4a53SLad, Prabhakar mutex_init(&mt9p031->power_lock); 112015af4a53SLad, Prabhakar 1121b28d7017SLad, Prabhakar v4l2_ctrl_handler_init(&mt9p031->ctrls, ARRAY_SIZE(mt9p031_ctrls) + 6); 1122cb7a01acSMauro Carvalho Chehab 1123cb7a01acSMauro Carvalho Chehab v4l2_ctrl_new_std(&mt9p031->ctrls, &mt9p031_ctrl_ops, 1124cb7a01acSMauro Carvalho Chehab V4L2_CID_EXPOSURE, MT9P031_SHUTTER_WIDTH_MIN, 1125cb7a01acSMauro Carvalho Chehab MT9P031_SHUTTER_WIDTH_MAX, 1, 1126cb7a01acSMauro Carvalho Chehab MT9P031_SHUTTER_WIDTH_DEF); 1127cb7a01acSMauro Carvalho Chehab v4l2_ctrl_new_std(&mt9p031->ctrls, &mt9p031_ctrl_ops, 1128cb7a01acSMauro Carvalho Chehab V4L2_CID_GAIN, MT9P031_GLOBAL_GAIN_MIN, 1129cb7a01acSMauro Carvalho Chehab MT9P031_GLOBAL_GAIN_MAX, 1, MT9P031_GLOBAL_GAIN_DEF); 1130cb7a01acSMauro Carvalho Chehab v4l2_ctrl_new_std(&mt9p031->ctrls, &mt9p031_ctrl_ops, 1131cb7a01acSMauro Carvalho Chehab V4L2_CID_HFLIP, 0, 1, 1, 0); 1132cb7a01acSMauro Carvalho Chehab v4l2_ctrl_new_std(&mt9p031->ctrls, &mt9p031_ctrl_ops, 1133cb7a01acSMauro Carvalho Chehab V4L2_CID_VFLIP, 0, 1, 1, 0); 1134cb7a01acSMauro Carvalho Chehab v4l2_ctrl_new_std(&mt9p031->ctrls, &mt9p031_ctrl_ops, 1135cb7a01acSMauro Carvalho Chehab V4L2_CID_PIXEL_RATE, pdata->target_freq, 1136cb7a01acSMauro Carvalho Chehab pdata->target_freq, 1, pdata->target_freq); 1137b28d7017SLad, Prabhakar v4l2_ctrl_new_std_menu_items(&mt9p031->ctrls, &mt9p031_ctrl_ops, 1138b28d7017SLad, Prabhakar V4L2_CID_TEST_PATTERN, 1139b28d7017SLad, Prabhakar ARRAY_SIZE(mt9p031_test_pattern_menu) - 1, 0, 1140b28d7017SLad, Prabhakar 0, mt9p031_test_pattern_menu); 1141cb7a01acSMauro Carvalho Chehab 1142cb7a01acSMauro Carvalho Chehab for (i = 0; i < ARRAY_SIZE(mt9p031_ctrls); ++i) 1143cb7a01acSMauro Carvalho Chehab v4l2_ctrl_new_custom(&mt9p031->ctrls, &mt9p031_ctrls[i], NULL); 1144cb7a01acSMauro Carvalho Chehab 1145cb7a01acSMauro Carvalho Chehab mt9p031->subdev.ctrl_handler = &mt9p031->ctrls; 1146cb7a01acSMauro Carvalho Chehab 1147cb7a01acSMauro Carvalho Chehab if (mt9p031->ctrls.error) { 1148cb7a01acSMauro Carvalho Chehab printk(KERN_INFO "%s: control initialization error %d\n", 1149cb7a01acSMauro Carvalho Chehab __func__, mt9p031->ctrls.error); 1150cb7a01acSMauro Carvalho Chehab ret = mt9p031->ctrls.error; 1151cb7a01acSMauro Carvalho Chehab goto done; 1152cb7a01acSMauro Carvalho Chehab } 1153cb7a01acSMauro Carvalho Chehab 1154cb7a01acSMauro Carvalho Chehab mt9p031->blc_auto = v4l2_ctrl_find(&mt9p031->ctrls, V4L2_CID_BLC_AUTO); 1155cb7a01acSMauro Carvalho Chehab mt9p031->blc_offset = v4l2_ctrl_find(&mt9p031->ctrls, 1156cb7a01acSMauro Carvalho Chehab V4L2_CID_BLC_DIGITAL_OFFSET); 1157cb7a01acSMauro Carvalho Chehab 1158cb7a01acSMauro Carvalho Chehab v4l2_i2c_subdev_init(&mt9p031->subdev, client, &mt9p031_subdev_ops); 1159cb7a01acSMauro Carvalho Chehab mt9p031->subdev.internal_ops = &mt9p031_subdev_internal_ops; 1160cb7a01acSMauro Carvalho Chehab 1161173bf6e5SHans Verkuil mt9p031->subdev.entity.function = MEDIA_ENT_F_CAM_SENSOR; 1162cb7a01acSMauro Carvalho Chehab mt9p031->pad.flags = MEDIA_PAD_FL_SOURCE; 1163ab22e77cSMauro Carvalho Chehab ret = media_entity_pads_init(&mt9p031->subdev.entity, 1, &mt9p031->pad); 1164cb7a01acSMauro Carvalho Chehab if (ret < 0) 1165cb7a01acSMauro Carvalho Chehab goto done; 1166cb7a01acSMauro Carvalho Chehab 1167cb7a01acSMauro Carvalho Chehab mt9p031->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; 1168cb7a01acSMauro Carvalho Chehab 1169cb7a01acSMauro Carvalho Chehab mt9p031->crop.width = MT9P031_WINDOW_WIDTH_DEF; 1170cb7a01acSMauro Carvalho Chehab mt9p031->crop.height = MT9P031_WINDOW_HEIGHT_DEF; 1171cb7a01acSMauro Carvalho Chehab mt9p031->crop.left = MT9P031_COLUMN_START_DEF; 1172cb7a01acSMauro Carvalho Chehab mt9p031->crop.top = MT9P031_ROW_START_DEF; 1173cb7a01acSMauro Carvalho Chehab 1174cb7a01acSMauro Carvalho Chehab if (mt9p031->model == MT9P031_MODEL_MONOCHROME) 1175f5fe58fdSBoris BREZILLON mt9p031->format.code = MEDIA_BUS_FMT_Y12_1X12; 1176cb7a01acSMauro Carvalho Chehab else 1177f5fe58fdSBoris BREZILLON mt9p031->format.code = MEDIA_BUS_FMT_SGRBG12_1X12; 1178cb7a01acSMauro Carvalho Chehab 1179cb7a01acSMauro Carvalho Chehab mt9p031->format.width = MT9P031_WINDOW_WIDTH_DEF; 1180cb7a01acSMauro Carvalho Chehab mt9p031->format.height = MT9P031_WINDOW_HEIGHT_DEF; 1181cb7a01acSMauro Carvalho Chehab mt9p031->format.field = V4L2_FIELD_NONE; 1182cb7a01acSMauro Carvalho Chehab mt9p031->format.colorspace = V4L2_COLORSPACE_SRGB; 1183cb7a01acSMauro Carvalho Chehab 11847c3be9f8SLaurent Pinchart mt9p031->reset = devm_gpiod_get_optional(&client->dev, "reset", 11857c3be9f8SLaurent Pinchart GPIOD_OUT_HIGH); 1186cb7a01acSMauro Carvalho Chehab 1187d6749258SLaurent Pinchart ret = mt9p031_clk_setup(mt9p031); 11889012d088SLad, Prabhakar if (ret) 11899012d088SLad, Prabhakar goto done; 11909012d088SLad, Prabhakar 11919012d088SLad, Prabhakar ret = v4l2_async_register_subdev(&mt9p031->subdev); 1192cb7a01acSMauro Carvalho Chehab 1193cb7a01acSMauro Carvalho Chehab done: 1194cb7a01acSMauro Carvalho Chehab if (ret < 0) { 1195cb7a01acSMauro Carvalho Chehab v4l2_ctrl_handler_free(&mt9p031->ctrls); 1196cb7a01acSMauro Carvalho Chehab media_entity_cleanup(&mt9p031->subdev.entity); 119715af4a53SLad, Prabhakar mutex_destroy(&mt9p031->power_lock); 1198cb7a01acSMauro Carvalho Chehab } 1199cb7a01acSMauro Carvalho Chehab 1200cb7a01acSMauro Carvalho Chehab return ret; 1201cb7a01acSMauro Carvalho Chehab } 1202cb7a01acSMauro Carvalho Chehab 1203cb7a01acSMauro Carvalho Chehab static int mt9p031_remove(struct i2c_client *client) 1204cb7a01acSMauro Carvalho Chehab { 1205cb7a01acSMauro Carvalho Chehab struct v4l2_subdev *subdev = i2c_get_clientdata(client); 1206cb7a01acSMauro Carvalho Chehab struct mt9p031 *mt9p031 = to_mt9p031(subdev); 1207cb7a01acSMauro Carvalho Chehab 1208cb7a01acSMauro Carvalho Chehab v4l2_ctrl_handler_free(&mt9p031->ctrls); 12099012d088SLad, Prabhakar v4l2_async_unregister_subdev(subdev); 1210cb7a01acSMauro Carvalho Chehab media_entity_cleanup(&subdev->entity); 121115af4a53SLad, Prabhakar mutex_destroy(&mt9p031->power_lock); 1212cb7a01acSMauro Carvalho Chehab 1213cb7a01acSMauro Carvalho Chehab return 0; 1214cb7a01acSMauro Carvalho Chehab } 1215cb7a01acSMauro Carvalho Chehab 1216cb7a01acSMauro Carvalho Chehab static const struct i2c_device_id mt9p031_id[] = { 1217*92fa766bSMarek Vasut { "mt9p006", MT9P031_MODEL_COLOR }, 1218cb7a01acSMauro Carvalho Chehab { "mt9p031", MT9P031_MODEL_COLOR }, 1219cb7a01acSMauro Carvalho Chehab { "mt9p031m", MT9P031_MODEL_MONOCHROME }, 1220cb7a01acSMauro Carvalho Chehab { } 1221cb7a01acSMauro Carvalho Chehab }; 1222cb7a01acSMauro Carvalho Chehab MODULE_DEVICE_TABLE(i2c, mt9p031_id); 1223cb7a01acSMauro Carvalho Chehab 12248d4da37cSLad, Prabhakar #if IS_ENABLED(CONFIG_OF) 12258d4da37cSLad, Prabhakar static const struct of_device_id mt9p031_of_match[] = { 1226*92fa766bSMarek Vasut { .compatible = "aptina,mt9p006", }, 12278d4da37cSLad, Prabhakar { .compatible = "aptina,mt9p031", }, 12288d4da37cSLad, Prabhakar { .compatible = "aptina,mt9p031m", }, 12298d4da37cSLad, Prabhakar { /* sentinel */ }, 12308d4da37cSLad, Prabhakar }; 12318d4da37cSLad, Prabhakar MODULE_DEVICE_TABLE(of, mt9p031_of_match); 12328d4da37cSLad, Prabhakar #endif 12338d4da37cSLad, Prabhakar 1234cb7a01acSMauro Carvalho Chehab static struct i2c_driver mt9p031_i2c_driver = { 1235cb7a01acSMauro Carvalho Chehab .driver = { 12368d4da37cSLad, Prabhakar .of_match_table = of_match_ptr(mt9p031_of_match), 1237cb7a01acSMauro Carvalho Chehab .name = "mt9p031", 1238cb7a01acSMauro Carvalho Chehab }, 1239cb7a01acSMauro Carvalho Chehab .probe = mt9p031_probe, 1240cb7a01acSMauro Carvalho Chehab .remove = mt9p031_remove, 1241cb7a01acSMauro Carvalho Chehab .id_table = mt9p031_id, 1242cb7a01acSMauro Carvalho Chehab }; 1243cb7a01acSMauro Carvalho Chehab 1244cb7a01acSMauro Carvalho Chehab module_i2c_driver(mt9p031_i2c_driver); 1245cb7a01acSMauro Carvalho Chehab 1246cb7a01acSMauro Carvalho Chehab MODULE_DESCRIPTION("Aptina MT9P031 Camera driver"); 1247cb7a01acSMauro Carvalho Chehab MODULE_AUTHOR("Bastian Hecht <hechtb@gmail.com>"); 1248cb7a01acSMauro Carvalho Chehab MODULE_LICENSE("GPL v2"); 1249